Skip to content

Commit

Permalink
Initial tail call implementation (#2197)
Browse files Browse the repository at this point in the history
Including parsing, printing, assembling, disassembling.

TODO:

 - interpreting
 - effects
 - finalization and typing
 - fuzzing
 - JS/C API
  • Loading branch information
tlively committed Jul 3, 2019
1 parent 256187c commit 2a138fa
Show file tree
Hide file tree
Showing 22 changed files with 182 additions and 35 deletions.
6 changes: 4 additions & 2 deletions scripts/gen-s-parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
("br_if", "makeBreak(s)"),
("br_table", "makeBreakTable(s)"),
("return", "makeReturn(s)"),
("call", "makeCall(s)"),
("call_indirect", "makeCallIndirect(s)"),
("call", "makeCall(s, /*isReturn=*/false)"),
("call_indirect", "makeCallIndirect(s, /*isReturn=*/false)"),
("return_call", "makeCall(s, /*isReturn=*/true)"),
("return_call_indirect", "makeCallIndirect(s, /*isReturn=*/true)"),
("drop", "makeDrop(s)"),
("select", "makeSelect(s)"),
("local.get", "makeLocalGet(s)"),
Expand Down
26 changes: 21 additions & 5 deletions src/gen-s-parser.inc
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ switch (op[0]) {
case 'c': {
switch (op[4]) {
case '\0':
if (strcmp(op, "call") == 0) { return makeCall(s); }
if (strcmp(op, "call") == 0) { return makeCall(s, /*isReturn=*/false); }
goto parse_error;
case '_':
if (strcmp(op, "call_indirect") == 0) { return makeCallIndirect(s); }
if (strcmp(op, "call_indirect") == 0) { return makeCallIndirect(s, /*isReturn=*/false); }
goto parse_error;
default: goto parse_error;
}
Expand Down Expand Up @@ -2228,9 +2228,25 @@ switch (op[0]) {
case 'p':
if (strcmp(op, "push") == 0) { return makePush(s); }
goto parse_error;
case 'r':
if (strcmp(op, "return") == 0) { return makeReturn(s); }
goto parse_error;
case 'r': {
switch (op[6]) {
case '\0':
if (strcmp(op, "return") == 0) { return makeReturn(s); }
goto parse_error;
case '_': {
switch (op[11]) {
case '\0':
if (strcmp(op, "return_call") == 0) { return makeCall(s, /*isReturn=*/true); }
goto parse_error;
case '_':
if (strcmp(op, "return_call_indirect") == 0) { return makeCallIndirect(s, /*isReturn=*/true); }
goto parse_error;
default: goto parse_error;
}
}
default: goto parse_error;
}
}
case 's':
if (strcmp(op, "select") == 0) { return makeSelect(s); }
goto parse_error;
Expand Down
13 changes: 11 additions & 2 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,20 @@ struct PrintExpressionContents
o << ' ' << curr->default_;
}
void visitCall(Call* curr) {
printMedium(o, "call ");
if (curr->isReturn) {
printMedium(o, "return_call ");
} else {
printMedium(o, "call ");
}
printName(curr->target, o);
}
void visitCallIndirect(CallIndirect* curr) {
printMedium(o, "call_indirect (type ") << curr->fullType << ')';
if (curr->isReturn) {
printMedium(o, "return_call_indirect (type ");
} else {
printMedium(o, "call_indirect (type ");
}
o << curr->fullType << ')';
}
void visitLocalGet(LocalGet* curr) {
printMedium(o, "local.get ") << printableLocal(curr->index, currFunction);
Expand Down
1 change: 1 addition & 0 deletions src/tools/tool-options.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ struct ToolOptions : public Options {
.addFeature(FeatureSet::BulkMemory, "bulk memory operations")
.addFeature(FeatureSet::ExceptionHandling,
"exception handling operations")
.addFeature(FeatureSet::TailCall, "tail call operations")
.add("--no-validation",
"-n",
"Disables validation, assumes inputs are correct",
Expand Down
3 changes: 3 additions & 0 deletions src/wasm-binary.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ extern const char* TruncSatFeature;
extern const char* SignExtFeature;
extern const char* SIMD128Feature;
extern const char* ExceptionHandlingFeature;
extern const char* TailCallFeature;

enum Subsection {
NameFunction = 1,
Expand All @@ -429,6 +430,8 @@ enum ASTNodes {

CallFunction = 0x10,
CallIndirect = 0x11,
RetCallFunction = 0x12,
RetCallIndirect = 0x13,

Drop = 0x1a,
Select = 0x1b,
Expand Down
24 changes: 14 additions & 10 deletions src/wasm-builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,41 +187,45 @@ class Builder {
ret->condition = condition;
return ret;
}
Call* makeCall(Name target, const std::vector<Expression*>& args, Type type) {
Call* makeCall(Name target,
const std::vector<Expression*>& args,
Type type,
bool isReturn = false) {
auto* call = allocator.alloc<Call>();
// not all functions may exist yet, so type must be provided
call->type = type;
call->target = target;
call->operands.set(args);
call->isReturn = isReturn;
return call;
}
template<typename T> Call* makeCall(Name target, const T& args, Type type) {
template<typename T>
Call* makeCall(Name target, const T& args, Type type, bool isReturn = false) {
auto* call = allocator.alloc<Call>();
// not all functions may exist yet, so type must be provided
call->type = type;
call->target = target;
call->operands.set(args);
call->isReturn = isReturn;
return call;
}
CallIndirect* makeCallIndirect(FunctionType* type,
Expression* target,
const std::vector<Expression*>& args) {
auto* call = allocator.alloc<CallIndirect>();
call->fullType = type->name;
call->type = type->result;
call->target = target;
call->operands.set(args);
return call;
const std::vector<Expression*>& args,
bool isReturn = false) {
return makeCallIndirect(type->name, target, args, type->result, isReturn);
}
CallIndirect* makeCallIndirect(Name fullType,
Expression* target,
const std::vector<Expression*>& args,
Type type) {
Type type,
bool isReturn = false) {
auto* call = allocator.alloc<CallIndirect>();
call->fullType = fullType;
call->type = type;
call->target = target;
call->operands.set(args);
call->isReturn = isReturn;
return call;
}
// FunctionType
Expand Down
11 changes: 9 additions & 2 deletions src/wasm-features.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ struct FeatureSet {
BulkMemory = 1 << 4,
SignExt = 1 << 5,
ExceptionHandling = 1 << 6,
All = Atomics | MutableGlobals | TruncSat | SIMD | BulkMemory | SignExt |
ExceptionHandling
TailCall = 1 << 7,
All = (1 << 8) - 1
};

static std::string toString(Feature f) {
Expand All @@ -52,6 +52,8 @@ struct FeatureSet {
return "sign-ext";
case ExceptionHandling:
return "exception-handling";
case TailCall:
return "tail-call";
default:
WASM_UNREACHABLE();
}
Expand All @@ -69,6 +71,7 @@ struct FeatureSet {
bool hasBulkMemory() const { return features & BulkMemory; }
bool hasSignExt() const { return features & SignExt; }
bool hasExceptionHandling() const { return features & ExceptionHandling; }
bool hasTailCall() const { return features & TailCall; }
bool hasAll() const { return features & All; }

void makeMVP() { features = MVP; }
Expand All @@ -82,6 +85,7 @@ struct FeatureSet {
void setBulkMemory(bool v = true) { set(BulkMemory, v); }
void setSignExt(bool v = true) { set(SignExt, v); }
void setExceptionHandling(bool v = true) { set(ExceptionHandling, v); }
void setTailCall(bool v = true) { set(TailCall, v); }
void setAll(bool v = true) { features = v ? All : MVP; }

void enable(const FeatureSet& other) { features |= other.features; }
Expand Down Expand Up @@ -111,6 +115,9 @@ struct FeatureSet {
if (hasSIMD()) {
f(SIMD);
}
if (hasTailCall()) {
f(TailCall);
}
}

bool operator<=(const FeatureSet& other) const {
Expand Down
4 changes: 2 additions & 2 deletions src/wasm-s-parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,8 @@ class SExpressionWasmBuilder {
Expression* makeIf(Element& s);
Expression* makeMaybeBlock(Element& s, size_t i, Type type);
Expression* makeLoop(Element& s);
Expression* makeCall(Element& s);
Expression* makeCallIndirect(Element& s);
Expression* makeCall(Element& s, bool isReturn);
Expression* makeCallIndirect(Element& s, bool isReturn);
template<class T>
void parseCallOperands(Element& s, Index i, Index j, T* call) {
while (i < j) {
Expand Down
10 changes: 6 additions & 4 deletions src/wasm-stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -626,8 +626,9 @@ void StackWriter<Mode, Parent>::visitCall(Call* curr) {
visitChild(operand);
}
if (!justAddToStack(curr)) {
o << int8_t(BinaryConsts::CallFunction)
<< U32LEB(parent.getFunctionIndex(curr->target));
int8_t op = curr->isReturn ? BinaryConsts::RetCallFunction
: BinaryConsts::CallFunction;
o << op << U32LEB(parent.getFunctionIndex(curr->target));
}
// TODO FIXME: this and similar can be removed
if (curr->type == unreachable) {
Expand All @@ -642,8 +643,9 @@ void StackWriter<Mode, Parent>::visitCallIndirect(CallIndirect* curr) {
}
visitChild(curr->target);
if (!justAddToStack(curr)) {
o << int8_t(BinaryConsts::CallIndirect)
<< U32LEB(parent.getFunctionTypeIndex(curr->fullType))
int8_t op = curr->isReturn ? BinaryConsts::RetCallIndirect
: BinaryConsts::CallIndirect;
o << op << U32LEB(parent.getFunctionTypeIndex(curr->fullType))
<< U32LEB(0); // Reserved flags field
}
if (curr->type == unreachable) {
Expand Down
2 changes: 2 additions & 0 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ class Call : public SpecificExpression<Expression::CallId> {

ExpressionList operands;
Name target;
bool isReturn = false;

void finalize();
};
Expand All @@ -647,6 +648,7 @@ class CallIndirect : public SpecificExpression<Expression::CallIndirectId> {
ExpressionList operands;
Name fullType;
Expression* target;
bool isReturn = false;

void finalize();
};
Expand Down
18 changes: 18 additions & 0 deletions src/wasm/wasm-binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,8 @@ void WasmBinaryWriter::writeFeaturesSection() {
return BinaryConsts::UserSections::SignExtFeature;
case FeatureSet::ExceptionHandling:
return BinaryConsts::UserSections::ExceptionHandlingFeature;
case FeatureSet::TailCall:
return BinaryConsts::UserSections::TailCallFeature;
default:
WASM_UNREACHABLE();
}
Expand Down Expand Up @@ -2162,6 +2164,8 @@ void WasmBinaryBuilder::readFeatures(size_t payloadLen) {
wasm.features.setSignExt();
} else if (name == BinaryConsts::UserSections::SIMD128Feature) {
wasm.features.setSIMD();
} else if (name == BinaryConsts::UserSections::TailCallFeature) {
wasm.features.setTailCall();
}
}
}
Expand Down Expand Up @@ -2210,6 +2214,20 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
visitCallIndirect(
(curr = allocator.alloc<CallIndirect>())->cast<CallIndirect>());
break;
case BinaryConsts::RetCallFunction: {
auto call = allocator.alloc<Call>();
call->isReturn = true;
curr = call;
visitCall(call);
break;
}
case BinaryConsts::RetCallIndirect: {
auto call = allocator.alloc<CallIndirect>();
call->isReturn = true;
curr = call;
visitCallIndirect(call);
break;
}
case BinaryConsts::LocalGet:
visitLocalGet((curr = allocator.alloc<LocalGet>())->cast<LocalGet>());
break;
Expand Down
7 changes: 5 additions & 2 deletions src/wasm/wasm-s-parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1622,17 +1622,19 @@ Expression* SExpressionWasmBuilder::makeLoop(Element& s) {
return ret;
}

Expression* SExpressionWasmBuilder::makeCall(Element& s) {
Expression* SExpressionWasmBuilder::makeCall(Element& s, bool isReturn) {
auto target = getFunctionName(*s[1]);
auto ret = allocator.alloc<Call>();
ret->target = target;
ret->type = functionTypes[ret->target];
parseCallOperands(s, 2, s.size(), ret);
ret->isReturn = isReturn;
ret->finalize();
return ret;
}

Expression* SExpressionWasmBuilder::makeCallIndirect(Element& s) {
Expression* SExpressionWasmBuilder::makeCallIndirect(Element& s,
bool isReturn) {
if (!wasm.table.exists) {
throw ParseException("no table");
}
Expand All @@ -1645,6 +1647,7 @@ Expression* SExpressionWasmBuilder::makeCallIndirect(Element& s) {
ret->type = functionType->result;
parseCallOperands(s, i, s.size() - 1, ret);
ret->target = parseExpression(s[s.size() - 1]);
ret->isReturn = isReturn;
ret->finalize();
return ret;
}
Expand Down
6 changes: 6 additions & 0 deletions src/wasm/wasm-validator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,9 @@ void FunctionValidator::visitSwitch(Switch* curr) {
}

void FunctionValidator::visitCall(Call* curr) {
shouldBeTrue(!curr->isReturn || getModule()->features.hasTailCall(),
curr,
"return_call requires tail calls to be enabled");
if (!info.validateGlobally) {
return;
}
Expand All @@ -593,6 +596,9 @@ void FunctionValidator::visitCall(Call* curr) {
}

void FunctionValidator::visitCallIndirect(CallIndirect* curr) {
shouldBeTrue(!curr->isReturn || getModule()->features.hasTailCall(),
curr,
"return_call_indirect requires tail calls to be enabled");
if (!info.validateGlobally) {
return;
}
Expand Down
1 change: 1 addition & 0 deletions src/wasm/wasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const char* MutableGlobalsFeature = "mutable-globals";
const char* TruncSatFeature = "nontrapping-fptoint";
const char* SignExtFeature = "sign-ext";
const char* SIMD128Feature = "simd128";
const char* TailCallFeature = "tail-call";
} // namespace UserSections
} // namespace BinaryConsts

Expand Down
4 changes: 2 additions & 2 deletions test/binaryen.js/kitchen-sink.js.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Binaryen.Features.NontrappingFPToInt: 4
Binaryen.Features.SignExt: 32
Binaryen.Features.SIMD128: 8
Binaryen.Features.ExceptionHandling: 64
Binaryen.Features.All: 127
Binaryen.Features.All: 255
BinaryenInvalidId: 0
BinaryenBlockId: 1
BinaryenIfId: 2
Expand Down Expand Up @@ -3406,7 +3406,7 @@ getExpressionInfo(f64.const)={"id":14,"type":4,"value":9.5}
functionTypes[4] = BinaryenAddFunctionType(the_module, NULL, 0, paramTypes, 0);
}
BinaryenModuleAutoDrop(the_module);
BinaryenModuleSetFeatures(the_module, 127);
BinaryenModuleSetFeatures(the_module, 255);
BinaryenModuleGetFeatures(the_module);
BinaryenModuleValidate(the_module);
BinaryenModulePrint(the_module);
Expand Down
4 changes: 2 additions & 2 deletions test/example/c-api-kitchen-sink.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ BinaryenFeatureNontrappingFPToInt: 4
BinaryenFeatureSignExt: 32
BinaryenFeatureSIMD128: 8
BinaryenFeatureExceptionHandling: 64
BinaryenFeatureAll: 127
BinaryenFeatureAll: 255
(f32.neg
(f32.const -33.61199951171875)
)
Expand Down Expand Up @@ -3351,7 +3351,7 @@ int main() {
functionTypes[4] = BinaryenAddFunctionType(the_module, NULL, 0, paramTypes, 0);
}
BinaryenModuleAutoDrop(the_module);
BinaryenModuleSetFeatures(the_module, 127);
BinaryenModuleSetFeatures(the_module, 255);
BinaryenModuleGetFeatures(the_module);
BinaryenModuleValidate(the_module);
BinaryenModulePrint(the_module);
Expand Down
Loading

0 comments on commit 2a138fa

Please sign in to comment.