Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build-js.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
27 changes: 27 additions & 0 deletions src/binaryen-c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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<Updater> {
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
Expand Down
3 changes: 3 additions & 0 deletions src/binaryen-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
80 changes: 80 additions & 0 deletions src/ir/inlining.h
Original file line number Diff line number Diff line change
@@ -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<Updater> {
std::map<Index, Index> 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
3 changes: 3 additions & 0 deletions src/js/binaryen.js-post.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand Down
59 changes: 3 additions & 56 deletions src/passes/Inlining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -172,61 +173,6 @@ struct Planner : public WalkerPass<PostWalker<Planner>> {
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<Call>();
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<Updater> {
std::map<Index, Index> 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;
Expand Down Expand Up @@ -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<Call>());

inlinedUses[inlinedName]++;
inlinedInto.insert(func.get());
assert(inlinedUses[inlinedName] <= infos[inlinedName].calls);
Expand Down
24 changes: 24 additions & 0 deletions test/binaryen.js/inlining.js
Original file line number Diff line number Diff line change
@@ -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());
21 changes: 21 additions & 0 deletions test/binaryen.js/inlining.js.txt
Original file line number Diff line number Diff line change
@@ -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)
)
)
)