diff options
author | Jacob Gravelle <jgravelle@google.com> | 2018-01-22 12:50:36 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-22 12:50:36 -0800 |
commit | 02729a12e1735f629d3066b51c96a056f712b080 (patch) | |
tree | 353a495836776695d0f86f08b6292635c4dba101 | |
parent | b01f2bb237e086fe4ae852c6004297fa8f8b39c2 (diff) | |
download | binaryen-02729a12e1735f629d3066b51c96a056f712b080.tar.gz binaryen-02729a12e1735f629d3066b51c96a056f712b080.tar.bz2 binaryen-02729a12e1735f629d3066b51c96a056f712b080.zip |
First pass at LLD support for Emscripten (#1346)
* Skeleton of a beginning of o2wasm, WIP and probably not going to be used
* Get building post-cherry-pick
* ast->ir, remove commented out code, include a debug module print because linking
* Read linking section, print emscripten metadata json
* WasmBinaryWriter emits user sections on Module
* Remove debugging prints, everything that isn't needed to build metadata
* Rename o2wasm to lld-metadata
* lld-metadata support for outputting to file
* Use tables index instead of function index for initializer functions
* Add lld-emscripten tool to add emscripten-runtime functions to wasm modules (built with lld)
* Handle EM_ASM in lld-emscripten
* Add a list of functions to forcibly export (for initializer functions)
* Disable incorrect initializer function reading
* Add error printing when parsing .o files in lld-metadata
* Remove ';; METADATA: ' prefix from lld-metadata, output is now standalone json
* Support em_asm consts that aren't at the start of a segment
* Initial test framework for lld-metadata tool
* Add em_asm test
* Add support for WASM_INIT_FUNCS in the linking section
* Remove reloc section parsing because it's unused
* lld-emscripten can read and write text
* Add test harness for lld-emscripten
* Export all functions for now
* Add missing lld test output
* Add support for reading object files differently
Only difference so far is in importing mutable globals being an object
file representation for symbols, but invalid wasm.
* Update help strings
* Update linking tests for stackAlloc fix
* Rename lld-emscripten,lld-metadata to wasm-emscripten-finalize,wasm-link-metadata
* Add help text to header comments
* auto& instead of auto &
* Extract LinkType to abi/wasm-object.h
* Remove special handling for wasm object file reading, allow mutable globals
* Add braces around default switch case
* Fix flake8 errors
* Handle generating dyncall thunks for imports as well
* Use explicit bool for stackPointerGlobal
* Use glob patterns for lld file iteration
* Use __wasm_call_ctors for all initializer functions
36 files changed, 1159 insertions, 62 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e27b602f..13f963106 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -275,6 +275,28 @@ SET_PROPERTY(TARGET s2wasm PROPERTY CXX_STANDARD 11) SET_PROPERTY(TARGET s2wasm PROPERTY CXX_STANDARD_REQUIRED ON) INSTALL(TARGETS s2wasm DESTINATION ${CMAKE_INSTALL_BINDIR}) +SET(wasm-link-metadata_SOURCES + src/tools/wasm-link-metadata.cpp + src/wasm-emscripten.cpp +) +ADD_EXECUTABLE(wasm-link-metadata + ${wasm-link-metadata_SOURCES}) +TARGET_LINK_LIBRARIES(wasm-link-metadata passes wasm asmjs ir cfg support) +SET_PROPERTY(TARGET wasm-link-metadata PROPERTY CXX_STANDARD 11) +SET_PROPERTY(TARGET wasm-link-metadata PROPERTY CXX_STANDARD_REQUIRED ON) +INSTALL(TARGETS wasm-link-metadata DESTINATION ${CMAKE_INSTALL_BINDIR}) + +SET(wasm-emscripten-finalize_SOURCES + src/tools/wasm-emscripten-finalize.cpp + src/wasm-emscripten.cpp +) +ADD_EXECUTABLE(wasm-emscripten-finalize + ${wasm-emscripten-finalize_SOURCES}) +TARGET_LINK_LIBRARIES(wasm-emscripten-finalize passes wasm asmjs ir cfg support) +SET_PROPERTY(TARGET wasm-emscripten-finalize PROPERTY CXX_STANDARD 11) +SET_PROPERTY(TARGET wasm-emscripten-finalize PROPERTY CXX_STANDARD_REQUIRED ON) +INSTALL(TARGETS wasm-emscripten-finalize DESTINATION ${CMAKE_INSTALL_BINDIR}) + SET(wasm_as_SOURCES src/tools/wasm-as.cpp ) diff --git a/auto_update_tests.py b/auto_update_tests.py index c2cdc1601..10481cab5 100755 --- a/auto_update_tests.py +++ b/auto_update_tests.py @@ -6,7 +6,8 @@ from scripts.test.support import run_command, split_wast, node_test_glue, node_h from scripts.test.shared import ( ASM2WASM, MOZJS, NODEJS, S2WASM, WASM_SHELL, WASM_OPT, WASM_AS, WASM_DIS, WASM_CTOR_EVAL, WASM_MERGE, WASM_REDUCE, WASM2ASM, WASM_METADCE, - BINARYEN_INSTALL_DIR, has_shell_timeout) + WASM_LINK_METADATA, WASM_EMSCRIPTEN_FINALIZE, BINARYEN_INSTALL_DIR, + files_with_pattern, has_shell_timeout) from scripts.test.wasm2asm import tests, spec_tests, extra_tests, assert_tests print '[ processing and updating testcases... ]\n' @@ -72,6 +73,25 @@ for dot_s_dir in ['dot_s', 'llvm_autogenerated']: with open(expected_file, 'w') as o: o.write(actual) +print '\n[ checking wasm-link-metadata testcases... ]\n' + +for obj_path in files_with_pattern('test', 'lld', '*.o'): + print '..', obj_path + json_path = obj_path.replace('.o', '.json') + + cmd = WASM_LINK_METADATA + [obj_path] + actual = run_command(cmd) + with open(json_path, 'w') as o: o.write(actual) + +print '\n[ checking wasm-emscripten-finalize testcases... ]\n' + +for wast_path in files_with_pattern('test', 'lld', '*.wast'): + print '..', wast_path + out_path = wast_path + '.out' + cmd = WASM_EMSCRIPTEN_FINALIZE + [wast_path, '-S'] + actual = run_command(cmd) + with open(out_path, 'w') as o: o.write(actual) + for t in sorted(os.listdir(os.path.join('test', 'print'))): if t.endswith('.wast'): print '..', t @@ -31,6 +31,7 @@ from scripts.test.shared import ( ) import scripts.test.asm2wasm as asm2wasm +import scripts.test.lld as lld import scripts.test.s2wasm as s2wasm import scripts.test.wasm2asm as wasm2asm @@ -604,6 +605,8 @@ def main(): run_binaryen_js_tests() s2wasm.test_s2wasm() s2wasm.test_linker() + lld.test_wasm_link_metadata() + lld.test_wasm_emscripten_finalize() wasm2asm.test_wasm2asm() run_validator_tests() if options.torture and options.test_waterfall: diff --git a/scripts/test/generate_lld_tests.py b/scripts/test/generate_lld_tests.py new file mode 100755 index 000000000..c959bd857 --- /dev/null +++ b/scripts/test/generate_lld_tests.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +# 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. + +import os +import sys +from support import run_command +import shared + + +def files_with_extensions(path, extensions): + for file in sorted(os.listdir(path)): + _, ext = os.path.splitext(file) + if ext in extensions: + yield file, ext + + +def generate_object_files(clang_bin): + print '\n[ building object files from C sources... ]\n' + + lld_path = os.path.join(shared.options.binaryen_test, 'lld') + for src_file, ext in files_with_extensions(lld_path, ['.c', '.cpp']): + print '..', src_file + obj_file = src_file.replace(ext, '.o') + + src_path = os.path.join(lld_path, src_file) + obj_path = os.path.join(lld_path, obj_file) + run_command([ + clang_bin, src_path, '-o', obj_path, + '--target=wasm32-unknown-unknown-wasm', + '-c', + '-nostdinc', + '-Xclang', '-nobuiltininc', + '-Xclang', '-nostdsysteminc', + '-Xclang', '-I/s/work/emscripten/system/include', + '-O1', + ]) + + +def generate_wast_files(lld_bin): + print '\n[ linking wasm files from object files... ]\n' + + lld_path = os.path.join(shared.options.binaryen_test, 'lld') + for obj_file, ext in files_with_extensions(lld_path, ['.o']): + print '..', obj_file + wasm_file = obj_file.replace(ext, '.wasm') + wast_file = obj_file.replace(ext, '.wast') + + obj_path = os.path.join(lld_path, obj_file) + wasm_path = os.path.join(lld_path, wasm_file) + wast_path = os.path.join(lld_path, wast_file) + run_command([ + lld_bin, '-flavor', 'wasm', + '-z', '-stack-size=1048576', + obj_path, '-o', wasm_path, + '--entry=main', + '--allow-undefined', + '--export', '__wasm_call_ctors', + ]) + try: + run_command(shared.WASM_DIS + [wasm_path, '-o', wast_path]) + finally: + # Don't need the .wasm file, don't leave it around + shared.delete_from_orbit(wasm_path) + + +if __name__ == '__main__': + if len(sys.argv) != 3: + print 'Usage: generate_lld_tests.py [path/to/clang] [path/to/lld]' + sys.exit(1) + generate_object_files(sys.argv[1]) + generate_wast_files(sys.argv[2]) diff --git a/scripts/test/lld.py b/scripts/test/lld.py new file mode 100755 index 000000000..793031ca6 --- /dev/null +++ b/scripts/test/lld.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +# 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. + +import os +from support import run_command +from shared import ( + fail, fail_with_error, files_with_pattern, options, + WASM_LINK_METADATA, WASM_EMSCRIPTEN_FINALIZE +) + + +def test_wasm_link_metadata(): + print '\n[ checking wasm-link-metadata testcases... ]\n' + + for obj_path in files_with_pattern(options.binaryen_test, 'lld', '*.o'): + print '..', obj_path + expected_file = obj_path.replace('.o', '.json') + + cmd = WASM_LINK_METADATA + [obj_path] + actual = run_command(cmd) + + if not os.path.exists(expected_file): + print actual + fail_with_error('output ' + expected_file + ' does not exist') + expected = open(expected_file, 'rb').read() + if actual != expected: + fail(actual, expected) + + +def test_wasm_emscripten_finalize(): + print '\n[ checking wasm-emscripten-finalize testcases... ]\n' + + for wast_path in files_with_pattern(options.binaryen_test, 'lld', '*.wast'): + print '..', wast_path + expected_file = wast_path + '.out' + cmd = WASM_EMSCRIPTEN_FINALIZE + [wast_path, '-S'] + actual = run_command(cmd) + + if not os.path.exists(expected_file): + print actual + fail_with_error('output ' + expected_file + ' does not exist') + expected = open(expected_file, 'rb').read() + if actual != expected: + fail(actual, expected) + + +if __name__ == '__main__': + test_wasm_link_metadata() + test_wasm_emscripten_finalize() diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 9933f85e1..863e97ec0 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -1,5 +1,6 @@ import argparse import difflib +import glob import os import shutil import subprocess @@ -169,6 +170,9 @@ WASM_MERGE = [os.path.join(options.binaryen_bin, 'wasm-merge')] S2WASM = [os.path.join(options.binaryen_bin, 's2wasm')] WASM_REDUCE = [os.path.join(options.binaryen_bin, 'wasm-reduce')] WASM_METADCE = [os.path.join(options.binaryen_bin, 'wasm-metadce')] +WASM_LINK_METADATA = [os.path.join(options.binaryen_bin, 'wasm-link-metadata')] +WASM_EMSCRIPTEN_FINALIZE = [os.path.join(options.binaryen_bin, + 'wasm-emscripten-finalize')] S2WASM_EXE = S2WASM[0] WASM_SHELL_EXE = WASM_SHELL[0] @@ -429,3 +433,7 @@ def minify_check(wast, verify_final_result=True): os.unlink('a.wast') if os.path.exists('b.wast'): os.unlink('b.wast') + + +def files_with_pattern(*path_pattern): + return sorted(glob.glob(os.path.join(*path_pattern))) diff --git a/src/abi/wasm-object.h b/src/abi/wasm-object.h new file mode 100644 index 000000000..0ce814030 --- /dev/null +++ b/src/abi/wasm-object.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 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. + */ + +// +// Contains definitions used for wasm object files. +// See: https://github.com/WebAssembly/tool-conventions/blob/master/Linking.md +// + +#ifndef wasm_abi_wasm_object_h +#define wasm_abi_wasm_object_h + +namespace wasm { + +namespace ABI { + enum LinkType : unsigned { + WASM_STACK_POINTER = 0x1, + WASM_SYMBOL_INFO = 0x2, + WASM_DATA_SIZE = 0x3, + WASM_DATA_ALIGNMENT = 0x4, + WASM_SEGMENT_INFO = 0x5, + WASM_INIT_FUNCS = 0x6, + }; +} // namespace ABI + +} // namespace wasm + +#endif // wasm_abi_wasm_object_h diff --git a/src/tools/s2wasm.cpp b/src/tools/s2wasm.cpp index 6e7b2c05e..32af57dba 100644 --- a/src/tools/s2wasm.cpp +++ b/src/tools/s2wasm.cpp @@ -184,7 +184,7 @@ int main(int argc, const char *argv[]) { std::cerr << "Emscripten gluing..." << std::endl; WasmPrinter::printModule(&wasm, std::cerr); } - metadata = emscriptenGlue( + metadata = ";; METADATA: " + emscriptenGlue( wasm, allowMemoryGrowth, linker.getStackPointerAddress(), diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp new file mode 100644 index 000000000..87505748c --- /dev/null +++ b/src/tools/wasm-emscripten-finalize.cpp @@ -0,0 +1,103 @@ +/* + * 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 "ir/trapping.h" +#include "support/colors.h" +#include "support/command-line.h" +#include "support/file.h" +#include "wasm-binary.h" +#include "wasm-emscripten.h" +#include "wasm-io.h" +#include "wasm-linker.h" +#include "wasm-printing.h" +#include "wasm-validator.h" + +using namespace cashew; +using namespace wasm; + +int main(int argc, const char *argv[]) { + std::string infile; + std::string outfile; + bool emitBinary = true; + std::vector<Name> forcedExports; + Options 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::disable(); + }) + .add("--emit-text", "-S", "Emit text instead of binary for the output file", + Options::Arguments::Zero, + [&emitBinary](Options*, const std::string &) { + emitBinary = false; + }) + .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.read(infile, wasm); + + if (options.debug) { + std::cerr << "Module before:\n"; + WasmPrinter::printModule(&wasm, std::cerr); + } + + EmscriptenGlueGenerator generator(wasm); + generator.generateRuntimeFunctions(); + generator.generateMemoryGrowthFunction(); + generator.generateDynCallThunks(); + generator.fixEmAsmConsts(); + + if (options.debug) { + std::cerr << "Module after:\n"; + WasmPrinter::printModule(&wasm, std::cerr); + } + + ModuleWriter writer; + // writer.setDebug(options.debug); + writer.setDebugInfo(true); + // writer.setDebugInfo(options.passOptions.debugInfo); + // writer.setSymbolMap(symbolMap); + writer.setBinary(emitBinary); + // if (emitBinary) { + // writer.setSourceMapFilename(sourceMapFilename); + // writer.setSourceMapUrl(sourceMapUrl); + // } + writer.write(wasm, outfile); + + return 0; +} diff --git a/src/tools/wasm-link-metadata.cpp b/src/tools/wasm-link-metadata.cpp new file mode 100644 index 000000000..1ca64880f --- /dev/null +++ b/src/tools/wasm-link-metadata.cpp @@ -0,0 +1,118 @@ +/* + * 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-link-metadata console tool +// Reads wasm .o file and emits .json metadata +// + +#include <exception> + +#include "abi/wasm-object.h" +#include "ir/trapping.h" +#include "support/colors.h" +#include "support/command-line.h" +#include "support/file.h" +#include "wasm-binary.h" +#include "wasm-emscripten.h" +#include "wasm-io.h" +#include "wasm-linker.h" +#include "wasm-printing.h" +#include "wasm-validator.h" + +using namespace cashew; +using namespace wasm; + +void parseLinkingSection(std::vector<char> const& data, uint32_t &dataSize) { + unsigned idx = 0; + auto get = [&idx, &data](){ return data[idx++]; }; + auto readNext = [get](){ + U32LEB leb; + leb.read(get); + return leb.value; + }; + + while (idx < data.size()) { + ABI::LinkType type = static_cast<ABI::LinkType>(readNext()); + uint32_t size = readNext(); + uint32_t startIdx = idx; + + switch(type) { + case ABI::WASM_DATA_SIZE: { + dataSize = readNext(); + break; + } + default: { + break; + } + } + // Always go to the end of the subsection based on size, not contents. + idx = startIdx + size; + } +} + +int main(int argc, const char *argv[]) { + std::string infile; + std::string outfile; + Options options("wasm-link-metadata", + "Reads wasm .o file and emits .json metadata"); + options + .add("--output", "-o", "Output file", + Options::Arguments::One, + [&outfile](Options *o, const std::string &argument) { + outfile = argument; + Colors::disable(); + }) + .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"; + } + + Module wasm; + try { + ModuleReader reader; + reader.readBinary(infile, wasm); + } catch (ParseException& p) { + p.dump(std::cerr); + Fatal() << "error in parsing wasm binary"; + } + + if (options.debug) { + WasmPrinter::printModule(&wasm, std::cerr); + } + + uint32_t dataSize = 0; + for (auto §ion : wasm.userSections) { + if (section.name == "linking") { + parseLinkingSection(section.data, dataSize); + } + } + + std::vector<Name> initializerFunctions; + initializerFunctions.push_back("__wasm_call_ctors"); + + EmscriptenGlueGenerator generator(wasm); + std::string metadata = generator.generateEmscriptenMetadata(dataSize, initializerFunctions); + Output output(outfile, Flags::Text, Flags::Release); + output << metadata; + + return 0; +} diff --git a/src/wasm-binary.h b/src/wasm-binary.h index e5bfd9f2d..7e932c305 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -724,6 +724,7 @@ public: void writeNames(); void writeSourceMapUrl(); void writeSymbolMap(); + void writeUserSections(); void writeSourceMapProlog(); void writeSourceMapEpilog(); @@ -814,7 +815,14 @@ class WasmBinaryBuilder { std::set<BinaryConsts::Section> seenSections; public: - WasmBinaryBuilder(Module& wasm, const std::vector<char>& input, bool debug) : wasm(wasm), allocator(wasm.allocator), input(input), debug(debug), sourceMap(nullptr), nextDebugLocation(0, { 0, 0, 0 }), useDebugLocation(false) {} + WasmBinaryBuilder(Module& wasm, const std::vector<char>& input, bool debug) + : wasm(wasm), + allocator(wasm.allocator), + input(input), + debug(debug), + sourceMap(nullptr), + nextDebugLocation(0, { 0, 0, 0 }), + useDebugLocation(false) {} void read(); void readUserSection(size_t payloadLen); diff --git a/src/wasm-emscripten.cpp b/src/wasm-emscripten.cpp index a07254061..60bf5cce3 100644 --- a/src/wasm-emscripten.cpp +++ b/src/wasm-emscripten.cpp @@ -40,28 +40,39 @@ void addExportedFunction(Module& wasm, Function* function) { wasm.addExport(export_); } -Load* EmscriptenGlueGenerator::generateLoadStackPointer() { - Load* load = builder.makeLoad( - /* bytes =*/ 4, - /* signed =*/ false, - /* offset =*/ stackPointerOffset, - /* align =*/ 4, - /* ptr =*/ builder.makeConst(Literal(0)), - /* type =*/ i32 - ); - return load; +Global* EmscriptenGlueGenerator::getStackPointerGlobal() { + // Assumption: first global is __stack_pointer + return wasm.globals[0].get(); } -Store* EmscriptenGlueGenerator::generateStoreStackPointer(Expression* value) { - Store* store = builder.makeStore( - /* bytes =*/ 4, - /* offset =*/ stackPointerOffset, - /* align =*/ 4, - /* ptr =*/ builder.makeConst(Literal(0)), - /* value =*/ value, - /* type =*/ i32 - ); - return store; +Expression* EmscriptenGlueGenerator::generateLoadStackPointer() { + if (!useStackPointerGlobal) { + return builder.makeLoad( + /* bytes =*/ 4, + /* signed =*/ false, + /* offset =*/ stackPointerOffset, + /* align =*/ 4, + /* ptr =*/ builder.makeConst(Literal(0)), + /* type =*/ i32 + ); + } + Global* stackPointer = getStackPointerGlobal(); + return builder.makeGetGlobal(stackPointer->name, i32); +} + +Expression* EmscriptenGlueGenerator::generateStoreStackPointer(Expression* value) { + if (!useStackPointerGlobal) { + return builder.makeStore( + /* bytes =*/ 4, + /* offset =*/ stackPointerOffset, + /* align =*/ 4, + /* ptr =*/ builder.makeConst(Literal(0)), + /* value =*/ value, + /* type =*/ i32 + ); + } + Global* stackPointer = getStackPointerGlobal(); + return builder.makeSetGlobal(stackPointer->name, value); } void EmscriptenGlueGenerator::generateStackSaveFunction() { @@ -82,7 +93,7 @@ void EmscriptenGlueGenerator::generateStackAllocFunction() { Function* function = builder.makeFunction( name, std::move(params), i32, { { "1", i32 } } ); - Load* loadStack = generateLoadStackPointer(); + Expression* loadStack = generateLoadStackPointer(); GetLocal* getSizeArg = builder.makeGetLocal(0, i32); Binary* sub = builder.makeBinary(SubInt32, loadStack, getSizeArg); const static uint32_t bitAlignment = 16; @@ -90,7 +101,7 @@ void EmscriptenGlueGenerator::generateStackAllocFunction() { Const* subConst = builder.makeConst(Literal(~bitMask)); Binary* maskedSub = builder.makeBinary(AndInt32, sub, subConst); SetLocal* teeStackLocal = builder.makeTeeLocal(1, maskedSub); - Store* storeStack = generateStoreStackPointer(teeStackLocal); + Expression* storeStack = generateStoreStackPointer(teeStackLocal); Block* block = builder.makeBlock(); block->list.push_back(storeStack); @@ -109,7 +120,7 @@ void EmscriptenGlueGenerator::generateStackRestoreFunction() { name, std::move(params), none, {} ); GetLocal* getArg = builder.makeGetLocal(0, i32); - Store* store = generateStoreStackPointer(getArg); + Expression* store = generateStoreStackPointer(getArg); function->body = store; @@ -147,21 +158,7 @@ static bool hasI64ResultOrParam(FunctionType* ft) { return false; } -void removeImportsWithSubstring(Module& module, Name name) { - std::vector<Name> toRemove; - for (auto& import : module.imports) { - if (import->name.hasSubstring(name)) { - toRemove.push_back(import->name); - } - } - for (auto importName : toRemove) { - module.removeImport(importName); - } -} - void EmscriptenGlueGenerator::generateDynCallThunks() { - removeImportsWithSubstring(wasm, EMSCRIPTEN_ASM_CONST); // we create _sig versions - std::unordered_set<std::string> sigs; Builder builder(wasm); std::vector<Name> tableSegmentData; @@ -172,7 +169,12 @@ void EmscriptenGlueGenerator::generateDynCallThunks() { if (indirectFunc == dummyFunction) { continue; } - std::string sig(getSig(wasm.getFunction(indirectFunc))); + std::string sig; + if (auto import = wasm.getImportOrNull(indirectFunc)) { + sig = getSig(wasm.getFunctionType(import->functionType)); + } else { + sig = getSig(wasm.getFunction(indirectFunc)); + } auto* funcType = ensureFunctionType(sig, &wasm); if (hasI64ResultOrParam(funcType)) continue; // Can't export i64s on the web. if (!sigs.insert(sig).second) continue; // Sig is already in the set @@ -196,7 +198,7 @@ void EmscriptenGlueGenerator::generateDynCallThunks() { struct AsmConstWalker : public PostWalker<AsmConstWalker> { Module& wasm; - std::unordered_map<Address, Address> segmentsByAddress; // address => segment index + std::vector<Address> segmentOffsets; // segment index => address offset std::map<std::string, std::set<std::string>> sigsForCode; std::map<std::string, Address> ids; @@ -206,7 +208,7 @@ struct AsmConstWalker : public PostWalker<AsmConstWalker> { for (unsigned i = 0; i < wasm.memory.segments.size(); ++i) { Const* addrConst = wasm.memory.segments[i].offset->cast<Const>(); auto address = addrConst->value.geti32(); - segmentsByAddress[address] = Address(i); + segmentOffsets.push_back(address); } } @@ -214,6 +216,7 @@ struct AsmConstWalker : public PostWalker<AsmConstWalker> { private: std::string codeForConstAddr(Const* addrConst); + const char* stringAtAddr(Address adddress); Literal idLiteralForCode(std::string code); std::string asmConstSig(std::string baseSig); Name nameForImportWithSig(std::string sig); @@ -222,7 +225,8 @@ private: }; void AsmConstWalker::visitCallImport(CallImport* curr) { - if (curr->target.hasSubstring(EMSCRIPTEN_ASM_CONST)) { + Import* import = wasm.getImport(curr->target); + if (import->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) { auto arg = curr->operands[0]->cast<Const>(); auto code = codeForConstAddr(arg); arg->value = idLiteralForCode(code); @@ -241,13 +245,25 @@ void AsmConstWalker::visitCallImport(CallImport* curr) { std::string AsmConstWalker::codeForConstAddr(Const* addrConst) { auto address = addrConst->value.geti32(); - auto segmentIterator = segmentsByAddress.find(address); - if (segmentIterator == segmentsByAddress.end()) { - // If we can't find the segment corresponding with the address, then we omitted the segment and the address points to an empty string. + const char* str = stringAtAddr(address); + if (!str) { + // If we can't find the segment corresponding with the address, then we + // omitted the segment and the address points to an empty string. return escape(""); } - Address segmentIndex = segmentsByAddress[address]; - return escape(&wasm.memory.segments[segmentIndex].data[0]); + auto result = escape(str); + return result; +} + +const char* AsmConstWalker::stringAtAddr(Address address) { + for (unsigned i = 0; i < wasm.memory.segments.size(); ++i) { + Memory::Segment &segment = wasm.memory.segments[i]; + Address offset = segmentOffsets[i]; + if (address >= offset && address < offset + segment.data.size()) { + return &segment.data[address - offset]; + } + } + return nullptr; } std::string AsmConstWalker::escape(const char *input) { @@ -308,6 +324,31 @@ void AsmConstWalker::addImport(Name importName, std::string baseSig) { wasm.addImport(import); } +AsmConstWalker fixEmAsmConstsAndReturnWalker(Module& wasm) { + // Collect imports to remove + // This would find our generated functions if we ran it later + std::vector<Name> toRemove; + for (auto& import : wasm.imports) { + if (import->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) { + toRemove.push_back(import->name); + } + } + + // Walk the module, generate _sig versions of EM_ASM functions + AsmConstWalker walker(wasm); + walker.walkModule(&wasm); + + // Remove the base functions that we didn't generate + for (auto importName : toRemove) { + wasm.removeImport(importName); + } + return walker; +} + +void EmscriptenGlueGenerator::fixEmAsmConsts() { + fixEmAsmConstsAndReturnWalker(wasm); +} + template<class C> void printSet(std::ostream& o, C& c) { o << "["; @@ -324,11 +365,9 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( Address staticBump, std::vector<Name> const& initializerFunctions) { std::stringstream meta; - meta << ";; METADATA: { "; + meta << "{ "; - // find asmConst calls, and emit their metadata - AsmConstWalker walker(wasm); - walker.walkModule(&wasm); + AsmConstWalker walker = fixEmAsmConstsAndReturnWalker(wasm); // print meta << "\"asmConsts\": {"; diff --git a/src/wasm-emscripten.h b/src/wasm-emscripten.h index 0a0f88574..1878e6531 100644 --- a/src/wasm-emscripten.h +++ b/src/wasm-emscripten.h @@ -29,7 +29,8 @@ public: EmscriptenGlueGenerator(Module& wasm, Address stackPointerOffset = Address(0)) : wasm(wasm), builder(wasm), - stackPointerOffset(stackPointerOffset) { } + stackPointerOffset(stackPointerOffset), + useStackPointerGlobal(stackPointerOffset == 0) { } void generateRuntimeFunctions(); Function* generateMemoryGrowthFunction(); @@ -41,13 +42,18 @@ public: std::string generateEmscriptenMetadata( Address staticBump, std::vector<Name> const& initializerFunctions); + // Replace placeholder emscripten_asm_const functions with *_signature versions. + void fixEmAsmConsts(); + private: Module& wasm; Builder builder; Address stackPointerOffset; + bool useStackPointerGlobal; - Load* generateLoadStackPointer(); - Store* generateStoreStackPointer(Expression* value); + Global* getStackPointerGlobal(); + Expression* generateLoadStackPointer(); + Expression* generateStoreStackPointer(Expression* value); void generateStackSaveFunction(); void generateStackAllocFunction(); void generateStackRestoreFunction(); diff --git a/src/wasm-io.h b/src/wasm-io.h index afdc4503c..17a722472 100644 --- a/src/wasm-io.h +++ b/src/wasm-io.h @@ -42,6 +42,9 @@ public: void readBinary(std::string filename, Module& wasm); // read text or binary, checking the contents for what it is void read(std::string filename, Module& wasm); + + // check whether a file is a wasm binary + bool isBinaryFile(std::string filename); }; class ModuleWriter : public ModuleIO { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 514b489a9..44ce9a2d1 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -61,6 +61,9 @@ void WasmBinaryWriter::write() { if (sourceMap) { writeSourceMapEpilog(); } + + writeUserSections(); + finishUp(); } @@ -551,6 +554,17 @@ static void writeBase64VLQ(std::ostream& out, int32_t n) { } } +void WasmBinaryWriter::writeUserSections() { + for (auto& section : wasm->userSections) { + auto start = startSection(0); + writeInlineString(section.name.c_str()); + for (size_t i = 0; i < section.data.size(); i++) { + o << uint8_t(section.data[i]); + } + finishSection(start); + } +} + void WasmBinaryWriter::writeDebugLocation(size_t offset, const Function::DebugLocation& loc) { if (lastBytecodeOffset > 0) { *sourceMap << ","; @@ -1626,9 +1640,9 @@ void WasmBinaryBuilder::readImports() { case ExternalKind::Global: { curr->globalType = getWasmType(); auto globalMutable = getU32LEB(); - if (globalMutable) { - throw ParseException("imported globals cannot be mutable"); - } + // TODO: actually use the globalMutable flag. Currently mutable global + // imports is a future feature, to be implemented with thread support. + (void)globalMutable; break; } default: { diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp index ebc9af8de..1f50d7140 100644 --- a/src/wasm/wasm-io.cpp +++ b/src/wasm/wasm-io.cpp @@ -46,15 +46,18 @@ void ModuleReader::readBinary(std::string filename, Module& wasm) { parser.read(); } -void ModuleReader::read(std::string filename, Module& wasm) { - // see if this is a wasm binary +bool ModuleReader::isBinaryFile(std::string filename) { std::ifstream infile; std::ios_base::openmode flags = std::ifstream::in | std::ifstream::binary; infile.open(filename, flags); char buffer[4] = { 1, 2, 3, 4 }; infile.read(buffer, 4); infile.close(); - if (buffer[0] == '\0' && buffer[1] == 'a' && buffer[2] == 's' && buffer[3] == 'm') { + return buffer[0] == '\0' && buffer[1] == 'a' && buffer[2] == 's' && buffer[3] == 'm'; +} + +void ModuleReader::read(std::string filename, Module& wasm) { + if (isBinaryFile(filename)) { readBinary(filename, wasm); } else { // default to text diff --git a/test/lld/em_asm.cpp b/test/lld/em_asm.cpp new file mode 100644 index 000000000..ed89783f8 --- /dev/null +++ b/test/lld/em_asm.cpp @@ -0,0 +1,8 @@ +#include <emscripten/em_asm.h> + +int main() { + EM_ASM({ Module.print("Hello world"); }); + int x = EM_ASM_INT({ return $0 + $1; }, 13, 27); + EM_ASM_({ Module.print("Got " + $0); }, x); + return 0; +} diff --git a/test/lld/em_asm.json b/test/lld/em_asm.json new file mode 100644 index 000000000..f5027845d --- /dev/null +++ b/test/lld/em_asm.json @@ -0,0 +1 @@ +{ "asmConsts": {"2": ["{ Module.print(\"Got \" + $0); }", ["ii"]],"0": ["{ Module.print(\"Hello world\"); }", ["i"]],"1": ["{ return $0 + $1; }", ["iii"]]},"staticBump": 84, "initializers": ["__wasm_call_ctors"] } diff --git a/test/lld/em_asm.o b/test/lld/em_asm.o Binary files differnew file mode 100644 index 000000000..f95befdee --- /dev/null +++ b/test/lld/em_asm.o diff --git a/test/lld/em_asm.wast b/test/lld/em_asm.wast new file mode 100644 index 000000000..da1d8c3b6 --- /dev/null +++ b/test/lld/em_asm.wast @@ -0,0 +1,41 @@ +(module + (type $0 (func (result i32))) + (type $1 (func (param i32) (result i32))) + (type $2 (func (param i32 i32 i32) (result i32))) + (type $3 (func (param i32 i32) (result i32))) + (type $4 (func)) + (import "env" "_Z24emscripten_asm_const_intIJEEiPKcDpT_" (func $_Z24emscripten_asm_const_intIJEEiPKcDpT_ (param i32) (result i32))) + (import "env" "_Z24emscripten_asm_const_intIJiiEEiPKcDpT_" (func $_Z24emscripten_asm_const_intIJiiEEiPKcDpT_ (param i32 i32 i32) (result i32))) + (import "env" "_Z24emscripten_asm_const_intIJiEEiPKcDpT_" (func $_Z24emscripten_asm_const_intIJiEEiPKcDpT_ (param i32 i32) (result i32))) + (global $global$0 (mut i32) (i32.const 66656)) + (global $global$1 i32 (i32.const 66656)) + (table 1 1 anyfunc) + (memory $0 2) + (data (i32.const 1024) "{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00") + (export "memory" (memory $0)) + (export "main" (func $main)) + (export "__wasm_call_ctors" (func $__wasm_call_ctors)) + (export "__heap_base" (global $global$1)) + (func $main (; 3 ;) (type $0) (result i32) + (drop + (call $_Z24emscripten_asm_const_intIJEEiPKcDpT_ + (i32.const 1024) + ) + ) + (drop + (call $_Z24emscripten_asm_const_intIJiEEiPKcDpT_ + (i32.const 1077) + (call $_Z24emscripten_asm_const_intIJiiEEiPKcDpT_ + (i32.const 1057) + (i32.const 13) + (i32.const 27) + ) + ) + ) + (i32.const 0) + ) + (func $__wasm_call_ctors (; 4 ;) (type $4) + ) + ;; custom section "linking", size 3 +) + diff --git a/test/lld/em_asm.wast.out b/test/lld/em_asm.wast.out new file mode 100644 index 000000000..921cd9782 --- /dev/null +++ b/test/lld/em_asm.wast.out @@ -0,0 +1,75 @@ +(module + (type $0 (func (result i32))) + (type $1 (func (param i32) (result i32))) + (type $2 (func (param i32 i32 i32) (result i32))) + (type $3 (func (param i32 i32) (result i32))) + (type $4 (func)) + (type $FUNCSIG$ii (func (param i32) (result i32))) + (type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32))) + (type $FUNCSIG$iii (func (param i32 i32) (result i32))) + (import "env" "emscripten_asm_const_i" (func $emscripten_asm_const_i (param i32) (result i32))) + (import "env" "emscripten_asm_const_iii" (func $emscripten_asm_const_iii (param i32 i32 i32) (result i32))) + (import "env" "emscripten_asm_const_ii" (func $emscripten_asm_const_ii (param i32 i32) (result i32))) + (global $global$0 (mut i32) (i32.const 66656)) + (global $global$1 i32 (i32.const 66656)) + (table 1 1 anyfunc) + (memory $0 2) + (data (i32.const 1024) "{ Module.print(\"Hello world\"); }\00{ return $0 + $1; }\00{ Module.print(\"Got \" + $0); }\00") + (export "memory" (memory $0)) + (export "main" (func $main)) + (export "__wasm_call_ctors" (func $__wasm_call_ctors)) + (export "__heap_base" (global $global$1)) + (export "stackSave" (func $stackSave)) + (export "stackAlloc" (func $stackAlloc)) + (export "stackRestore" (func $stackRestore)) + (export "__growWasmMemory" (func $__growWasmMemory)) + (func $main (; 3 ;) (type $0) (result i32) + (drop + (call $emscripten_asm_const_i + (i32.const 0) + ) + ) + (drop + (call $emscripten_asm_const_ii + (i32.const 2) + (call $emscripten_asm_const_iii + (i32.const 1) + (i32.const 13) + (i32.const 27) + ) + ) + ) + (i32.const 0) + ) + (func $__wasm_call_ctors (; 4 ;) (type $4) + (nop) + ) + (func $stackSave (; 5 ;) (result i32) + (get_global $global$0) + ) + (func $stackAlloc (; 6 ;) (param $0 i32) (result i32) + (local $1 i32) + (set_global $global$0 + (tee_local $1 + (i32.and + (i32.sub + (get_global $global$0) + (get_local $0) + ) + (i32.const -16) + ) + ) + ) + (get_local $1) + ) + (func $stackRestore (; 7 ;) (param $0 i32) + (set_global $global$0 + (get_local $0) + ) + ) + (func $__growWasmMemory (; 8 ;) (param $newSize i32) (result i32) + (grow_memory + (get_local $newSize) + ) + ) +) diff --git a/test/lld/hello_world.c b/test/lld/hello_world.c new file mode 100644 index 000000000..babd8d43c --- /dev/null +++ b/test/lld/hello_world.c @@ -0,0 +1,6 @@ +int puts(const char* str); + +int main() { + puts("Hello, world"); + return 0; +} diff --git a/test/lld/hello_world.json b/test/lld/hello_world.json new file mode 100644 index 000000000..c0a391b6e --- /dev/null +++ b/test/lld/hello_world.json @@ -0,0 +1 @@ +{ "asmConsts": {},"staticBump": 13, "initializers": ["__wasm_call_ctors"] } diff --git a/test/lld/hello_world.o b/test/lld/hello_world.o Binary files differnew file mode 100644 index 000000000..a14f95993 --- /dev/null +++ b/test/lld/hello_world.o diff --git a/test/lld/hello_world.wast b/test/lld/hello_world.wast new file mode 100644 index 000000000..d1610cece --- /dev/null +++ b/test/lld/hello_world.wast @@ -0,0 +1,27 @@ +(module + (type $0 (func (result i32))) + (type $1 (func (param i32) (result i32))) + (type $2 (func)) + (import "env" "puts" (func $puts (param i32) (result i32))) + (global $global$0 (mut i32) (i32.const 66576)) + (global $global$1 i32 (i32.const 66576)) + (table 1 1 anyfunc) + (memory $0 2) + (data (i32.const 1024) "Hello, world\00") + (export "memory" (memory $0)) + (export "main" (func $main)) + (export "__wasm_call_ctors" (func $__wasm_call_ctors)) + (export "__heap_base" (global $global$1)) + (func $main (; 1 ;) (type $0) (result i32) + (drop + (call $puts + (i32.const 1024) + ) + ) + (i32.const 0) + ) + (func $__wasm_call_ctors (; 2 ;) (type $2) + ) + ;; custom section "linking", size 3 +) + diff --git a/test/lld/hello_world.wast.out b/test/lld/hello_world.wast.out new file mode 100644 index 000000000..ab861cc94 --- /dev/null +++ b/test/lld/hello_world.wast.out @@ -0,0 +1,59 @@ +(module + (type $0 (func (result i32))) + (type $1 (func (param i32) (result i32))) + (type $2 (func)) + (type $FUNCSIG$ii (func (param i32) (result i32))) + (import "env" "puts" (func $puts (param i32) (result i32))) + (global $global$0 (mut i32) (i32.const 66576)) + (global $global$1 i32 (i32.const 66576)) + (table 1 1 anyfunc) + (memory $0 2) + (data (i32.const 1024) "Hello, world\00") + (export "memory" (memory $0)) + (export "main" (func $main)) + (export "__wasm_call_ctors" (func $__wasm_call_ctors)) + (export "__heap_base" (global $global$1)) + (export "stackSave" (func $stackSave)) + (export "stackAlloc" (func $stackAlloc)) + (export "stackRestore" (func $stackRestore)) + (export "__growWasmMemory" (func $__growWasmMemory)) + (func $main (; 1 ;) (type $0) (result i32) + (drop + (call $puts + (i32.const 1024) + ) + ) + (i32.const 0) + ) + (func $__wasm_call_ctors (; 2 ;) (type $2) + (nop) + ) + (func $stackSave (; 3 ;) (result i32) + (get_global $global$0) + ) + (func $stackAlloc (; 4 ;) (param $0 i32) (result i32) + (local $1 i32) + (set_global $global$0 + (tee_local $1 + (i32.and + (i32.sub + (get_global $global$0) + (get_local $0) + ) + (i32.const -16) + ) + ) + ) + (get_local $1) + ) + (func $stackRestore (; 5 ;) (param $0 i32) + (set_global $global$0 + (get_local $0) + ) + ) + (func $__growWasmMemory (; 6 ;) (param $newSize i32) (result i32) + (grow_memory + (get_local $newSize) + ) + ) +) diff --git a/test/lld/init.c b/test/lld/init.c new file mode 100644 index 000000000..3a9311e3c --- /dev/null +++ b/test/lld/init.c @@ -0,0 +1,15 @@ +volatile int x, y; + +__attribute__((constructor)) +void init_x() { + x = 14; +} + +__attribute__((constructor)) +void init_y() { + y = 144; +} + +int main() { + return x + y; +} diff --git a/test/lld/init.json b/test/lld/init.json new file mode 100644 index 000000000..f4d13ada1 --- /dev/null +++ b/test/lld/init.json @@ -0,0 +1 @@ +{ "asmConsts": {},"staticBump": 8, "initializers": ["__wasm_call_ctors"] } diff --git a/test/lld/init.o b/test/lld/init.o Binary files differnew file mode 100644 index 000000000..e86c68092 --- /dev/null +++ b/test/lld/init.o diff --git a/test/lld/init.wast b/test/lld/init.wast new file mode 100644 index 000000000..be25ed464 --- /dev/null +++ b/test/lld/init.wast @@ -0,0 +1,41 @@ +(module + (type $0 (func)) + (type $1 (func (result i32))) + (global $global$0 (mut i32) (i32.const 66576)) + (global $global$1 i32 (i32.const 66576)) + (table 1 1 anyfunc) + (memory $0 2) + (data (i32.const 1024) "\00\00\00\00\00\00\00\00") + (export "memory" (memory $0)) + (export "main" (func $main)) + (export "__wasm_call_ctors" (func $__wasm_call_ctors)) + (export "__heap_base" (global $global$1)) + (func $init_x (; 0 ;) (type $0) + (i32.store offset=1024 + (i32.const 0) + (i32.const 14) + ) + ) + (func $init_y (; 1 ;) (type $0) + (i32.store offset=1028 + (i32.const 0) + (i32.const 144) + ) + ) + (func $main (; 2 ;) (type $1) (result i32) + (i32.add + (i32.load offset=1024 + (i32.const 0) + ) + (i32.load offset=1028 + (i32.const 0) + ) + ) + ) + (func $__wasm_call_ctors (; 3 ;) (type $0) + (call $init_x) + (call $init_y) + ) + ;; custom section "linking", size 3 +) + diff --git a/test/lld/init.wast.out b/test/lld/init.wast.out new file mode 100644 index 000000000..3b46d26d4 --- /dev/null +++ b/test/lld/init.wast.out @@ -0,0 +1,71 @@ +(module + (type $0 (func)) + (type $1 (func (result i32))) + (global $global$0 (mut i32) (i32.const 66576)) + (global $global$1 i32 (i32.const 66576)) + (table 1 1 anyfunc) + (memory $0 2) + (data (i32.const 1024) "\00\00\00\00\00\00\00\00") + (export "memory" (memory $0)) + (export "main" (func $main)) + (export "__wasm_call_ctors" (func $__wasm_call_ctors)) + (export "__heap_base" (global $global$1)) + (export "stackSave" (func $stackSave)) + (export "stackAlloc" (func $stackAlloc)) + (export "stackRestore" (func $stackRestore)) + (export "__growWasmMemory" (func $__growWasmMemory)) + (func $init_x (; 0 ;) (type $0) + (i32.store offset=1024 + (i32.const 0) + (i32.const 14) + ) + ) + (func $init_y (; 1 ;) (type $0) + (i32.store offset=1028 + (i32.const 0) + (i32.const 144) + ) + ) + (func $main (; 2 ;) (type $1) (result i32) + (i32.add + (i32.load offset=1024 + (i32.const 0) + ) + (i32.load offset=1028 + (i32.const 0) + ) + ) + ) + (func $__wasm_call_ctors (; 3 ;) (type $0) + (call $init_x) + (call $init_y) + ) + (func $stackSave (; 4 ;) (result i32) + (get_global $global$0) + ) + (func $stackAlloc (; 5 ;) (param $0 i32) (result i32) + (local $1 i32) + (set_global $global$0 + (tee_local $1 + (i32.and + (i32.sub + (get_global $global$0) + (get_local $0) + ) + (i32.const -16) + ) + ) + ) + (get_local $1) + ) + (func $stackRestore (; 6 ;) (param $0 i32) + (set_global $global$0 + (get_local $0) + ) + ) + (func $__growWasmMemory (; 7 ;) (param $newSize i32) (result i32) + (grow_memory + (get_local $newSize) + ) + ) +) diff --git a/test/lld/recursive.c b/test/lld/recursive.c new file mode 100644 index 000000000..c0cdaa425 --- /dev/null +++ b/test/lld/recursive.c @@ -0,0 +1,15 @@ +// This test emits a stack pointer, which tests global importing in object files +// (which are mutable and not normally allowed). + +int printf(const char* fmt, ...); + +__attribute__((noinline)) +int foo(int a, int b) { + printf("%d:%d\n", a, b); + return a + b; +} + +int main() { + printf("Result: %d\n", foo(1, 2)); + return 0; +} diff --git a/test/lld/recursive.json b/test/lld/recursive.json new file mode 100644 index 000000000..a976be232 --- /dev/null +++ b/test/lld/recursive.json @@ -0,0 +1 @@ +{ "asmConsts": {},"staticBump": 19, "initializers": ["__wasm_call_ctors"] } diff --git a/test/lld/recursive.o b/test/lld/recursive.o Binary files differnew file mode 100644 index 000000000..409257fd4 --- /dev/null +++ b/test/lld/recursive.o diff --git a/test/lld/recursive.wast b/test/lld/recursive.wast new file mode 100644 index 000000000..21b46a31f --- /dev/null +++ b/test/lld/recursive.wast @@ -0,0 +1,85 @@ +(module + (type $0 (func (param i32 i32) (result i32))) + (type $1 (func (result i32))) + (type $2 (func)) + (import "env" "printf" (func $printf (param i32 i32) (result i32))) + (global $global$0 (mut i32) (i32.const 66592)) + (global $global$1 i32 (i32.const 66592)) + (table 1 1 anyfunc) + (memory $0 2) + (data (i32.const 1024) "%d:%d\n\00Result: %d\n\00") + (export "memory" (memory $0)) + (export "main" (func $main)) + (export "__wasm_call_ctors" (func $__wasm_call_ctors)) + (export "__heap_base" (global $global$1)) + (func $foo (; 1 ;) (type $0) (param $var$0 i32) (param $var$1 i32) (result i32) + (local $var$2 i32) + (set_global $global$0 + (tee_local $var$2 + (i32.sub + (get_global $global$0) + (i32.const 16) + ) + ) + ) + (i32.store offset=4 + (get_local $var$2) + (get_local $var$1) + ) + (i32.store + (get_local $var$2) + (get_local $var$0) + ) + (drop + (call $printf + (i32.const 1024) + (get_local $var$2) + ) + ) + (set_global $global$0 + (i32.add + (get_local $var$2) + (i32.const 16) + ) + ) + (i32.add + (get_local $var$1) + (get_local $var$0) + ) + ) + (func $main (; 2 ;) (type $1) (result i32) + (local $var$0 i32) + (set_global $global$0 + (tee_local $var$0 + (i32.sub + (get_global $global$0) + (i32.const 16) + ) + ) + ) + (i32.store + (get_local $var$0) + (call $foo + (i32.const 1) + (i32.const 2) + ) + ) + (drop + (call $printf + (i32.const 1031) + (get_local $var$0) + ) + ) + (set_global $global$0 + (i32.add + (get_local $var$0) + (i32.const 16) + ) + ) + (i32.const 0) + ) + (func $__wasm_call_ctors (; 3 ;) (type $2) + ) + ;; custom section "linking", size 3 +) + diff --git a/test/lld/recursive.wast.out b/test/lld/recursive.wast.out new file mode 100644 index 000000000..4920a7622 --- /dev/null +++ b/test/lld/recursive.wast.out @@ -0,0 +1,117 @@ +(module + (type $0 (func (param i32 i32) (result i32))) + (type $1 (func (result i32))) + (type $2 (func)) + (type $FUNCSIG$iii (func (param i32 i32) (result i32))) + (import "env" "printf" (func $printf (param i32 i32) (result i32))) + (global $global$0 (mut i32) (i32.const 66592)) + (global $global$1 i32 (i32.const 66592)) + (table 1 1 anyfunc) + (memory $0 2) + (data (i32.const 1024) "%d:%d\n\00Result: %d\n\00") + (export "memory" (memory $0)) + (export "main" (func $main)) + (export "__wasm_call_ctors" (func $__wasm_call_ctors)) + (export "__heap_base" (global $global$1)) + (export "stackSave" (func $stackSave)) + (export "stackAlloc" (func $stackAlloc)) + (export "stackRestore" (func $stackRestore)) + (export "__growWasmMemory" (func $__growWasmMemory)) + (func $foo (; 1 ;) (type $0) (param $var$0 i32) (param $var$1 i32) (result i32) + (local $var$2 i32) + (set_global $global$0 + (tee_local $var$2 + (i32.sub + (get_global $global$0) + (i32.const 16) + ) + ) + ) + (i32.store offset=4 + (get_local $var$2) + (get_local $var$1) + ) + (i32.store + (get_local $var$2) + (get_local $var$0) + ) + (drop + (call $printf + (i32.const 1024) + (get_local $var$2) + ) + ) + (set_global $global$0 + (i32.add + (get_local $var$2) + (i32.const 16) + ) + ) + (i32.add + (get_local $var$1) + (get_local $var$0) + ) + ) + (func $main (; 2 ;) (type $1) (result i32) + (local $var$0 i32) + (set_global $global$0 + (tee_local $var$0 + (i32.sub + (get_global $global$0) + (i32.const 16) + ) + ) + ) + (i32.store + (get_local $var$0) + (call $foo + (i32.const 1) + (i32.const 2) + ) + ) + (drop + (call $printf + (i32.const 1031) + (get_local $var$0) + ) + ) + (set_global $global$0 + (i32.add + (get_local $var$0) + (i32.const 16) + ) + ) + (i32.const 0) + ) + (func $__wasm_call_ctors (; 3 ;) (type $2) + (nop) + ) + (func $stackSave (; 4 ;) (result i32) + (get_global $global$0) + ) + (func $stackAlloc (; 5 ;) (param $0 i32) (result i32) + (local $1 i32) + (set_global $global$0 + (tee_local $1 + (i32.and + (i32.sub + (get_global $global$0) + (get_local $0) + ) + (i32.const -16) + ) + ) + ) + (get_local $1) + ) + (func $stackRestore (; 6 ;) (param $0 i32) + (set_global $global$0 + (get_local $0) + ) + ) + (func $__growWasmMemory (; 7 ;) (param $newSize i32) (result i32) + (grow_memory + (get_local $newSize) + ) + ) +) |