From 19500d54b57aeab4feaac2f9e813cf4d3073f707 Mon Sep 17 00:00:00 2001 From: dcode Date: Mon, 4 Feb 2019 12:23:12 +0100 Subject: [PATCH 1/6] Refactor core inlining logic into src/ir/inlining.h --- src/ir/inlining.h | 90 +++++++++++++++++++++++++++++++++++++++++ src/passes/Inlining.cpp | 63 +---------------------------- 2 files changed, 91 insertions(+), 62 deletions(-) create mode 100644 src/ir/inlining.h diff --git a/src/ir/inlining.h b/src/ir/inlining.h new file mode 100644 index 00000000000..40c76a71951 --- /dev/null +++ b/src/ir/inlining.h @@ -0,0 +1,90 @@ +/* + * Copyright 2019 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. + */ + +#ifndef wasm_ir_inlining_h +#define wasm_ir_inlining_h + +#include "wasm.h" +#include "wasm-builder.h" +#include "ir/literal-utils.h" + +namespace wasm { + +struct InliningAction { + Expression** callSite; + Function* contents; + + InliningAction(Expression** callSite, Function* contents) : callSite(callSite), contents(contents) {} +}; + +// Core inlining logic. Modifies the outside function (adding locals as +// needed), and returns the inlined code. +static Expression* doInlining(Module* module, Function* into, InliningAction& action) { + Function* from = action.contents; + auto* call = (*action.callSite)->cast(); + Builder builder(*module); + auto* block = Builder(*module).makeBlock(); + block->name = Name(std::string("__inlined_func$") + from->name.str); + *action.callSite = block; + // set up a locals mapping + struct Updater : public PostWalker { + std::map localMapping; + Name returnName; + Builder* builder; + + void visitReturn(Return* curr) { + replaceCurrent(builder->makeBreak(returnName, curr->value)); + } + void visitGetLocal(GetLocal* curr) { + curr->index = localMapping[curr->index]; + } + void visitSetLocal(SetLocal* curr) { + curr->index = localMapping[curr->index]; + } + } updater; + updater.returnName = block->name; + updater.builder = &builder; + for (Index i = 0; i < from->getNumLocals(); i++) { + updater.localMapping[i] = builder.addVar(into, from->getLocalType(i)); + } + // assign the operands into the params + for (Index i = 0; i < from->params.size(); i++) { + block->list.push_back(builder.makeSetLocal(updater.localMapping[i], call->operands[i])); + } + // zero out the vars (as we may be in a loop, and may depend on their zero-init value + for (Index i = 0; i < from->vars.size(); i++) { + block->list.push_back(builder.makeSetLocal(updater.localMapping[from->getVarIndexBase() + i], LiteralUtils::makeZero(from->vars[i], *module))); + } + // generate and update the inlined contents + auto* contents = ExpressionManipulator::copy(from->body, *module); + updater.walk(contents); + block->list.push_back(contents); + block->type = call->type; + // if the function returned a value, we just set the block containing the + // inlined code to have that type. or, if the function was void and + // contained void, that is fine too. a bad case is a void function in which + // we have unreachable code, so we would be replacing a void call with an + // unreachable; we need to handle + if (contents->type == unreachable && block->type == none) { + // make the block reachable by adding a break to it + block->list.push_back(builder.makeBreak(block->name)); + } + return block; +} + +} // namespace wasm + +#endif // wasm_ir_inlining_h diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index f801662e0eb..ddb5f44beb5 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -35,6 +35,7 @@ #include "wasm.h" #include "pass.h" #include "wasm-builder.h" +#include "ir/inlining.h" #include "ir/literal-utils.h" #include "ir/module-utils.h" #include "ir/utils.h" @@ -126,13 +127,6 @@ struct FunctionInfoScanner : public WalkerPass> NameInfoMap* infos; }; -struct InliningAction { - Expression** callSite; - Function* contents; - - InliningAction(Expression** callSite, Function* contents) : callSite(callSite), contents(contents) {} -}; - struct InliningState { std::unordered_set worthInlining; std::unordered_map> actionsForFunction; // function name => actions that can be performed in it @@ -172,61 +166,6 @@ struct Planner : public WalkerPass> { InliningState* state; }; -// Core inlining logic. Modifies the outside function (adding locals as -// needed), and returns the inlined code. -static Expression* doInlining(Module* module, Function* into, InliningAction& action) { - Function* from = action.contents; - auto* call = (*action.callSite)->cast(); - Builder builder(*module); - auto* block = Builder(*module).makeBlock(); - block->name = Name(std::string("__inlined_func$") + from->name.str); - *action.callSite = block; - // set up a locals mapping - struct Updater : public PostWalker { - std::map localMapping; - Name returnName; - Builder* builder; - - void visitReturn(Return* curr) { - replaceCurrent(builder->makeBreak(returnName, curr->value)); - } - void visitGetLocal(GetLocal* curr) { - curr->index = localMapping[curr->index]; - } - void visitSetLocal(SetLocal* curr) { - curr->index = localMapping[curr->index]; - } - } updater; - updater.returnName = block->name; - updater.builder = &builder; - for (Index i = 0; i < from->getNumLocals(); i++) { - updater.localMapping[i] = builder.addVar(into, from->getLocalType(i)); - } - // assign the operands into the params - for (Index i = 0; i < from->params.size(); i++) { - block->list.push_back(builder.makeSetLocal(updater.localMapping[i], call->operands[i])); - } - // zero out the vars (as we may be in a loop, and may depend on their zero-init value - for (Index i = 0; i < from->vars.size(); i++) { - block->list.push_back(builder.makeSetLocal(updater.localMapping[from->getVarIndexBase() + i], LiteralUtils::makeZero(from->vars[i], *module))); - } - // generate and update the inlined contents - auto* contents = ExpressionManipulator::copy(from->body, *module); - updater.walk(contents); - block->list.push_back(contents); - block->type = call->type; - // if the function returned a value, we just set the block containing the - // inlined code to have that type. or, if the function was void and - // contained void, that is fine too. a bad case is a void function in which - // we have unreachable code, so we would be replacing a void call with an - // unreachable; we need to handle - if (contents->type == unreachable && block->type == none) { - // make the block reachable by adding a break to it - block->list.push_back(builder.makeBreak(block->name)); - } - return block; -} - struct Inlining : public Pass { // whether to optimize where we inline bool optimize = false; From 4264c1380d784d05ab16f4c9fc380eb29f7fe017 Mon Sep 17 00:00:00 2001 From: dcode Date: Mon, 4 Feb 2019 12:48:37 +0100 Subject: [PATCH 2/6] Make core inlininig helper more general --- src/ir/inlining.h | 30 ++++++++++-------------------- src/passes/Inlining.cpp | 10 +++++++++- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/ir/inlining.h b/src/ir/inlining.h index 40c76a71951..8cb5cf25a16 100644 --- a/src/ir/inlining.h +++ b/src/ir/inlining.h @@ -23,22 +23,12 @@ namespace wasm { -struct InliningAction { - Expression** callSite; - Function* contents; - - InliningAction(Expression** callSite, Function* contents) : callSite(callSite), contents(contents) {} -}; - -// Core inlining logic. Modifies the outside function (adding locals as -// needed), and returns the inlined code. -static Expression* doInlining(Module* module, Function* into, InliningAction& action) { - Function* from = action.contents; - auto* call = (*action.callSite)->cast(); +// Core inlining logic. Modifies the caller (adding locals as needed), and returns the inlined code. +static Expression* doInlining(Module* module, Function* caller, Call* call) { + auto* callee = module->getFunction(call->target); Builder builder(*module); auto* block = Builder(*module).makeBlock(); - block->name = Name(std::string("__inlined_func$") + from->name.str); - *action.callSite = block; + block->name = Name(std::string("__inlined_func$") + callee->name.str); // set up a locals mapping struct Updater : public PostWalker { std::map localMapping; @@ -57,19 +47,19 @@ static Expression* doInlining(Module* module, Function* into, InliningAction& ac } updater; updater.returnName = block->name; updater.builder = &builder; - for (Index i = 0; i < from->getNumLocals(); i++) { - updater.localMapping[i] = builder.addVar(into, from->getLocalType(i)); + for (Index i = 0; i < callee->getNumLocals(); i++) { + updater.localMapping[i] = builder.addVar(caller, callee->getLocalType(i)); } // assign the operands into the params - for (Index i = 0; i < from->params.size(); i++) { + for (Index i = 0; i < callee->params.size(); i++) { block->list.push_back(builder.makeSetLocal(updater.localMapping[i], call->operands[i])); } // zero out the vars (as we may be in a loop, and may depend on their zero-init value - for (Index i = 0; i < from->vars.size(); i++) { - block->list.push_back(builder.makeSetLocal(updater.localMapping[from->getVarIndexBase() + i], LiteralUtils::makeZero(from->vars[i], *module))); + for (Index i = 0; i < callee->vars.size(); i++) { + block->list.push_back(builder.makeSetLocal(updater.localMapping[callee->getVarIndexBase() + i], LiteralUtils::makeZero(callee->vars[i], *module))); } // generate and update the inlined contents - auto* contents = ExpressionManipulator::copy(from->body, *module); + auto* contents = ExpressionManipulator::copy(callee->body, *module); updater.walk(contents); block->list.push_back(contents); block->type = call->type; diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index ddb5f44beb5..61d4f663840 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -127,6 +127,13 @@ struct FunctionInfoScanner : public WalkerPass> NameInfoMap* infos; }; +struct InliningAction { + Expression** callSite; + Function* contents; + + InliningAction(Expression** callSite, Function* contents) : callSite(callSite), contents(contents) {} +}; + struct InliningState { std::unordered_set worthInlining; std::unordered_map> actionsForFunction; // function name => actions that can be performed in it @@ -259,7 +266,8 @@ struct Inlining : public Pass { #ifdef INLINING_DEBUG std::cout << "inline " << inlinedName << " into " << func->name << '\n'; #endif - doInlining(module, func.get(), action); + *action.callSite = doInlining(module, func.get(), (*action.callSite)->cast()); + inlinedUses[inlinedName]++; inlinedInto.insert(func.get()); assert(inlinedUses[inlinedName] <= infos[inlinedName].calls); From 8a078180db721563a3a9aec4f3a312feddd1021f Mon Sep 17 00:00:00 2001 From: dcode Date: Tue, 5 Feb 2019 12:50:11 +0100 Subject: [PATCH 3/6] Add 'BinaryenForceInline' --- build-js.sh | 3 +++ src/binaryen-c.cpp | 26 ++++++++++++++++++++++++++ src/binaryen-c.h | 3 +++ src/js/binaryen.js-post.js | 3 +++ test/binaryen.js/inlining.js | 24 ++++++++++++++++++++++++ test/binaryen.js/inlining.txt | 0 6 files changed, 59 insertions(+) create mode 100644 test/binaryen.js/inlining.js create mode 100644 test/binaryen.js/inlining.txt diff --git a/build-js.sh b/build-js.sh index 43cfaca7b0f..41c25fe428c 100755 --- a/build-js.sh +++ b/build-js.sh @@ -768,6 +768,9 @@ export_function "_BinaryenExportGetKind" export_function "_BinaryenExportGetName" export_function "_BinaryenExportGetValue" +# Utility +export_function "_BinaryenForceInline" + # 'Relooper' operations export_function "_RelooperCreate" export_function "_RelooperAddBlock" diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 9ed6ad31455..28f772bd93c 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -32,6 +32,7 @@ #include "wasm2js.h" #include "cfg/Relooper.h" #include "ir/function-type-utils.h" +#include "ir/inlining.h" #include "ir/utils.h" #include "shell-interface.h" @@ -2909,6 +2910,31 @@ BinaryenFunctionTypeRef BinaryenGetFunctionTypeBySignature(BinaryenModuleRef mod return NULL; } +int BinaryenForceInline(BinaryenModuleRef module, BinaryenFunctionRef funcRef, BinaryenExpressionRef callRef) { + auto* wasm = (Module*)module; + auto* func = (Function*)funcRef; + auto* call = (Call*)callRef; + + if (tracing) { + std::cout << " BinaryenForceInline(theModule, functions[" << functions[funcRef] << "], " << callRef << ");\n"; + } + + struct Updater : public PostWalker { + Call* callToReplace; + bool callReplaced; + + void visitCall(Call* call) { + if (callToReplace == call) { + replaceCurrent(doInlining(getModule(), getFunction(), call)); + callReplaced = true; + } + } + } updater; + updater.callToReplace = call; + updater.callReplaced = false; + updater.walkFunctionInModule(func, wasm); + return updater.callReplaced; +} #ifdef __EMSCRIPTEN__ // Override atexit - we don't need any global ctors to actually run, and diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 2a3254f7b08..c9d80680751 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -950,6 +950,9 @@ void BinaryenSetAPITracing(int on); // signature, it returns a pointer to the existing signature or NULL if there is no // such signature yet. BinaryenFunctionTypeRef BinaryenGetFunctionTypeBySignature(BinaryenModuleRef module, BinaryenType result, BinaryenType* paramTypes, BinaryenIndex numParams); +// Directs the API to inline a specific call into the caller. Returns `1` if the +// call has been found in the caller's body and replaced, otherwise `0`. +int BinaryenForceInline(BinaryenModuleRef module, BinaryenFunctionRef func, BinaryenExpressionRef call); #ifdef __cplusplus } // extern "C" diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index aa2e613ce98..cb86b3fcfee 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -1819,6 +1819,9 @@ function wrapModule(module, self) { self['setStart'] = function(start) { return Module['_BinaryenSetStart'](module, start); }; + self['forceInline'] = function (func, call) { + return Module['_BinaryenForceInline'](module, func, call) == 1; + }; self['emitText'] = function() { var old = out; var ret = ''; diff --git a/test/binaryen.js/inlining.js b/test/binaryen.js/inlining.js new file mode 100644 index 00000000000..f45d58be5fd --- /dev/null +++ b/test/binaryen.js/inlining.js @@ -0,0 +1,24 @@ +var module = new Binaryen.Module(); +var signature = module.addFunctionType("i", Binaryen.i32, []); + +var theCall; + +var outer = module.addFunction("outer", signature, [], + theCall = module.call("inner", [], Binaryen.i32), +); + +var inner = module.addFunction("inner", signature, [], + module.i32.const(42) +); + +console.log("=== before ==="); +module.validate(); +console.log(module.emitText()); + +if (module.forceInline(outer, theCall)) { + module.removeFunction("inner"); +} + +console.log("=== after ==="); +module.validate(); +console.log(module.emitText()); diff --git a/test/binaryen.js/inlining.txt b/test/binaryen.js/inlining.txt new file mode 100644 index 00000000000..e69de29bb2d From 6266e6e49fed0886206672051d86432b91b5de4e Mon Sep 17 00:00:00 2001 From: dcode Date: Tue, 5 Feb 2019 13:20:33 +0100 Subject: [PATCH 4/6] fix --- src/binaryen-c.cpp | 8 ++++---- test/binaryen.js/inlining.txt | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 28f772bd93c..e6920d7cd41 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -2911,14 +2911,14 @@ BinaryenFunctionTypeRef BinaryenGetFunctionTypeBySignature(BinaryenModuleRef mod return NULL; } int BinaryenForceInline(BinaryenModuleRef module, BinaryenFunctionRef funcRef, BinaryenExpressionRef callRef) { - auto* wasm = (Module*)module; - auto* func = (Function*)funcRef; - auto* call = (Call*)callRef; - if (tracing) { std::cout << " BinaryenForceInline(theModule, functions[" << functions[funcRef] << "], " << callRef << ");\n"; } + auto* wasm = (Module*)module; + auto* func = (Function*)funcRef; + auto* call = (Call*)callRef; + struct Updater : public PostWalker { Call* callToReplace; bool callReplaced; diff --git a/test/binaryen.js/inlining.txt b/test/binaryen.js/inlining.txt index e69de29bb2d..c128900122f 100644 --- a/test/binaryen.js/inlining.txt +++ b/test/binaryen.js/inlining.txt @@ -0,0 +1,21 @@ +=== before === +(module + (type $i (func (result i32))) + (func $outer (; 0 ;) (type $i) (result i32) + (call $inner) + ) + (func $inner (; 1 ;) (type $i) (result i32) + (i32.const 42) + ) +) + +=== after === +(module + (type $i (func (result i32))) + (func $outer (; 0 ;) (type $i) (result i32) + (block $__inlined_func$inner (result i32) + (i32.const 42) + ) + ) +) + From 66ee198d6038be5afbba2ff7d46a77c2528c734c Mon Sep 17 00:00:00 2001 From: dcode Date: Tue, 5 Feb 2019 14:01:32 +0100 Subject: [PATCH 5/6] fix --- test/binaryen.js/{inlining.txt => inlining.js.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/binaryen.js/{inlining.txt => inlining.js.txt} (100%) diff --git a/test/binaryen.js/inlining.txt b/test/binaryen.js/inlining.js.txt similarity index 100% rename from test/binaryen.js/inlining.txt rename to test/binaryen.js/inlining.js.txt From 0a7697d47232de591b74721108d865dd49b7512e Mon Sep 17 00:00:00 2001 From: dcode Date: Tue, 5 Feb 2019 16:59:54 +0100 Subject: [PATCH 6/6] uniquify names --- src/binaryen-c.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index e6920d7cd41..6b3be10a0a2 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -2933,6 +2933,7 @@ int BinaryenForceInline(BinaryenModuleRef module, BinaryenFunctionRef funcRef, B updater.callToReplace = call; updater.callReplaced = false; updater.walkFunctionInModule(func, wasm); + if (updater.callReplaced) UniqueNameMapper::uniquify(func->body); return updater.callReplaced; }