From 8337de51893871de7a7868c29d7d3c8219bd6f23 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 31 Oct 2019 17:31:35 +0100 Subject: [PATCH] [WASM] Inject type conversions on the fly if needed. --- libyul/backends/wasm/EWasmCodeTransform.cpp | 70 ++++++++++++++++++- libyul/backends/wasm/EWasmCodeTransform.h | 6 ++ .../standard_eWasm_requested/output.json | 14 ++-- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/libyul/backends/wasm/EWasmCodeTransform.cpp b/libyul/backends/wasm/EWasmCodeTransform.cpp index 0670c7bc9..20ca375a7 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.cpp +++ b/libyul/backends/wasm/EWasmCodeTransform.cpp @@ -133,6 +133,8 @@ wasm::Expression EWasmCodeTransform::operator()(FunctionalInstruction const& _f) wasm::Expression EWasmCodeTransform::operator()(FunctionCall const& _call) { + bool typeConversionNeeded = false; + if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name)) { if (_call.functionName.name.str().substr(0, 4) == "eth.") @@ -152,6 +154,7 @@ wasm::Expression EWasmCodeTransform::operator()(FunctionCall const& _call) imp.paramTypes.emplace_back(param.str()); m_functionsToImport[builtin->name] = std::move(imp); } + typeConversionNeeded = true; } else if (builtin->literalArguments) { @@ -161,14 +164,32 @@ wasm::Expression EWasmCodeTransform::operator()(FunctionCall const& _call) return wasm::BuiltinCall{_call.functionName.name.str(), std::move(literals)}; } else - return wasm::BuiltinCall{_call.functionName.name.str(), visit(_call.arguments)}; + { + wasm::BuiltinCall call{ + _call.functionName.name.str(), + injectTypeConversionIfNeeded(visit(_call.arguments), builtin->parameters) + }; + if (!builtin->returns.empty() && !builtin->returns.front().empty() && builtin->returns.front() != "i64"_yulstring) + { + yulAssert(builtin->returns.front() == "i32"_yulstring, "Invalid type " + builtin->returns.front().str()); + call = wasm::BuiltinCall{"i64.extend_i32_u", make_vector(std::move(call))}; + } + return {std::move(call)}; + } } // If this function returns multiple values, then the first one will // be returned in the expression itself and the others in global variables. // The values have to be used right away in an assignment or variable declaration, // so it is handled there. - return wasm::FunctionCall{_call.functionName.name.str(), visit(_call.arguments)}; + + wasm::FunctionCall funCall{_call.functionName.name.str(), visit(_call.arguments)}; + if (typeConversionNeeded) + // Inject type conversion if needed on the fly. This is just a temporary measure + // and can be removed once we have proper types in Yul. + return injectTypeConversionIfNeeded(std::move(funCall)); + else + return {std::move(funCall)}; } wasm::Expression EWasmCodeTransform::operator()(Identifier const& _identifier) @@ -191,7 +212,16 @@ wasm::Expression EWasmCodeTransform::operator()(yul::Instruction const&) wasm::Expression EWasmCodeTransform::operator()(If const& _if) { - return wasm::If{visit(*_if.condition), visit(_if.body.statements), {}}; + // TODO converting i64 to i32 might not always be needed. + + vector args; + args.emplace_back(visitReturnByValue(*_if.condition)); + args.emplace_back(wasm::Literal{0}); + return wasm::If{ + make_unique(wasm::BuiltinCall{"i64.ne", std::move(args)}), + visit(_if.body.statements), + {} + }; } wasm::Expression EWasmCodeTransform::operator()(Switch const& _switch) @@ -336,6 +366,40 @@ wasm::FunctionDefinition EWasmCodeTransform::translateFunction(yul::FunctionDefi return fun; } +wasm::Expression EWasmCodeTransform::injectTypeConversionIfNeeded(wasm::FunctionCall _call) const +{ + wasm::FunctionImport const& import = m_functionsToImport.at(YulString{_call.functionName}); + for (size_t i = 0; i < _call.arguments.size(); ++i) + if (import.paramTypes.at(i) == "i32") + _call.arguments[i] = wasm::BuiltinCall{"i32.wrap_i64", make_vector(std::move(_call.arguments[i]))}; + else + yulAssert(import.paramTypes.at(i) == "i64", "Unknown type " + import.paramTypes.at(i)); + + if (import.returnType && *import.returnType != "i64") + { + yulAssert(*import.returnType == "i32", "Invalid type " + *import.returnType); + return wasm::BuiltinCall{"i64.extend_i32_u", make_vector(std::move(_call))}; + } + return {std::move(_call)}; +} + +vector EWasmCodeTransform::injectTypeConversionIfNeeded( + vector _arguments, + vector const& _parameterTypes +) const +{ + for (size_t i = 0; i < _arguments.size(); ++i) + if (_parameterTypes.at(i) == "i32"_yulstring) + _arguments[i] = wasm::BuiltinCall{"i32.wrap_i64", make_vector(std::move(_arguments[i]))}; + else + yulAssert( + _parameterTypes.at(i).empty() || _parameterTypes.at(i) == "i64"_yulstring, + "Unknown type " + _parameterTypes.at(i).str() + ); + + return _arguments; +} + string EWasmCodeTransform::newLabel() { return m_nameDispenser.newName("label_"_yulstring).str(); diff --git a/libyul/backends/wasm/EWasmCodeTransform.h b/libyul/backends/wasm/EWasmCodeTransform.h index bbe0e1b69..0addbba1c 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.h +++ b/libyul/backends/wasm/EWasmCodeTransform.h @@ -82,6 +82,12 @@ private: wasm::FunctionDefinition translateFunction(yul::FunctionDefinition const& _funDef); + wasm::Expression injectTypeConversionIfNeeded(wasm::FunctionCall _call) const; + std::vector injectTypeConversionIfNeeded( + std::vector _arguments, + std::vector const& _parameterTypes + ) const; + std::string newLabel(); /// Makes sure that there are at least @a _amount global variables. void allocateGlobals(size_t _amount); diff --git a/test/cmdlineTests/standard_eWasm_requested/output.json b/test/cmdlineTests/standard_eWasm_requested/output.json index 396f0f43c..244d74a02 100644 --- a/test/cmdlineTests/standard_eWasm_requested/output.json +++ b/test/cmdlineTests/standard_eWasm_requested/output.json @@ -20,7 +20,7 @@ (i64.store (i64.add (local.get $_2) (i64.const 16)) (local.get $y)) (local.set $hi_1 (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (i64.const 128) (i64.const 8)) (local.get $_3)) (i64.and (i64.shr_u (i64.const 128) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (i64.const 128) (i64.const 16)))) (i64.const 32))) (i64.store (i64.add (local.get $_2) (i64.const 24)) (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32))))) - (call $eth.revert (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (i64.const 64)) (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))) + (call $eth.revert (i32.wrap_i64 (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (i64.const 64))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)))) ) (func $u256_to_i32 @@ -30,9 +30,9 @@ (param $x4 i64) (result i64) (local $v i64) - (if (i64.ne (local.get $v) (i64.or (i64.or (local.get $x1) (local.get $x2)) (local.get $x3))) (then + (if (i64.ne (i64.ne (local.get $v) (i64.or (i64.or (local.get $x1) (local.get $x2)) (local.get $x3))) (i64.const 0)) (then (unreachable))) - (if (i64.ne (local.get $v) (i64.shr_u (local.get $x4) (i64.const 32))) (then + (if (i64.ne (i64.ne (local.get $v) (i64.shr_u (local.get $x4) (i64.const 32))) (i64.const 0)) (then (unreachable))) (local.set $v (local.get $x4)) (local.get $v) @@ -80,8 +80,8 @@ (local.set $hi_1 (i64.shl (call $endian_swap_32 (i64.const 128)) (i64.const 32))) (i64.store (i64.add (local.get $_2) (i64.const 24)) (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32))))) (local.set $_3 (datasize \"C_2_deployed\")) - (call $eth.codeCopy (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (i64.const 64)) (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (dataoffset \"C_2_deployed\")) (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_3))) - (call $eth.finish (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (i64.const 64)) (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_3))) + (call $eth.codeCopy (i32.wrap_i64 (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (i64.const 64))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (dataoffset \"C_2_deployed\"))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_3)))) + (call $eth.finish (i32.wrap_i64 (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (i64.const 64))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_3)))) ) (func $u256_to_i32 @@ -91,9 +91,9 @@ (param $x4 i64) (result i64) (local $v i64) - (if (i64.ne (local.get $v) (i64.or (i64.or (local.get $x1) (local.get $x2)) (local.get $x3))) (then + (if (i64.ne (i64.ne (local.get $v) (i64.or (i64.or (local.get $x1) (local.get $x2)) (local.get $x3))) (i64.const 0)) (then (unreachable))) - (if (i64.ne (local.get $v) (i64.shr_u (local.get $x4) (i64.const 32))) (then + (if (i64.ne (i64.ne (local.get $v) (i64.shr_u (local.get $x4) (i64.const 32))) (i64.const 0)) (then (unreachable))) (local.set $v (local.get $x4)) (local.get $v)