| /* |
| * Copyright 2017 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. |
| */ |
| |
| // |
| // wasm-emscripten-finalize console tool |
| // Performs Emscripten-specific transforms on .wasm files |
| // |
| |
| #include <exception> |
| |
| #include "abi/js.h" |
| #include "ir/trapping.h" |
| #include "support/colors.h" |
| #include "support/debug.h" |
| #include "support/file.h" |
| #include "tool-options.h" |
| #include "wasm-binary.h" |
| #include "wasm-emscripten.h" |
| #include "wasm-io.h" |
| #include "wasm-printing.h" |
| #include "wasm-validator.h" |
| |
| #define DEBUG_TYPE "emscripten" |
| |
| using namespace cashew; |
| using namespace wasm; |
| |
| int main(int argc, const char* argv[]) { |
| const uint64_t INVALID_BASE = -1; |
| |
| std::string infile; |
| std::string outfile; |
| std::string inputSourceMapFilename; |
| std::string outputSourceMapFilename; |
| std::string outputSourceMapUrl; |
| std::string dataSegmentFile; |
| bool emitBinary = true; |
| bool debugInfo = false; |
| bool DWARF = false; |
| bool isSideModule = false; |
| bool legalizeJavaScriptFFI = true; |
| bool checkStackOverflow = false; |
| uint64_t globalBase = INVALID_BASE; |
| bool standaloneWasm = false; |
| |
| ToolOptions options("wasm-emscripten-finalize", |
| "Performs Emscripten-specific transforms on .wasm files"); |
| options |
| .add("--output", |
| "-o", |
| "Output file", |
| Options::Arguments::One, |
| [&outfile](Options*, const std::string& argument) { |
| outfile = argument; |
| Colors::setEnabled(false); |
| }) |
| .add("--debuginfo", |
| "-g", |
| "Emit names section in wasm binary (or full debuginfo in wast)", |
| Options::Arguments::Zero, |
| [&debugInfo](Options*, const std::string&) { debugInfo = true; }) |
| .add("--dwarf", |
| "", |
| "Update DWARF debug info", |
| Options::Arguments::Zero, |
| [&DWARF](Options*, const std::string&) { DWARF = true; }) |
| .add("--emit-text", |
| "-S", |
| "Emit text instead of binary for the output file", |
| Options::Arguments::Zero, |
| [&emitBinary](Options*, const std::string&) { emitBinary = false; }) |
| .add("--global-base", |
| "", |
| "The address at which static globals were placed", |
| Options::Arguments::One, |
| [&globalBase](Options*, const std::string& argument) { |
| globalBase = std::stoull(argument); |
| }) |
| // TODO(sbc): Remove this one this argument is no longer passed by |
| // emscripten. See https://github.com/emscripten-core/emscripten/issues/8905 |
| .add("--initial-stack-pointer", |
| "", |
| "ignored - will be removed in a future release", |
| Options::Arguments::One, |
| [](Options*, const std::string& argument) {}) |
| .add("--side-module", |
| "", |
| "Input is an emscripten side module", |
| Options::Arguments::Zero, |
| [&isSideModule](Options* o, const std::string& argument) { |
| isSideModule = true; |
| }) |
| .add("--input-source-map", |
| "-ism", |
| "Consume source map from the specified file", |
| Options::Arguments::One, |
| [&inputSourceMapFilename](Options* o, const std::string& argument) { |
| inputSourceMapFilename = argument; |
| }) |
| .add("--no-legalize-javascript-ffi", |
| "-nj", |
| "Do not fully legalize (i64->i32, " |
| "f32->f64) the imports and exports for interfacing with JS", |
| Options::Arguments::Zero, |
| [&legalizeJavaScriptFFI](Options* o, const std::string&) { |
| legalizeJavaScriptFFI = false; |
| }) |
| .add("--output-source-map", |
| "-osm", |
| "Emit source map to the specified file", |
| Options::Arguments::One, |
| [&outputSourceMapFilename](Options* o, const std::string& argument) { |
| outputSourceMapFilename = argument; |
| }) |
| .add("--output-source-map-url", |
| "-osu", |
| "Emit specified string as source map URL", |
| Options::Arguments::One, |
| [&outputSourceMapUrl](Options* o, const std::string& argument) { |
| outputSourceMapUrl = argument; |
| }) |
| .add("--separate-data-segments", |
| "", |
| "Separate data segments to a file", |
| Options::Arguments::One, |
| [&dataSegmentFile](Options* o, const std::string& argument) { |
| dataSegmentFile = argument; |
| }) |
| .add("--check-stack-overflow", |
| "", |
| "Check for stack overflows every time the stack is extended", |
| Options::Arguments::Zero, |
| [&checkStackOverflow](Options* o, const std::string&) { |
| checkStackOverflow = true; |
| }) |
| .add("--standalone-wasm", |
| "", |
| "Emit a wasm file that does not depend on JS, as much as possible," |
| " using wasi and other standard conventions etc. where possible", |
| Options::Arguments::Zero, |
| [&standaloneWasm](Options* o, const std::string&) { |
| standaloneWasm = true; |
| }) |
| .add_positional("INFILE", |
| Options::Arguments::One, |
| [&infile](Options* o, const std::string& argument) { |
| infile = argument; |
| }); |
| options.parse(argc, argv); |
| |
| if (infile == "") { |
| Fatal() << "Need to specify an infile\n"; |
| } |
| if (outfile == "" && emitBinary) { |
| Fatal() << "Need to specify an outfile, or use text output\n"; |
| } |
| |
| Module wasm; |
| ModuleReader reader; |
| reader.setDWARF(DWARF); |
| try { |
| reader.read(infile, wasm, inputSourceMapFilename); |
| } catch (ParseException& p) { |
| p.dump(std::cerr); |
| std::cerr << '\n'; |
| Fatal() << "error in parsing input"; |
| } catch (MapParseException& p) { |
| p.dump(std::cerr); |
| std::cerr << '\n'; |
| Fatal() << "error in parsing wasm source map"; |
| } |
| |
| options.applyFeatures(wasm); |
| |
| BYN_TRACE_WITH_TYPE("emscripten-dump", "Module before:\n"); |
| BYN_DEBUG_WITH_TYPE("emscripten-dump", |
| WasmPrinter::printModule(&wasm, std::cerr)); |
| |
| uint32_t dataSize = 0; |
| |
| if (!isSideModule) { |
| if (globalBase == INVALID_BASE) { |
| Fatal() << "globalBase must be set"; |
| } |
| Export* dataEndExport = wasm.getExport("__data_end"); |
| if (dataEndExport == nullptr) { |
| Fatal() << "__data_end export not found"; |
| } |
| Global* dataEnd = wasm.getGlobal(dataEndExport->value); |
| if (dataEnd == nullptr) { |
| Fatal() << "__data_end global not found"; |
| } |
| if (dataEnd->type != Type::i32) { |
| Fatal() << "__data_end global has wrong type"; |
| } |
| if (dataEnd->imported()) { |
| Fatal() << "__data_end must not be an imported global"; |
| } |
| Const* dataEndConst = dataEnd->init->cast<Const>(); |
| dataSize = dataEndConst->value.geti32() - globalBase; |
| } |
| |
| EmscriptenGlueGenerator generator(wasm); |
| generator.setStandalone(standaloneWasm); |
| |
| generator.fixInvokeFunctionNames(); |
| |
| std::vector<Name> initializerFunctions; |
| |
| if (wasm.table.imported()) { |
| if (wasm.table.base != "table") { |
| wasm.table.base = Name("table"); |
| } |
| } |
| if (wasm.memory.imported()) { |
| if (wasm.table.base != "memory") { |
| wasm.memory.base = Name("memory"); |
| } |
| } |
| wasm.updateMaps(); |
| |
| if (checkStackOverflow && !isSideModule) { |
| generator.enforceStackLimit(); |
| } |
| |
| if (isSideModule) { |
| BYN_TRACE("finalizing as side module\n"); |
| generator.replaceStackPointerGlobal(); |
| generator.generatePostInstantiateFunction(); |
| } else { |
| BYN_TRACE("finalizing as regular module\n"); |
| generator.generateRuntimeFunctions(); |
| generator.internalizeStackPointerGlobal(); |
| generator.generateMemoryGrowthFunction(); |
| // For side modules these gets called via __post_instantiate |
| if (Function* F = generator.generateAssignGOTEntriesFunction()) { |
| auto* ex = new Export(); |
| ex->value = F->name; |
| ex->name = F->name; |
| ex->kind = ExternalKind::Function; |
| wasm.addExport(ex); |
| initializerFunctions.push_back(F->name); |
| } |
| // Costructors get called from crt1 in wasm standalone mode. |
| // Unless there is no entry point. |
| if (!standaloneWasm || !wasm.getExportOrNull("_start")) { |
| if (auto* e = wasm.getExportOrNull(WASM_CALL_CTORS)) { |
| initializerFunctions.push_back(e->name); |
| } |
| } |
| } |
| |
| if (standaloneWasm) { |
| // Export a standard wasi "_start" method. |
| generator.exportWasiStart(); |
| } else { |
| // If not standalone wasm then JS is relevant and we need dynCalls. |
| generator.generateDynCallThunks(); |
| } |
| |
| // Legalize the wasm. |
| { |
| BYN_TRACE("legalizing types\n"); |
| PassRunner passRunner(&wasm); |
| passRunner.setOptions(options.passOptions); |
| passRunner.setDebug(options.debug); |
| passRunner.setDebugInfo(debugInfo); |
| passRunner.add(ABI::getLegalizationPass( |
| legalizeJavaScriptFFI ? ABI::LegalizationLevel::Full |
| : ABI::LegalizationLevel::Minimal)); |
| passRunner.run(); |
| } |
| |
| BYN_TRACE("generated metadata\n"); |
| // Substantial changes to the wasm are done, enough to create the metadata. |
| std::string metadata = |
| generator.generateEmscriptenMetadata(dataSize, initializerFunctions); |
| |
| // Finally, separate out data segments if relevant (they may have been needed |
| // for metadata). |
| if (!dataSegmentFile.empty()) { |
| Output memInitFile(dataSegmentFile, Flags::Binary); |
| if (globalBase == INVALID_BASE) { |
| Fatal() << "globalBase must be set"; |
| } |
| generator.separateDataSegments(&memInitFile, globalBase); |
| } |
| |
| BYN_TRACE_WITH_TYPE("emscripten-dump", "Module after:\n"); |
| BYN_DEBUG_WITH_TYPE("emscripten-dump", |
| WasmPrinter::printModule(&wasm, std::cerr)); |
| |
| // Strip target features section (its information is in the metadata) |
| { |
| PassRunner passRunner(&wasm); |
| passRunner.add("strip-target-features"); |
| passRunner.run(); |
| } |
| |
| // If DWARF is unused, strip it out. This avoids us keeping it alive |
| // until wasm-opt strips it later. |
| if (!DWARF) { |
| PassRunner passRunner(&wasm); |
| passRunner.add("strip-dwarf"); |
| passRunner.run(); |
| } |
| |
| Output output(outfile, emitBinary ? Flags::Binary : Flags::Text); |
| ModuleWriter writer; |
| writer.setDebugInfo(debugInfo); |
| // writer.setSymbolMap(symbolMap); |
| writer.setBinary(emitBinary); |
| if (outputSourceMapFilename.size()) { |
| writer.setSourceMapFilename(outputSourceMapFilename); |
| writer.setSourceMapUrl(outputSourceMapUrl); |
| } |
| writer.write(wasm, output); |
| if (emitBinary) { |
| std::cout << metadata; |
| } else { |
| output << "(;\n"; |
| output << "--BEGIN METADATA --\n" << metadata << "-- END METADATA --\n"; |
| output << ";)\n"; |
| } |
| |
| return 0; |
| } |