]>
Commit | Line | Data |
---|---|---|
abe05a73 XL |
1 | /* |
2 | * Copyright 2016 WebAssembly Community Group participants | |
3 | * | |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include "wasm-linker.h" | |
18 | #include "asm_v_wasm.h" | |
19 | #include "ir/utils.h" | |
20 | #include "s2wasm.h" | |
21 | #include "support/utilities.h" | |
22 | #include "wasm-builder.h" | |
23 | #include "wasm-emscripten.h" | |
24 | #include "wasm-printing.h" | |
25 | ||
26 | using namespace wasm; | |
27 | ||
28 | // Name of the dummy function to prevent erroneous nullptr comparisons. | |
29 | static constexpr const char* dummyFunction = "__wasm_nullptr"; | |
30 | static constexpr const char* stackPointer = "__stack_pointer"; | |
31 | ||
32 | void Linker::placeStackPointer(Address stackAllocation) { | |
33 | // ensure this is the first allocation | |
34 | assert(nextStatic == globalBase || nextStatic == 1); | |
35 | const Address pointerSize = 4; | |
36 | // Unconditionally allocate space for the stack pointer. Emscripten | |
37 | // allocates the stack itself, and initializes the stack pointer itself. | |
38 | out.addStatic(pointerSize, pointerSize, stackPointer); | |
39 | if (stackAllocation) { | |
40 | // If we are allocating the stack, set up a relocation to initialize the | |
41 | // stack pointer to point to one past-the-end of the stack allocation. | |
42 | std::vector<char> raw; | |
43 | raw.resize(pointerSize); | |
44 | auto relocation = new LinkerObject::Relocation( | |
45 | LinkerObject::Relocation::kData, (uint32_t*)&raw[0], ".stack", stackAllocation); | |
46 | out.addRelocation(relocation); | |
47 | assert(out.wasm.memory.segments.empty()); | |
48 | out.addSegment(stackPointer, raw); | |
49 | } | |
50 | } | |
51 | ||
52 | void Linker::ensureFunctionImport(Name target, std::string signature) { | |
53 | if (!out.wasm.getImportOrNull(target)) { | |
54 | auto import = new Import; | |
55 | import->name = import->base = target; | |
56 | import->module = ENV; | |
57 | import->functionType = ensureFunctionType(signature, &out.wasm)->name; | |
58 | import->kind = ExternalKind::Function; | |
59 | out.wasm.addImport(import); | |
60 | } | |
61 | } | |
62 | ||
63 | void Linker::ensureObjectImport(Name target) { | |
64 | if (!out.wasm.getImportOrNull(target)) { | |
65 | auto import = new Import; | |
66 | import->name = import->base = target; | |
67 | import->module = ENV; | |
68 | import->kind = ExternalKind::Global; | |
69 | import->globalType = i32; | |
70 | out.wasm.addImport(import); | |
71 | } | |
72 | } | |
73 | ||
74 | void Linker::layout() { | |
75 | // Convert calls to undefined functions to call_imports | |
76 | for (const auto& f : out.undefinedFunctionCalls) { | |
77 | Name target = f.first; | |
78 | if (!out.symbolInfo.undefinedFunctions.count(target)) continue; | |
79 | // Create an import for the target if necessary. | |
80 | ensureFunctionImport(target, getSig(*f.second.begin())); | |
81 | // Change each call. The target is the same since it's still the name. | |
82 | // Delete and re-allocate the Expression as CallImport to avoid undefined | |
83 | // behavior. | |
84 | for (auto* call : f.second) { | |
85 | auto type = call->type; | |
86 | ExpressionList operands(out.wasm.allocator); | |
87 | operands.swap(call->operands); | |
88 | auto target = call->target; | |
89 | CallImport* newCall = ExpressionManipulator::convert<Call, CallImport>(call, out.wasm.allocator); | |
90 | newCall->type = type; | |
91 | newCall->operands.swap(operands); | |
92 | newCall->target = target; | |
93 | } | |
94 | } | |
95 | ||
96 | // Allocate all user statics | |
97 | for (const auto& obj : out.staticObjects) { | |
98 | allocateStatic(obj.allocSize, obj.alignment, obj.name); | |
99 | } | |
100 | ||
101 | // Update the segments with their addresses now that they have been allocated. | |
102 | for (const auto& seg : out.segments) { | |
103 | Address address = staticAddresses[seg.first]; | |
104 | out.wasm.memory.segments[seg.second].offset = out.wasm.allocator.alloc<Const>()->set(Literal(uint32_t(address))); | |
105 | segmentsByAddress[address] = seg.second; | |
106 | } | |
107 | ||
108 | // Place the stack after the user's static data, to keep those addresses | |
109 | // small. | |
110 | if (stackAllocation) allocateStatic(stackAllocation, 16, ".stack"); | |
111 | ||
112 | // The minimum initial memory size is the amount of static variables we have | |
113 | // allocated. Round it up to a page, and update the page-increment versions | |
114 | // of initial and max | |
115 | Address initialMem = roundUpToPageSize(nextStatic); | |
116 | if (userInitialMemory) { | |
117 | if (initialMem > userInitialMemory) { | |
118 | Fatal() << "Specified initial memory size " << userInitialMemory << | |
119 | " is smaller than required size " << initialMem; | |
120 | } | |
121 | out.wasm.memory.initial = userInitialMemory / Memory::kPageSize; | |
122 | } else { | |
123 | out.wasm.memory.initial = initialMem / Memory::kPageSize; | |
124 | } | |
125 | out.wasm.memory.exists = true; | |
126 | ||
127 | if (userMaxMemory) out.wasm.memory.max = userMaxMemory / Memory::kPageSize; | |
128 | ||
129 | if (importMemory) { | |
130 | auto memoryImport = make_unique<Import>(); | |
131 | memoryImport->name = MEMORY; | |
132 | memoryImport->module = ENV; | |
133 | memoryImport->base = MEMORY; | |
134 | memoryImport->kind = ExternalKind::Memory; | |
135 | out.wasm.memory.imported = true; | |
136 | out.wasm.addImport(memoryImport.release()); | |
137 | } else { | |
138 | auto memoryExport = make_unique<Export>(); | |
139 | memoryExport->name = MEMORY; | |
140 | memoryExport->value = Name::fromInt(0); | |
141 | memoryExport->kind = ExternalKind::Memory; | |
142 | out.wasm.addExport(memoryExport.release()); | |
143 | } | |
144 | ||
145 | // Add imports for any imported objects | |
146 | for (const auto& obj : out.symbolInfo.importedObjects) { | |
147 | ensureObjectImport(obj); | |
148 | } | |
149 | ||
150 | // XXX For now, export all functions marked .globl. | |
151 | for (Name name : out.globls) exportFunction(out.wasm, name, false); | |
152 | for (Name name : out.initializerFunctions) exportFunction(out.wasm, name, true); | |
153 | ||
154 | // Pad the indirect function table with a dummy function | |
155 | makeDummyFunction(); | |
156 | // Always create a table, even if it's empty. | |
157 | out.wasm.table.exists = true; | |
158 | ||
159 | // Pre-assign the function indexes | |
160 | for (auto& pair : out.indirectIndexes) { | |
161 | if (functionIndexes.count(pair.first) != 0 || | |
162 | functionNames.count(pair.second) != 0) { | |
163 | Fatal() << "Function " << pair.first << " already has an index " << | |
164 | functionIndexes[pair.first] << " while setting index " << pair.second; | |
165 | } | |
166 | if (debug) { | |
167 | std::cerr << "pre-assigned function index: " << pair.first << ": " | |
168 | << pair.second << '\n'; | |
169 | } | |
170 | functionIndexes[pair.first] = pair.second; | |
171 | functionNames[pair.second] = pair.first; | |
172 | } | |
173 | ||
174 | // Emit the pre-assigned function names in sorted order | |
175 | for (const auto& P : functionNames) { | |
176 | ensureTableSegment(); | |
177 | getTableDataRef().push_back(P.second); | |
178 | } | |
179 | ||
180 | for (auto& relocation : out.relocations) { | |
181 | // TODO: Handle weak symbols properly, instead of always taking the weak definition. | |
182 | auto *alias = out.getAlias(relocation->symbol, relocation->kind); | |
183 | Name name = relocation->symbol; | |
184 | ||
185 | if (debug) std::cerr << "fix relocation " << name << '\n'; | |
186 | ||
187 | if (alias) { | |
188 | name = alias->symbol; | |
189 | relocation->addend += alias->offset; | |
190 | } | |
191 | ||
192 | if (relocation->kind == LinkerObject::Relocation::kData) { | |
193 | const auto& symbolAddress = staticAddresses.find(name); | |
194 | if (symbolAddress == staticAddresses.end()) Fatal() << "Unknown relocation: " << name << '\n'; | |
195 | *(relocation->data) = symbolAddress->second + relocation->addend; | |
196 | if (debug) std::cerr << " ==> " << *(relocation->data) << '\n'; | |
197 | } else { | |
198 | // function address | |
199 | if (!out.wasm.getFunctionOrNull(name)) { | |
200 | if (FunctionType* f = out.getExternType(name)) { | |
201 | // Address of an imported function is taken, but imports do not have addresses in wasm. | |
202 | // Generate a thunk to forward to the call_import. | |
203 | Function* thunk = getImportThunk(name, f); | |
204 | *(relocation->data) = getFunctionIndex(thunk->name) + relocation->addend; | |
205 | } else { | |
206 | std::cerr << "Unknown symbol: " << name << '\n'; | |
207 | if (!ignoreUnknownSymbols) Fatal() << "undefined reference\n"; | |
208 | *(relocation->data) = 0; | |
209 | } | |
210 | } else { | |
211 | *(relocation->data) = getFunctionIndex(name) + relocation->addend; | |
212 | } | |
213 | } | |
214 | } | |
215 | out.relocations.clear(); | |
216 | ||
217 | if (!!startFunction) { | |
218 | if (out.symbolInfo.implementedFunctions.count(startFunction) == 0) { | |
219 | Fatal() << "Unknown start function: `" << startFunction << "`\n"; | |
220 | } | |
221 | const auto *target = out.wasm.getFunction(startFunction); | |
222 | Name start("_start"); | |
223 | if (out.symbolInfo.implementedFunctions.count(start) != 0) { | |
224 | Fatal() << "Start function already present: `" << start << "`\n"; | |
225 | } | |
226 | auto* func = new Function; | |
227 | func->name = start; | |
228 | out.wasm.addFunction(func); | |
229 | out.wasm.addStart(start); | |
230 | Builder builder(out.wasm); | |
231 | auto* block = builder.makeBlock(); | |
232 | func->body = block; | |
233 | { | |
234 | // Create the call, matching its parameters. | |
235 | // TODO allow calling with non-default values. | |
236 | std::vector<Expression*> args; | |
237 | Index paramNum = 0; | |
238 | for (WasmType type : target->params) { | |
239 | Name name = Name::fromInt(paramNum++); | |
240 | Builder::addVar(func, name, type); | |
241 | auto* param = builder.makeGetLocal(func->getLocalIndex(name), type); | |
242 | args.push_back(param); | |
243 | } | |
244 | auto* call = builder.makeCall(startFunction, args, target->result); | |
245 | Expression* e = call; | |
246 | if (target->result != none) e = builder.makeDrop(call); | |
247 | block->list.push_back(e); | |
248 | block->finalize(); | |
249 | } | |
250 | } | |
251 | ||
252 | // ensure an explicit function type for indirect call targets | |
253 | for (auto& segment : out.wasm.table.segments) { | |
254 | for (auto& name : segment.data) { | |
255 | auto* func = out.wasm.getFunction(name); | |
256 | func->type = ensureFunctionType(getSig(func), &out.wasm)->name; | |
257 | } | |
258 | } | |
259 | ||
260 | // Export malloc/realloc/free/memalign whenever availble. JavsScript version of | |
261 | // malloc has some issues and it cannot be called once _sbrk() is called, and | |
262 | // JS glue code does not have realloc(). TODO This should get the list of | |
263 | // exported functions from emcc.py - it has EXPORTED_FUNCTION metadata to keep | |
264 | // track of this. Get the list of exported functions using a command-line | |
265 | // argument from emcc.py and export all of them. | |
266 | for (auto function : {"malloc", "free", "realloc", "memalign"}) { | |
267 | if (out.symbolInfo.implementedFunctions.count(function)) { | |
268 | exportFunction(out.wasm, function, true); | |
269 | } | |
270 | } | |
271 | ||
272 | // finalize function table | |
273 | unsigned int tableSize = getTableData().size(); | |
274 | if (tableSize > 0) { | |
275 | out.wasm.table.initial = out.wasm.table.max = tableSize; | |
276 | } | |
277 | } | |
278 | ||
279 | bool Linker::linkObject(S2WasmBuilder& builder) { | |
280 | LinkerObject::SymbolInfo *newSymbols = builder.getSymbolInfo(); | |
281 | // check for multiple definitions | |
282 | for (const Name& symbol : newSymbols->implementedFunctions) { | |
283 | if (out.symbolInfo.implementedFunctions.count(symbol)) { | |
284 | // TODO: Figure out error handling for library-style pieces | |
285 | // TODO: give LinkerObjects (or builders) names for better errors. | |
286 | std::cerr << "Error: multiple definition of symbol " << symbol << "\n"; | |
287 | return false; | |
288 | } | |
289 | } | |
290 | // Allow duplicate aliases only if they refer to the same name. For now we | |
291 | // do not expect aliases in compiler-rt files. | |
292 | // TODO: figure out what the semantics of merging aliases should be. | |
293 | for (const auto& alias : newSymbols->aliasedSymbols) { | |
294 | if (out.symbolInfo.aliasedSymbols.count(alias.first) && | |
295 | (out.symbolInfo.aliasedSymbols.at(alias.first).symbol != alias.second.symbol || | |
296 | out.symbolInfo.aliasedSymbols.at(alias.first).kind != alias.second.kind)) { | |
297 | std::cerr << "Error: conflicting definitions for alias " | |
298 | << alias.first.c_str() << "of type " << alias.second.kind << "\n"; | |
299 | return false; | |
300 | } | |
301 | } | |
302 | out.symbolInfo.merge(*newSymbols); | |
303 | builder.build(&out); | |
304 | return true; | |
305 | } | |
306 | ||
307 | bool Linker::linkArchive(Archive& archive) { | |
308 | bool selected; | |
309 | do { | |
310 | selected = false; | |
311 | for (auto child = archive.child_begin(), end = archive.child_end(); | |
312 | child != end; ++child) { | |
313 | Archive::SubBuffer memberBuf = child->getBuffer(); | |
314 | // S2WasmBuilder expects its input to be NUL-terminated. Archive members | |
315 | // are | |
316 | // not NUL-terminated. So we have to copy the contents out before parsing. | |
317 | std::vector<char> memberString(memberBuf.len + 1); | |
318 | memcpy(memberString.data(), memberBuf.data, memberBuf.len); | |
319 | memberString[memberBuf.len] = '\0'; | |
320 | S2WasmBuilder memberBuilder(memberString.data(), false); | |
321 | auto* memberSymbols = memberBuilder.getSymbolInfo(); | |
322 | for (const Name& symbol : memberSymbols->implementedFunctions) { | |
323 | if (out.symbolInfo.undefinedFunctions.count(symbol)) { | |
324 | if (!linkObject(memberBuilder)) return false; | |
325 | selected = true; | |
326 | break; | |
327 | } | |
328 | } | |
329 | } | |
330 | // If we selected an archive member, it may depend on another archive member | |
331 | // so continue to make passes over the members until no more are added. | |
332 | } while (selected); | |
333 | return true; | |
334 | } | |
335 | ||
336 | Address Linker::getStaticBump() const { | |
337 | return nextStatic - globalBase; | |
338 | } | |
339 | ||
340 | void Linker::ensureTableSegment() { | |
341 | if (out.wasm.table.segments.size() == 0) { | |
342 | auto emptySegment = out.wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0))); | |
343 | out.wasm.table.segments.emplace_back(emptySegment); | |
344 | } | |
345 | } | |
346 | ||
347 | std::vector<Name>& Linker::getTableDataRef() { | |
348 | assert(out.wasm.table.segments.size() == 1); | |
349 | return out.wasm.table.segments[0].data; | |
350 | } | |
351 | ||
352 | std::vector<Name> Linker::getTableData() { | |
353 | if (out.wasm.table.segments.size() > 0) { | |
354 | return getTableDataRef(); | |
355 | } | |
356 | return {}; | |
357 | } | |
358 | ||
359 | Index Linker::getFunctionIndex(Name name) { | |
360 | if (!functionIndexes.count(name)) { | |
361 | ensureTableSegment(); | |
362 | functionIndexes[name] = getTableData().size(); | |
363 | getTableDataRef().push_back(name); | |
364 | if (debug) { | |
365 | std::cerr << "function index: " << name << ": " | |
366 | << functionIndexes[name] << '\n'; | |
367 | } | |
368 | } | |
369 | return functionIndexes[name]; | |
370 | } | |
371 | ||
372 | void Linker::makeDummyFunction() { | |
373 | bool create = false; | |
374 | // Check if there are address-taken functions | |
375 | for (auto& relocation : out.relocations) { | |
376 | if (relocation->kind == LinkerObject::Relocation::kFunction) { | |
377 | create = true; | |
378 | break; | |
379 | } | |
380 | } | |
381 | ||
382 | if (!create) return; | |
383 | wasm::Builder wasmBuilder(out.wasm); | |
384 | Expression *unreachable = wasmBuilder.makeUnreachable(); | |
385 | Function *dummy = wasmBuilder.makeFunction(Name(dummyFunction), {}, WasmType::none, {}, unreachable); | |
386 | out.wasm.addFunction(dummy); | |
387 | getFunctionIndex(dummy->name); | |
388 | } | |
389 | ||
390 | Function* Linker::getImportThunk(Name name, const FunctionType* funcType) { | |
391 | Name thunkName = std::string("__importThunk_") + name.c_str(); | |
392 | if (Function* thunk = out.wasm.getFunctionOrNull(thunkName)) return thunk; | |
393 | ensureFunctionImport(name, getSig(funcType)); | |
394 | wasm::Builder wasmBuilder(out.wasm); | |
395 | std::vector<NameType> params; | |
396 | Index p = 0; | |
397 | for (const auto& ty : funcType->params) params.emplace_back(std::to_string(p++), ty); | |
398 | Function *f = wasmBuilder.makeFunction(thunkName, std::move(params), funcType->result, {}); | |
399 | std::vector<Expression*> args; | |
400 | for (Index i = 0; i < funcType->params.size(); ++i) { | |
401 | args.push_back(wasmBuilder.makeGetLocal(i, funcType->params[i])); | |
402 | } | |
403 | Expression* call = wasmBuilder.makeCallImport(name, args, funcType->result); | |
404 | f->body = call; | |
405 | out.wasm.addFunction(f); | |
406 | return f; | |
407 | } | |
408 | ||
409 | Address Linker::getStackPointerAddress() const { | |
410 | return Address(staticAddresses.at(stackPointer)); | |
411 | } |