Merge pull request #9142 from ethereum/wasm-support-for-i32-in-generated-code

Add support for types in Wasm AST and code generation
This commit is contained in:
chriseth 2020-06-11 13:59:44 +02:00 committed by GitHub
commit c42373dce4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 248 additions and 60 deletions

View File

@ -22,9 +22,11 @@
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <libsolutil/Visitor.h>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <boost/range/adaptor/map.hpp> #include <boost/range/adaptor/map.hpp>
#include <boost/range/adaptor/transformed.hpp>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
@ -82,6 +84,16 @@ bytes toBytes(ValueType _vt)
return toBytes(uint8_t(_vt)); return toBytes(uint8_t(_vt));
} }
ValueType toValueType(wasm::Type _type)
{
if (_type == wasm::Type::i32)
return ValueType::I32;
else if (_type == wasm::Type::i64)
return ValueType::I64;
else
yulAssert(false, "Invalid wasm variable type");
}
enum class Export: uint8_t enum class Export: uint8_t
{ {
Function = 0x0, Function = 0x0,
@ -131,6 +143,16 @@ bytes toBytes(Opcode _o)
return toBytes(uint8_t(_o)); return toBytes(uint8_t(_o));
} }
Opcode constOpcodeFor(ValueType _type)
{
if (_type == ValueType::I32)
return Opcode::I32Const;
else if (_type == ValueType::I64)
return Opcode::I64Const;
else
yulAssert(false, "Values of this type cannot be used with const opcode");
}
static map<string, uint8_t> const builtins = { static map<string, uint8_t> const builtins = {
{"i32.load", 0x28}, {"i32.load", 0x28},
{"i64.load", 0x29}, {"i64.load", 0x29},
@ -249,6 +271,34 @@ bytes makeSection(Section _section, bytes _data)
return toBytes(_section) + prefixSize(move(_data)); return toBytes(_section) + prefixSize(move(_data));
} }
/// This is a kind of run-length-encoding of local types.
vector<pair<size_t, ValueType>> groupLocalVariables(vector<VariableDeclaration> _localVariables)
{
vector<pair<size_t, ValueType>> localEntries;
size_t entrySize = 0;
ValueType entryType = ValueType::I32; // Any type would work here
for (VariableDeclaration const& localVariable: _localVariables)
{
ValueType variableType = toValueType(localVariable.type);
if (variableType != entryType)
{
if (entrySize > 0)
localEntries.emplace_back(entrySize, entryType);
entryType = variableType;
entrySize = 0;
}
++entrySize;
}
if (entrySize > 0)
localEntries.emplace_back(entrySize, entryType);
return localEntries;
}
} }
bytes BinaryTransform::run(Module const& _module) bytes BinaryTransform::run(Module const& _module)
@ -297,7 +347,10 @@ bytes BinaryTransform::run(Module const& _module)
bytes BinaryTransform::operator()(Literal const& _literal) bytes BinaryTransform::operator()(Literal const& _literal)
{ {
return toBytes(Opcode::I64Const) + lebEncodeSigned(_literal.value); return std::visit(GenericVisitor{
[&](uint32_t _value) -> bytes { return toBytes(Opcode::I32Const) + lebEncodeSigned(static_cast<int32_t>(_value)); },
[&](uint64_t _value) -> bytes { return toBytes(Opcode::I64Const) + lebEncodeSigned(static_cast<int64_t>(_value)); },
}, _literal.value);
} }
bytes BinaryTransform::operator()(StringLiteral const&) bytes BinaryTransform::operator()(StringLiteral const&)
@ -443,21 +496,18 @@ bytes BinaryTransform::operator()(FunctionDefinition const& _function)
{ {
bytes ret; bytes ret;
// This is a kind of run-length-encoding of local types. Has to be adapted once vector<pair<size_t, ValueType>> localEntries = groupLocalVariables(_function.locals);
// we have locals of different types. ret += lebEncode(localEntries.size());
if (_function.locals.size() == 0) for (pair<size_t, ValueType> const& entry: localEntries)
ret += lebEncode(0); // number of locals groups
else
{ {
ret += lebEncode(1); // number of locals groups ret += lebEncode(entry.first);
ret += lebEncode(_function.locals.size()); ret += toBytes(entry.second);
ret += toBytes(ValueType::I64);
} }
m_locals.clear(); m_locals.clear();
size_t varIdx = 0; size_t varIdx = 0;
for (size_t i = 0; i < _function.parameterNames.size(); ++i) for (size_t i = 0; i < _function.parameters.size(); ++i)
m_locals[_function.parameterNames[i]] = varIdx++; m_locals[_function.parameters[i].name] = varIdx++;
for (size_t i = 0; i < _function.locals.size(); ++i) for (size_t i = 0; i < _function.locals.size(); ++i)
m_locals[_function.locals[i].variableName] = varIdx++; m_locals[_function.locals[i].variableName] = varIdx++;
@ -475,38 +525,39 @@ BinaryTransform::Type BinaryTransform::typeOf(FunctionImport const& _import)
{ {
return { return {
encodeTypes(_import.paramTypes), encodeTypes(_import.paramTypes),
encodeTypes(_import.returnType ? vector<string>(1, *_import.returnType) : vector<string>()) encodeTypes(_import.returnType ? vector<wasm::Type>(1, *_import.returnType) : vector<wasm::Type>())
}; };
} }
BinaryTransform::Type BinaryTransform::typeOf(FunctionDefinition const& _funDef) BinaryTransform::Type BinaryTransform::typeOf(FunctionDefinition const& _funDef)
{ {
return { return {
encodeTypes(vector<string>(_funDef.parameterNames.size(), "i64")), encodeTypes(_funDef.parameters),
encodeTypes(vector<string>(_funDef.returns ? 1 : 0, "i64")) encodeTypes(_funDef.returnType ? vector<wasm::Type>(1, *_funDef.returnType) : vector<wasm::Type>())
}; };
} }
uint8_t BinaryTransform::encodeType(string const& _typeName) uint8_t BinaryTransform::encodeType(wasm::Type _type)
{ {
if (_typeName == "i32") return uint8_t(toValueType(_type));
return uint8_t(ValueType::I32);
else if (_typeName == "i64")
return uint8_t(ValueType::I64);
else
yulAssert(false, "");
return 0;
} }
vector<uint8_t> BinaryTransform::encodeTypes(vector<string> const& _typeNames) vector<uint8_t> BinaryTransform::encodeTypes(vector<wasm::Type> const& _types)
{ {
vector<uint8_t> result; vector<uint8_t> result;
for (auto const& t: _typeNames) for (wasm::Type t: _types)
result.emplace_back(encodeType(t)); result.emplace_back(encodeType(t));
return result; return result;
} }
vector<uint8_t> BinaryTransform::encodeTypes(wasm::TypedNameList const& _typedNameList)
{
vector<uint8_t> result;
for (TypedName const& typedName: _typedNameList)
result.emplace_back(encodeType(typedName.type));
return result;
}
map<BinaryTransform::Type, vector<string>> BinaryTransform::typeToFunctionMap( map<BinaryTransform::Type, vector<string>> BinaryTransform::typeToFunctionMap(
vector<wasm::FunctionImport> const& _imports, vector<wasm::FunctionImport> const& _imports,
vector<wasm::FunctionDefinition> const& _functions vector<wasm::FunctionDefinition> const& _functions
@ -612,13 +663,16 @@ bytes BinaryTransform::memorySection()
bytes BinaryTransform::globalSection(vector<wasm::GlobalVariableDeclaration> const& _globals) bytes BinaryTransform::globalSection(vector<wasm::GlobalVariableDeclaration> const& _globals)
{ {
bytes result = lebEncode(_globals.size()); bytes result = lebEncode(_globals.size());
for (size_t i = 0; i < _globals.size(); ++i) for (wasm::GlobalVariableDeclaration const& global: _globals)
{
ValueType globalType = toValueType(global.type);
result += result +=
toBytes(ValueType::I64) + toBytes(globalType) +
lebEncode(static_cast<uint8_t>(Mutability::Var)) + lebEncode(static_cast<uint8_t>(Mutability::Var)) +
toBytes(Opcode::I64Const) + toBytes(constOpcodeFor(globalType)) +
lebEncodeSigned(0) + lebEncodeSigned(0) +
toBytes(Opcode::End); toBytes(Opcode::End);
}
return makeSection(Section::GLOBAL, move(result)); return makeSection(Section::GLOBAL, move(result));
} }

View File

@ -71,8 +71,9 @@ private:
static Type typeOf(wasm::FunctionImport const& _import); static Type typeOf(wasm::FunctionImport const& _import);
static Type typeOf(wasm::FunctionDefinition const& _funDef); static Type typeOf(wasm::FunctionDefinition const& _funDef);
static uint8_t encodeType(std::string const& _typeName); static uint8_t encodeType(wasm::Type _type);
static std::vector<uint8_t> encodeTypes(std::vector<std::string> const& _typeNames); static std::vector<uint8_t> encodeTypes(std::vector<wasm::Type> const& _types);
static std::vector<uint8_t> encodeTypes(wasm::TypedNameList const& _typedNameList);
static std::map<Type, std::vector<std::string>> typeToFunctionMap( static std::map<Type, std::vector<std::string>> typeToFunctionMap(
std::vector<wasm::FunctionImport> const& _imports, std::vector<wasm::FunctionImport> const& _imports,

View File

@ -20,7 +20,10 @@
#include <libyul/backends/wasm/TextTransform.h> #include <libyul/backends/wasm/TextTransform.h>
#include <libyul/Exceptions.h>
#include <libsolutil/StringUtils.h> #include <libsolutil/StringUtils.h>
#include <libsolutil/Visitor.h>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
@ -44,9 +47,9 @@ string TextTransform::run(wasm::Module const& _module)
{ {
ret += " (import \"" + imp.module + "\" \"" + imp.externalName + "\" (func $" + imp.internalName; ret += " (import \"" + imp.module + "\" \"" + imp.externalName + "\" (func $" + imp.internalName;
if (!imp.paramTypes.empty()) if (!imp.paramTypes.empty())
ret += " (param" + joinHumanReadablePrefixed(imp.paramTypes, " ", " ") + ")"; ret += " (param" + joinHumanReadablePrefixed(imp.paramTypes | boost::adaptors::transformed(encodeType), " ", " ") + ")";
if (imp.returnType) if (imp.returnType)
ret += " (result " + *imp.returnType + ")"; ret += " (result " + encodeType(*imp.returnType) + ")";
ret += "))\n"; ret += "))\n";
} }
@ -56,7 +59,7 @@ string TextTransform::run(wasm::Module const& _module)
ret += " (export \"main\" (func $main))\n"; ret += " (export \"main\" (func $main))\n";
for (auto const& g: _module.globals) for (auto const& g: _module.globals)
ret += " (global $" + g.variableName + " (mut i64) (i64.const 0))\n"; ret += " (global $" + g.variableName + " (mut " + encodeType(g.type) + ") (" + encodeType(g.type) + ".const 0))\n";
ret += "\n"; ret += "\n";
for (auto const& f: _module.functions) for (auto const& f: _module.functions)
ret += transform(f) + "\n"; ret += transform(f) + "\n";
@ -65,7 +68,10 @@ string TextTransform::run(wasm::Module const& _module)
string TextTransform::operator()(wasm::Literal const& _literal) string TextTransform::operator()(wasm::Literal const& _literal)
{ {
return "(i64.const " + to_string(_literal.value) + ")"; return std::visit(GenericVisitor{
[&](uint32_t _value) -> string { return "(i32.const " + to_string(_value) + ")"; },
[&](uint64_t _value) -> string { return "(i64.const " + to_string(_value) + ")"; },
}, _literal.value);
} }
string TextTransform::operator()(wasm::StringLiteral const& _literal) string TextTransform::operator()(wasm::StringLiteral const& _literal)
@ -162,12 +168,12 @@ string TextTransform::indented(string const& _in)
string TextTransform::transform(wasm::FunctionDefinition const& _function) string TextTransform::transform(wasm::FunctionDefinition const& _function)
{ {
string ret = "(func $" + _function.name + "\n"; string ret = "(func $" + _function.name + "\n";
for (auto const& param: _function.parameterNames) for (auto const& param: _function.parameters)
ret += " (param $" + param + " i64)\n"; ret += " (param $" + param.name + " " + encodeType(param.type) + ")\n";
if (_function.returns) if (_function.returnType.has_value())
ret += " (result i64)\n"; ret += " (result " + encodeType(_function.returnType.value()) + ")\n";
for (auto const& local: _function.locals) for (auto const& local: _function.locals)
ret += " (local $" + local.variableName + " i64)\n"; ret += " (local $" + local.variableName + " " + encodeType(local.type) + ")\n";
ret += indented(joinTransformed(_function.body, '\n')); ret += indented(joinTransformed(_function.body, '\n'));
if (ret.back() != '\n') if (ret.back() != '\n')
ret += '\n'; ret += '\n';
@ -193,3 +199,13 @@ string TextTransform::joinTransformed(vector<wasm::Expression> const& _expressio
} }
return ret; return ret;
} }
string TextTransform::encodeType(wasm::Type _type)
{
if (_type == wasm::Type::i32)
return "i32";
else if (_type == wasm::Type::i64)
return "i64";
else
yulAssert(false, "Invalid wasm type");
}

View File

@ -63,6 +63,8 @@ private:
std::vector<wasm::Expression> const& _expressions, std::vector<wasm::Expression> const& _expressions,
char _separator = ' ' char _separator = ' '
); );
static std::string encodeType(wasm::Type _type);
}; };
} }

