blob: e89c324b4b46e0ae557797b6606b6d94b679f8d6 [file] [log] [blame]
/*
* Copyright 2016 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wasm-linker.h"
#include "asm_v_wasm.h"
#include "ast_utils.h"
#include "s2wasm.h"
#include "support/utilities.h"
#include "wasm-builder.h"
#include "wasm-printing.h"
using namespace wasm;
cashew::IString EMSCRIPTEN_ASM_CONST("emscripten_asm_const");
namespace wasm {
// These are defined (not just declared) in shared-constants.h, so we can't just
// include that header. TODO: Move the definitions into a cpp file.
extern cashew::IString ENV;
extern cashew::IString MEMORY;
}
void Linker::placeStackPointer(Address stackAllocation) {
// ensure this is the first allocation
assert(nextStatic == globalBase || nextStatic == 1);
const Address pointerSize = 4;
// Unconditionally allocate space for the stack pointer. Emscripten
// allocates the stack itself, and initializes the stack pointer itself.
out.addStatic(pointerSize, pointerSize, "__stack_pointer");
if (stackAllocation) {
// If we are allocating the stack, set up a relocation to initialize the
// stack pointer to point to one past-the-end of the stack allocation.
std::vector<char> raw;
raw.resize(pointerSize);
out.addRelocation(LinkerObject::Relocation::kData, (uint32_t*)&raw[0], ".stack", stackAllocation);
assert(out.wasm.memory.segments.size() == 0);
out.addSegment("__stack_pointer", raw);
}
}
void Linker::ensureImport(Name target, std::string signature) {
if (!out.wasm.checkImport(target)) {
auto import = new Import;
import->name = import->base = target;
import->module = ENV;
import->type = ensureFunctionType(signature, &out.wasm);
out.wasm.addImport(import);
}
}
void Linker::layout() {
// Convert calls to undefined functions to call_imports
for (const auto& f : out.undefinedFunctionCalls) {
Name target = f.first;
if (!out.symbolInfo.undefinedFunctions.count(target)) continue;
// Create an import for the target if necessary.
ensureImport(target, getSig(*f.second.begin()));
// Change each call. The target is the same since it's still the name.
// Delete and re-allocate the Expression as CallImport to avoid undefined
// behavior.
for (auto* call : f.second) {
auto type = call->type;
auto operands = std::move(call->operands);
auto target = call->target;
CallImport* newCall = ExpressionManipulator::convert<Call, CallImport>(call, out.wasm.allocator);
newCall->type = type;
newCall->operands = std::move(operands);
newCall->target = target;
}
}
// Allocate all user statics
for (const auto& obj : out.staticObjects) {
allocateStatic(obj.allocSize, obj.alignment, obj.name);
}
// Update the segments with their addresses now that they have been allocated.
for (const auto& seg : out.segments) {
Address address = staticAddresses[seg.first];
out.wasm.memory.segments[seg.second].offset = address;
segmentsByAddress[address] = seg.second;
}
// Place the stack after the user's static data, to keep those addresses
// small.
if (stackAllocation) allocateStatic(stackAllocation, 16, ".stack");
// The minimum initial memory size is the amount of static variables we have
// allocated. Round it up to a page, and update the page-increment versions
// of initial and max
Address initialMem = roundUpToPageSize(nextStatic);
if (userInitialMemory) {
if (initialMem > userInitialMemory) {
Fatal() << "Specified initial memory size " << userInitialMemory <<
" is smaller than required size " << initialMem;
}
out.wasm.memory.initial = userInitialMemory / Memory::kPageSize;
} else {
out.wasm.memory.initial = initialMem / Memory::kPageSize;
}
if (userMaxMemory) out.wasm.memory.max = userMaxMemory / Memory::kPageSize;
out.wasm.memory.exportName = MEMORY;
// XXX For now, export all functions marked .globl.
for (Name name : out.globls) exportFunction(name, false);
for (Name name : out.initializerFunctions) exportFunction(name, true);
auto ensureFunctionIndex = [this](Name name) {
if (functionIndexes.count(name) == 0) {
functionIndexes[name] = out.wasm.table.names.size();
out.wasm.table.names.push_back(name);
if (debug) {
std::cerr << "function index: " << name << ": "
<< functionIndexes[name] << '\n';
}
}
};
for (auto& relocation : out.relocations) {
Name name = relocation->symbol;
if (debug) std::cerr << "fix relocation " << name << '\n';
if (relocation->kind == LinkerObject::Relocation::kData) {
const auto& symbolAddress = staticAddresses.find(name);
if (symbolAddress == staticAddresses.end()) Fatal() << "Unknown relocation: " << name << '\n';
*(relocation->data) = symbolAddress->second + relocation->addend;
if (debug) std::cerr << " ==> " << *(relocation->data) << '\n';
} else {
// function address
name = out.resolveAlias(name);
if (!out.wasm.checkFunction(name)) {
if (FunctionType* f = out.getExternType(name)) {
// Address of an imported function is taken, but imports do not have addresses in wasm.
// Generate a thunk to forward to the call_import.
Function* thunk = getImportThunk(name, f);
ensureFunctionIndex(thunk->name);
*(relocation->data) = functionIndexes[thunk->name] + relocation->addend;
} else {
std::cerr << "Unknown symbol: " << name << '\n';
if (!ignoreUnknownSymbols) Fatal() << "undefined reference\n";
*(relocation->data) = 0;
}
} else {
ensureFunctionIndex(name);
*(relocation->data) = functionIndexes[name] + relocation->addend;
}
}
}
if (!!startFunction) {
if (out.symbolInfo.implementedFunctions.count(startFunction) == 0) {
Fatal() << "Unknown start function: `" << startFunction << "`\n";
}
const auto *target = out.wasm.getFunction(startFunction);
Name start("_start");
if (out.symbolInfo.implementedFunctions.count(start) != 0) {
Fatal() << "Start function already present: `" << start << "`\n";
}
auto* func = new Function;
func->name = start;
out.wasm.addFunction(func);
exportFunction(start, true);
out.wasm.addStart(start);
auto* block = out.wasm.allocator.alloc<Block>();
func->body = block;
{
// Create the call, matching its parameters.
// TODO allow calling with non-default values.
auto* call = out.wasm.allocator.alloc<Call>();
call->target = startFunction;
size_t paramNum = 0;
for (WasmType type : target->params) {
Name name = Name::fromInt(paramNum++);
Builder::addVar(func, name, type);
auto* param = out.wasm.allocator.alloc<GetLocal>();
param->index = func->getLocalIndex(name);
param->type = type;
call->operands.push_back(param);
}
block->list.push_back(call);
block->finalize();
}
}
// ensure an explicit function type for indirect call targets
for (auto& name : out.wasm.table.names) {
auto* func = out.wasm.getFunction(name);
func->type = ensureFunctionType(getSig(func), &out.wasm)->name;
}
}
bool Linker::linkObject(S2WasmBuilder& builder) {
LinkerObject::SymbolInfo *newSymbols = builder.getSymbolInfo();
// check for multiple definitions
for (const Name& symbol : newSymbols->implementedFunctions) {
if (out.symbolInfo.implementedFunctions.count(symbol)) {
// TODO: Figure out error handling for library-style pieces
// TODO: give LinkerObjects (or builders) names for better errors.
std::cerr << "Error: multiple definition of symbol " << symbol << "\n";
return false;
}
}
// Allow duplicate aliases only if they refer to the same name. For now we
// do not expect aliases in compiler-rt files.
// TODO: figure out what the semantics of merging aliases should be.
for (const auto& alias : newSymbols->aliasedFunctions) {
if (out.symbolInfo.aliasedFunctions.count(alias.first) &&
out.symbolInfo.aliasedFunctions[alias.first] != alias.second) {
std::cerr << "Error: conflicting definitions for alias "
<< alias.first.c_str() << "\n";
return false;
}
}
out.symbolInfo.merge(*newSymbols);
builder.build(&out);
return true;
}
bool Linker::linkArchive(Archive& archive) {
bool selected;
do {
selected = false;
for (auto child = archive.child_begin(), end = archive.child_end();
child != end; ++child) {
Archive::SubBuffer memberBuf = child->getBuffer();
// S2WasmBuilder expects its input to be NUL-terminated. Archive members
// are
// not NUL-terminated. So we have to copy the contents out before parsing.
std::vector<char> memberString(memberBuf.len + 1);
memcpy(memberString.data(), memberBuf.data, memberBuf.len);
memberString[memberBuf.len] = '\0';
S2WasmBuilder memberBuilder(memberString.data(), false);
auto* memberSymbols = memberBuilder.getSymbolInfo();
for (const Name& symbol : memberSymbols->implementedFunctions) {
if (out.symbolInfo.undefinedFunctions.count(symbol)) {
if (!linkObject(memberBuilder)) return false;
selected = true;
break;
}
}
}
// If we selected an archive member, it may depend on another archive member
// so continue to make passes over the members until no more are added.
} while (selected);
return true;
}
void Linker::emscriptenGlue(std::ostream& o) {
if (debug) {
WasmPrinter::printModule(&out.wasm, std::cerr);
}
out.wasm.removeImport(EMSCRIPTEN_ASM_CONST); // we create _sig versions
makeDynCallThunks();
o << ";; METADATA: { ";
// find asmConst calls, and emit their metadata
struct AsmConstWalker : public PostWalker<AsmConstWalker, Visitor<AsmConstWalker>> {
Linker* parent;
std::map<std::string, std::set<std::string>> sigsForCode;
std::map<std::string, Address> ids;
std::set<std::string> allSigs;
void visitCallImport(CallImport* curr) {
if (curr->target == EMSCRIPTEN_ASM_CONST) {
auto arg = curr->operands[0]->cast<Const>();
Address segmentIndex = parent->segmentsByAddress[arg->value.geti32()];
std::string code = escape(&parent->out.wasm.memory.segments[segmentIndex].data[0]);
int32_t id;
if (ids.count(code) == 0) {
id = ids.size();
ids[code] = id;
} else {
id = ids[code];
}
std::string sig = getSig(curr);
sigsForCode[code].insert(sig);
std::string fixedTarget = EMSCRIPTEN_ASM_CONST.str + std::string("_") + sig;
curr->target = cashew::IString(fixedTarget.c_str(), false);
arg->value = Literal(id);
// add import, if necessary
if (allSigs.count(sig) == 0) {
allSigs.insert(sig);
auto import = new Import;
import->name = import->base = curr->target;
import->module = ENV;
import->type = ensureFunctionType(getSig(curr), &parent->out.wasm);
parent->out.wasm.addImport(import);
}
}
}
std::string escape(const char *input) {
std::string code = input;
// replace newlines quotes with escaped newlines
size_t curr = 0;
while ((curr = code.find("\\n", curr)) != std::string::npos) {
code = code.replace(curr, 2, "\\\\n");
curr += 3; // skip this one
}
// replace double quotes with escaped single quotes
curr = 0;
while ((curr = code.find('"', curr)) != std::string::npos) {
if (curr == 0 || code[curr-1] != '\\') {
code = code.replace(curr, 1, "\\" "\"");
curr += 2; // skip this one
} else { // already escaped, escape the slash as well
code = code.replace(curr, 1, "\\" "\\" "\"");
curr += 3; // skip this one
}
}
return code;
}
};
AsmConstWalker walker;
walker.parent = this;
walker.walkModule(&out.wasm);
// print
o << "\"asmConsts\": {";
bool first = true;
for (auto& pair : walker.sigsForCode) {
auto& code = pair.first;
auto& sigs = pair.second;
if (first) first = false;
else o << ",";
o << '"' << walker.ids[code] << "\": [\"" << code << "\", ";
printSet(o, sigs);
o << "]";
}
o << "}";
o << ",";
o << "\"staticBump\": " << (nextStatic - globalBase) << ", ";
o << "\"initializers\": [";
first = true;
for (const auto& func : out.initializerFunctions) {
if (first) first = false;
else o << ", ";
o << "\"" << func.c_str() << "\"";
}
o << "]";
o << " }\n";
}
bool hasI64ResultOrParam(FunctionType* ft) {
if (ft->result == i64) return true;
for (auto ty : ft->params) {
if (ty == i64) return true;
}
return false;
}
void Linker::makeDynCallThunks() {
std::unordered_set<std::string> sigs;
wasm::Builder wasmBuilder(out.wasm);
for (const auto& indirectFunc : out.wasm.table.names) {
std::string sig(getSig(out.wasm.getFunction(indirectFunc)));
auto* funcType = ensureFunctionType(sig, &out.wasm);
if (hasI64ResultOrParam(funcType)) continue; // Can't export i64s on the web.
if (!sigs.insert(sig).second) continue; // Sig is already in the set
std::vector<NameType> params;
params.emplace_back("fptr", i32); // function pointer param
int p = 0;
for (const auto& ty : funcType->params) params.emplace_back(std::to_string(p++), ty);
Function* f = wasmBuilder.makeFunction(std::string("dynCall_") + sig, std::move(params), funcType->result, {});
Expression* fptr = wasmBuilder.makeGetLocal(0, i32);
std::vector<Expression*> args;
for (unsigned i = 0; i < funcType->params.size(); ++i) {
args.push_back(wasmBuilder.makeGetLocal(i + 1, funcType->params[i]));
}
Expression* call = wasmBuilder.makeCallIndirect(funcType, fptr, args);
f->body = call;
out.wasm.addFunction(f);
exportFunction(f->name, true);
}
}
Function* Linker::getImportThunk(Name name, const FunctionType* funcType) {
Name thunkName = std::string("__importThunk_") + name.c_str();
if (Function* thunk = out.wasm.checkFunction(thunkName)) return thunk;
ensureImport(name, getSig(funcType));
wasm::Builder wasmBuilder(out.wasm);
std::vector<NameType> params;
Index p = 0;
for (const auto& ty : funcType->params) params.emplace_back(std::to_string(p++), ty);
Function *f = wasmBuilder.makeFunction(thunkName, std::move(params), funcType->result, {});
std::vector<Expression*> args;
for (Index i = 0; i < funcType->params.size(); ++i) {
args.push_back(wasmBuilder.makeGetLocal(i, funcType->params[i]));
}
Expression* call = wasmBuilder.makeCallImport(name, args);
f->body = call;
out.wasm.addFunction(f);
return f;
}