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..6b3be10a0a2 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,32 @@ BinaryenFunctionTypeRef BinaryenGetFunctionTypeBySignature(BinaryenModuleRef mod return NULL; } +int BinaryenForceInline(BinaryenModuleRef module, BinaryenFunctionRef funcRef, BinaryenExpressionRef 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; + + 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); + if (updater.callReplaced) UniqueNameMapper::uniquify(func->body); + 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/ir/inlining.h b/src/ir/inlining.h new file mode 100644 index 00000000000..8cb5cf25a16 --- /dev/null +++ b/src/ir/inlining.h @@ -0,0 +1,80 @@ +/* + * 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 { + +// 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$") + callee->name.str); + // 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 < callee->getNumLocals(); i++) { + updater.localMapping[i] = builder.addVar(caller, callee->getLocalType(i)); + } + // assign the operands into the params + 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 < 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(callee->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/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/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index f801662e0eb..61d4f663840 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" @@ -172,61 +173,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; @@ -320,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); 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.js.txt b/test/binaryen.js/inlining.js.txt new file mode 100644 index 00000000000..c128900122f --- /dev/null +++ b/test/binaryen.js/inlining.js.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) + ) + ) +) +