/* * 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 #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(); dataSize = dataEndConst->value.geti32() - globalBase; } EmscriptenGlueGenerator generator(wasm); generator.setStandalone(standaloneWasm); generator.fixInvokeFunctionNames(); std::vector 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; }