View File

@ -30,6 +30,15 @@
namespace solidity::yul::wasm namespace solidity::yul::wasm
{ {
enum class Type
{
i32,
i64,
};
struct TypedName { std::string name; Type type; };
using TypedNameList = std::vector<TypedName>;
struct Literal; struct Literal;
struct StringLiteral; struct StringLiteral;
struct LocalVariable; struct LocalVariable;
@ -50,7 +59,7 @@ using Expression = std::variant<
Block, If, Loop, Branch, BranchIf, Return Block, If, Loop, Branch, BranchIf, Return
>; >;
struct Literal { uint64_t value; }; struct Literal { std::variant<uint32_t, uint64_t> value; };
struct StringLiteral { std::string value; }; struct StringLiteral { std::string value; };
struct LocalVariable { std::string name; }; struct LocalVariable { std::string name; };
struct GlobalVariable { std::string name; }; struct GlobalVariable { std::string name; };
@ -70,21 +79,21 @@ struct Branch { Label label; };
struct Return {}; struct Return {};
struct BranchIf { Label label; std::unique_ptr<Expression> condition; }; struct BranchIf { Label label; std::unique_ptr<Expression> condition; };
struct VariableDeclaration { std::string variableName; }; struct VariableDeclaration { std::string variableName; Type type; };
struct GlobalVariableDeclaration { std::string variableName; }; struct GlobalVariableDeclaration { std::string variableName; Type type; };
struct FunctionImport { struct FunctionImport {
std::string module; std::string module;
std::string externalName; std::string externalName;
std::string internalName; std::string internalName;
std::vector<std::string> paramTypes; std::vector<Type> paramTypes;
std::optional<std::string> returnType; std::optional<Type> returnType;
}; };
struct FunctionDefinition struct FunctionDefinition
{ {
std::string name; std::string name;
std::vector<std::string> parameterNames; std::vector<TypedName> parameters;
bool returns; std::optional<Type> returnType;
std::vector<VariableDeclaration> locals; std::vector<VariableDeclaration> locals;
std::vector<Expression> body; std::vector<Expression> body;
}; };

View File

@ -88,7 +88,7 @@ wasm::Expression WasmCodeTransform::operator()(VariableDeclaration const& _varDe
for (auto const& var: _varDecl.variables) for (auto const& var: _varDecl.variables)
{ {
variableNames.emplace_back(var.name.str()); variableNames.emplace_back(var.name.str());
m_localVariables.emplace_back(wasm::VariableDeclaration{variableNames.back()}); m_localVariables.emplace_back(wasm::VariableDeclaration{variableNames.back(), wasm::Type::i64});
} }
if (_varDecl.value) if (_varDecl.value)
@ -127,10 +127,10 @@ wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
builtin->name.str().substr(4), builtin->name.str().substr(4),
builtin->name.str(), builtin->name.str(),
{}, {},
builtin->returns.empty() ? nullopt : make_optional<string>(builtin->returns.front().str()) builtin->returns.empty() ? nullopt : make_optional<wasm::Type>(translatedType(builtin->returns.front()))
}; };
for (auto const& param: builtin->parameters) for (auto const& param: builtin->parameters)
imp.paramTypes.emplace_back(param.str()); imp.paramTypes.emplace_back(translatedType(param));
m_functionsToImport[builtin->name] = std::move(imp); m_functionsToImport[builtin->name] = std::move(imp);
} }
typeConversionNeeded = true; typeConversionNeeded = true;
@ -184,7 +184,7 @@ wasm::Expression WasmCodeTransform::operator()(Literal const& _literal)
{ {
u256 value = valueOfLiteral(_literal); u256 value = valueOfLiteral(_literal);
yulAssert(value <= numeric_limits<uint64_t>::max(), "Literal too large: " + value.str()); yulAssert(value <= numeric_limits<uint64_t>::max(), "Literal too large: " + value.str());
return wasm::Literal{uint64_t(value)}; return wasm::Literal{static_cast<uint64_t>(value)};
} }
wasm::Expression WasmCodeTransform::operator()(If const& _if) wasm::Expression WasmCodeTransform::operator()(If const& _if)
@ -193,7 +193,7 @@ wasm::Expression WasmCodeTransform::operator()(If const& _if)
vector<wasm::Expression> args; vector<wasm::Expression> args;
args.emplace_back(visitReturnByValue(*_if.condition)); args.emplace_back(visitReturnByValue(*_if.condition));
args.emplace_back(wasm::Literal{0}); args.emplace_back(wasm::Literal{static_cast<uint64_t>(0)});
return wasm::If{ return wasm::If{
make_unique<wasm::Expression>(wasm::BuiltinCall{"i64.ne", std::move(args)}), make_unique<wasm::Expression>(wasm::BuiltinCall{"i64.ne", std::move(args)}),
visit(_if.body.statements), visit(_if.body.statements),
@ -205,7 +205,7 @@ wasm::Expression WasmCodeTransform::operator()(Switch const& _switch)
{ {
wasm::Block block; wasm::Block block;
string condition = m_nameDispenser.newName("condition"_yulstring).str(); string condition = m_nameDispenser.newName("condition"_yulstring).str();
m_localVariables.emplace_back(wasm::VariableDeclaration{condition}); m_localVariables.emplace_back(wasm::VariableDeclaration{condition, wasm::Type::i64});
block.statements.emplace_back(wasm::LocalAssignment{condition, visit(*_switch.expression)}); block.statements.emplace_back(wasm::LocalAssignment{condition, visit(*_switch.expression)});
vector<wasm::Expression>* currentBlock = &block.statements; vector<wasm::Expression>* currentBlock = &block.statements;
@ -325,10 +325,11 @@ wasm::FunctionDefinition WasmCodeTransform::translateFunction(yul::FunctionDefin
wasm::FunctionDefinition fun; wasm::FunctionDefinition fun;
fun.name = _fun.name.str(); fun.name = _fun.name.str();
for (auto const& param: _fun.parameters) for (auto const& param: _fun.parameters)
fun.parameterNames.emplace_back(param.name.str()); fun.parameters.push_back({param.name.str(), wasm::Type::i64});
for (auto const& retParam: _fun.returnVariables) for (auto const& retParam: _fun.returnVariables)
fun.locals.emplace_back(wasm::VariableDeclaration{retParam.name.str()}); fun.locals.emplace_back(wasm::VariableDeclaration{retParam.name.str(), wasm::Type::i64});
fun.returns = !_fun.returnVariables.empty(); if (!_fun.returnVariables.empty())
fun.returnType = wasm::Type::i64;
yulAssert(m_localVariables.empty(), ""); yulAssert(m_localVariables.empty(), "");
yulAssert(m_functionBodyLabel.empty(), ""); yulAssert(m_functionBodyLabel.empty(), "");
@ -361,14 +362,14 @@ wasm::Expression WasmCodeTransform::injectTypeConversionIfNeeded(wasm::FunctionC
{ {
wasm::FunctionImport const& import = m_functionsToImport.at(YulString{_call.functionName}); wasm::FunctionImport const& import = m_functionsToImport.at(YulString{_call.functionName});
for (size_t i = 0; i < _call.arguments.size(); ++i) for (size_t i = 0; i < _call.arguments.size(); ++i)
if (import.paramTypes.at(i) == "i32") if (import.paramTypes.at(i) == wasm::Type::i32)
_call.arguments[i] = wasm::BuiltinCall{"i32.wrap_i64", make_vector<wasm::Expression>(std::move(_call.arguments[i]))}; _call.arguments[i] = wasm::BuiltinCall{"i32.wrap_i64", make_vector<wasm::Expression>(std::move(_call.arguments[i]))};
else else
yulAssert(import.paramTypes.at(i) == "i64", "Unknown type " + import.paramTypes.at(i)); yulAssert(import.paramTypes.at(i) == wasm::Type::i64, "Invalid Wasm type");
if (import.returnType && *import.returnType != "i64") if (import.returnType && *import.returnType != wasm::Type::i64)
{ {
yulAssert(*import.returnType == "i32", "Invalid type " + *import.returnType); yulAssert(*import.returnType == wasm::Type::i32, "Invalid Wasm type");
return wasm::BuiltinCall{"i64.extend_i32_u", make_vector<wasm::Expression>(std::move(_call))}; return wasm::BuiltinCall{"i64.extend_i32_u", make_vector<wasm::Expression>(std::move(_call))};
} }
return {std::move(_call)}; return {std::move(_call)};
@ -400,6 +401,17 @@ void WasmCodeTransform::allocateGlobals(size_t _amount)
{ {
while (m_globalVariables.size() < _amount) while (m_globalVariables.size() < _amount)
m_globalVariables.emplace_back(wasm::GlobalVariableDeclaration{ m_globalVariables.emplace_back(wasm::GlobalVariableDeclaration{
m_nameDispenser.newName("global_"_yulstring).str() m_nameDispenser.newName("global_"_yulstring).str(),
wasm::Type::i64
}); });
} }
wasm::Type WasmCodeTransform::translatedType(yul::Type _yulType)
{
if (_yulType == "i32"_yulstring)
return wasm::Type::i32;
else if (_yulType == "i64"_yulstring)
return wasm::Type::i64;
else
yulAssert(false, "This Yul type does not have a corresponding type in Wasm.");
}

View File

@ -89,6 +89,8 @@ private:
/// Makes sure that there are at least @a _amount global variables. /// Makes sure that there are at least @a _amount global variables.
void allocateGlobals(size_t _amount); void allocateGlobals(size_t _amount);
static wasm::Type translatedType(yul::Type _yulType);
Dialect const& m_dialect; Dialect const& m_dialect;
NameDispenser m_nameDispenser; NameDispenser m_nameDispenser;

View File

@ -0,0 +1 @@
--yul --yul-dialect ewasm --machine ewasm

View File

@ -0,0 +1 @@
Warning: Yul is still experimental. Please use the output with care.

View File

@ -0,0 +1,17 @@
object "object" {
code {
function main()
{
let m:i64, n:i32, p:i32, q:i64 := multireturn(1:i32, 2:i64, 3:i64, 4:i32)
}
function multireturn(a:i32, b:i64, c:i64, d:i32) -> x:i64, y:i32, z:i32, w:i64
{
x := b
w := c
y := a
z := d
}
}
}

View File

@ -0,0 +1,73 @@
======= wasm_to_wasm_function_returning_multiple_values/input.yul (Ewasm) =======
Pretty printed source:
object "object" {
code {
function main()
{
let m, n:i32, p:i32, q := multireturn(1:i32, 2, 3, 4:i32)
}
function multireturn(a:i32, b, c, d:i32) -> x, y:i32, z:i32, w
{
x := b
w := c
y := a
z := d
}
}
}
Binary representation:
0061736d01000000010c0260000060047e7e7e7e017e020100030302000105030100010610037e0142000b7e0142000b7e0142000b071102066d656d6f72790200046d61696e00000a4a022201047e024002404201420242034204100121002300210123012102230221030b0b0b2501047e0240200121042002210720002105200321060b20052400200624012007240220040b
Text representation:
(module
(memory $memory (export "memory") 1)
(export "main" (func $main))
(global $global_ (mut i64) (i64.const 0))
(global $global__1 (mut i64) (i64.const 0))
(global $global__2 (mut i64) (i64.const 0))
(func $main
(local $m i64)
(local $n i64)
(local $p i64)
(local $q i64)
(block $label_
(block
(local.set $m (call $multireturn (i64.const 1) (i64.const 2) (i64.const 3) (i64.const 4)))
(local.set $n (global.get $global_))
(local.set $p (global.get $global__1))
(local.set $q (global.get $global__2))
)
)
)
(func $multireturn
(param $a i64)
(param $b i64)
(param $c i64)
(param $d i64)
(result i64)
(local $x i64)
(local $y i64)
(local $z i64)
(local $w i64)
(block $label__3
(local.set $x (local.get $b))
(local.set $w (local.get $c))
(local.set $y (local.get $a))
(local.set $z (local.get $d))
)
(global.set $global_ (local.get $y))
(global.set $global__1 (local.get $z))
(global.set $global__2 (local.get $w))
(local.get $x)
)
)