diff --git a/libyul/backends/wasm/BinaryTransform.cpp b/libyul/backends/wasm/BinaryTransform.cpp index 8abd2b080..ad2628695 100644 --- a/libyul/backends/wasm/BinaryTransform.cpp +++ b/libyul/backends/wasm/BinaryTransform.cpp @@ -311,7 +311,11 @@ bytes BinaryTransform::operator()(BuiltinCall const& _call) bytes args = visit(_call.arguments); if (_call.functionName == "unreachable") - return toBytes(Opcode::Unreachable); + return toBytes(Opcode::Unreachable); + else if (_call.functionName == "nop") + return toBytes(Opcode::Nop); + else if (_call.functionName == "drop") + return toBytes(Opcode::Drop); else { yulAssert(builtins.count(_call.functionName), "Builtin " + _call.functionName + " not found"); diff --git a/libyul/backends/wasm/EVMToEwasmTranslator.cpp b/libyul/backends/wasm/EVMToEwasmTranslator.cpp index 2090dd2f5..19bd98da7 100644 --- a/libyul/backends/wasm/EVMToEwasmTranslator.cpp +++ b/libyul/backends/wasm/EVMToEwasmTranslator.cpp @@ -48,25 +48,26 @@ using namespace solidity::langutil; namespace { -static string const polyfill{R"({ -function or_bool(a, b, c, d) -> r { - r := i64.or(i64.or(a, b), i64.or(c, d)) +static string const polyfill{R"( +{ +function or_bool(a, b, c, d) -> r:i32 { + r := i32.eqz(i64.eqz(i64.or(i64.or(a, b), i64.or(c, d)))) } -function or_bool_320(a, b, c, d, e) -> r { - r := i64.or(or_bool(a, b, c, d), e) +function or_bool_320(a, b, c, d, e) -> r:i32 { + r := i32.or(or_bool(a, b, c, 0), or_bool(d, e, 0, 0)) } -function or_bool_512(a, b, c, d, e, f, g, h) -> r { - r := i64.or(or_bool(a, b, c, d), or_bool(e, f, g, h)) +function or_bool_512(a, b, c, d, e, f, g, h) -> r:i32 { + r := i32.or(or_bool(a, b, c, d), or_bool(e, f, g, h)) } // returns a + y + c plus carry value on 64 bit values. // c should be at most 1 function add_carry(x, y, c) -> r, r_c { let t := i64.add(x, y) r := i64.add(t, c) - r_c := i64.or( + r_c := i64.extend_i32_u(i32.or( i64.lt_u(t, x), i64.lt_u(r, t) - ) + )) } function add(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { let carry @@ -254,8 +255,8 @@ function div(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { let m3 := 0 let m4 := 1 - for {} 1 {} { - if i64.or(i64.eqz(i64.clz(y1)), gte_256x256_64(y1, y2, y3, y4, x1, x2, x3, x4)) { + for {} true {} { + if i32.or(i64.eqz(i64.clz(y1)), gte_256x256_64(y1, y2, y3, y4, x1, x2, x3, x4)) { break } y1, y2, y3, y4 := shl_internal(1, y1, y2, y3, y4) @@ -276,7 +277,7 @@ function div(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { function sdiv(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { // Based on https://github.com/ewasm/evm2wasm/blob/master/wasm/SDIV.wast - let sign := i64.shr_u(i64.xor(x1, y1), 63) + let sign:i32 := i32.wrap_i64(i64.shr_u(i64.xor(x1, y1), 63)) if i64.eqz(i64.clz(x1)) { x1, x2, x3, x4 := xor( @@ -320,8 +321,8 @@ function mod(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { let m3 := 0 let m4 := 1 - for {} 1 {} { - if i64.or(i64.eqz(i64.clz(y1)), gte_256x256_64(y1, y2, y3, y4, r1, r2, r3, r4)) { + for {} true {} { + if i32.or(i64.eqz(i64.clz(y1)), gte_256x256_64(y1, y2, y3, y4, r1, r2, r3, r4)) { break } @@ -356,8 +357,8 @@ function mod320(x1, x2, x3, x4, x5, y1, y2, y3, y4, y5) -> r1, r2, r3, r4, r5 { r4 := x4 r5 := x5 - for {} 1 {} { - if i64.or(i64.eqz(i64.clz(y1)), gte_320x320_64(y1, y2, y3, y4, y5, r1, r2, r3, r4, r5)) { + for {} true {} { + if i32.or(i64.eqz(i64.clz(y1)), gte_320x320_64(y1, y2, y3, y4, y5, r1, r2, r3, r4, r5)) { break } y1, y2, y3, y4, y5 := shl320_internal(1, y1, y2, y3, y4, y5) @@ -397,8 +398,8 @@ function mod512(x1, x2, x3, x4, x5, x6, x7, x8, y1, y2, y3, y4, y5, y6, y7, y8) r7 := x7 r8 := x8 - for {} 1 {} { - if i64.or( + for {} true {} { + if i32.or( i64.eqz(i64.clz(y1)), gte_512x512_64(y1, y2, y3, y4, y5, y6, y7, y8, r1, r2, r3, r4, r5, r6, r7, r8) ) @@ -425,7 +426,7 @@ function smod(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { let m3 := 0 let m4 := 1 - let sign := i64.shr_u(x1, 63) + let sign:i32 := i32.wrap_i64(i64.shr_u(x1, 63)) if i64.eqz(i64.clz(x1)) { x1, x2, x3, x4 := xor( @@ -456,7 +457,7 @@ function smod(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { function exp(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { r4 := 1 for {} or_bool(y1, y2, y3, y4) {} { - if i64.and(y4, 1) { + if i32.wrap_i64(i64.and(y4, 1)) { r1, r2, r3, r4 := mul(r1, r2, r3, r4, x1, x2, x3, x4) } x1, x2, x3, x4 := mul(x1, x2, x3, x4, x1, x2, x3, x4) @@ -499,17 +500,17 @@ function not(x1, x2, x3, x4) -> r1, r2, r3, r4 { let mask := 0xffffffffffffffff r1, r2, r3, r4 := xor(x1, x2, x3, x4, mask, mask, mask, mask) } -function iszero(x1, x2, x3, x4) -> r1, r2, r3 ,r4 { - r4 := iszero256(x1, x2, x3, x4) +function iszero(x1, x2, x3, x4) -> r1, r2, r3, r4 { + r4 := i64.extend_i32_u(iszero256(x1, x2, x3, x4)) } -function iszero256(x1, x2, x3, x4) -> r { +function iszero256(x1, x2, x3, x4) -> r:i32 { r := i64.eqz(i64.or(i64.or(x1, x2), i64.or(x3, x4))) } -function iszero320(x1, x2, x3, x4, x5) -> r { +function iszero320(x1, x2, x3, x4, x5) -> r:i32 { r := i64.eqz(i64.or(i64.or(i64.or(x1, x2), i64.or(x3, x4)), x5)) } -function iszero512(x1, x2, x3, x4, x5, x6, x7, x8) -> r { - r := i64.and(iszero256(x1, x2, x3, x4), iszero256(x5, x6, x7, x8)) +function iszero512(x1, x2, x3, x4, x5, x6, x7, x8) -> r:i32 { + r := i32.and(iszero256(x1, x2, x3, x4), iszero256(x5, x6, x7, x8)) } function eq(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { if i64.eq(x1, y1) { @@ -524,107 +525,108 @@ function eq(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 { } // returns 0 if a == b, -1 if a < b and 1 if a > b -function cmp(a, b) -> r { +function cmp(a, b) -> r:i32 { switch i64.lt_u(a, b) - case 1 { r := 0xffffffffffffffff } + case 1:i32 { r := 0xffffffff:i32 } default { r := i64.ne(a, b) } } -function lt_320x320_64(x1, x2, x3, x4, x5, y1, y2, y3, y4, y5) -> z { +function lt_320x320_64(x1, x2, x3, x4, x5, y1, y2, y3, y4, y5) -> z:i32 { switch cmp(x1, y1) - case 0 { + case 0:i32 { switch cmp(x2, y2) - case 0 { + case 0:i32 { switch cmp(x3, y3) - case 0 { + case 0:i32 { switch cmp(x4, y4) - case 0 { + case 0:i32 { z := i64.lt_u(x5, y5) } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } -function lt_512x512_64(x1, x2, x3, x4, x5, x6, x7, x8, y1, y2, y3, y4, y5, y6, y7, y8) -> z { +function lt_512x512_64(x1, x2, x3, x4, x5, x6, x7, x8, y1, y2, y3, y4, y5, y6, y7, y8) -> z:i32 { switch cmp(x1, y1) - case 0 { + case 0:i32 { switch cmp(x2, y2) - case 0 { + case 0:i32 { switch cmp(x3, y3) - case 0 { + case 0:i32 { switch cmp(x4, y4) - case 0 { + case 0:i32 { switch cmp(x5, y5) - case 0 { + case 0:i32 { switch cmp(x6, y6) - case 0 { + case 0:i32 { switch cmp(x7, y7) - case 0 { + case 0:i32 { z := i64.lt_u(x8, y8) } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } -} + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } +}/* )" // Split long string to make it compilable on msvc // https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2026?view=vs-2019 R"( -function lt_256x256_64(x1, x2, x3, x4, y1, y2, y3, y4) -> z { +*/ +function lt_256x256_64(x1, x2, x3, x4, y1, y2, y3, y4) -> z:i32 { switch cmp(x1, y1) - case 0 { + case 0:i32 { switch cmp(x2, y2) - case 0 { + case 0:i32 { switch cmp(x3, y3) - case 0 { + case 0:i32 { z := i64.lt_u(x4, y4) } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } - case 1 { z := 0 } - default { z := 1 } + case 1:i32 { z := 0:i32 } + default { z := 1:i32 } } function lt(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 { - z4 := lt_256x256_64(x1, x2, x3, x4, y1, y2, y3, y4) + z4 := i64.extend_i32_u(lt_256x256_64(x1, x2, x3, x4, y1, y2, y3, y4)) } -function gte_256x256_64(x1, x2, x3, x4, y1, y2, y3, y4) -> z { - z := i64.eqz(lt_256x256_64(x1, x2, x3, x4, y1, y2, y3, y4)) +function gte_256x256_64(x1, x2, x3, x4, y1, y2, y3, y4) -> z:i32 { + z := i32.eqz(lt_256x256_64(x1, x2, x3, x4, y1, y2, y3, y4)) } -function gte_320x320_64(x1, x2, x3, x4, x5, y1, y2, y3, y4, y5) -> z { - z := i64.eqz(lt_320x320_64(x1, x2, x3, x4, x5, y1, y2, y3, y4, y5)) +function gte_320x320_64(x1, x2, x3, x4, x5, y1, y2, y3, y4, y5) -> z:i32 { + z := i32.eqz(lt_320x320_64(x1, x2, x3, x4, x5, y1, y2, y3, y4, y5)) } -function gte_512x512_64(x1, x2, x3, x4, x5, x6, x7, x8, y1, y2, y3, y4, y5, y6, y7, y8) -> z { - z := i64.eqz(lt_512x512_64(x1, x2, x3, x4, x5, x6, x7, x8, y1, y2, y3, y4, y5, y6, y7, y8)) +function gte_512x512_64(x1, x2, x3, x4, x5, x6, x7, x8, y1, y2, y3, y4, y5, y6, y7, y8) -> z:i32 { + z := i32.eqz(lt_512x512_64(x1, x2, x3, x4, x5, x6, x7, x8, y1, y2, y3, y4, y5, y6, y7, y8)) } function gt(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 { z1, z2, z3, z4 := lt(y1, y2, y3, y4, x1, x2, x3, x4) @@ -646,7 +648,7 @@ function shl_single(a, amount) -> x, y { } function shl(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 { - if i64.and(i64.eqz(x1), i64.eqz(x2)) { + if i32.and(i64.eqz(x1), i64.eqz(x2)) { if i64.eqz(x3) { if i64.lt_u(x4, 256) { if i64.ge_u(x4, 128) { @@ -683,7 +685,7 @@ function shr_single(a, amount) -> x, y { } function shr(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 { - if i64.and(i64.eqz(x1), i64.eqz(x2)) { + if i32.and(i64.eqz(x1), i64.eqz(x2)) { if i64.eqz(x3) { if i64.lt_u(x4, 256) { if i64.ge_u(x4, 128) { @@ -713,7 +715,7 @@ function shr(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 { } } function sar(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 { - if i64.clz(y1) { + if i64.gt_u(i64.clz(y1), 0) { z1, z2, z3, z4 := shr(x1, x2, x3, x4, y1, y2, y3, y4) leave } @@ -773,10 +775,10 @@ function u256_to_i64(x1, x2, x3, x4) -> v { v := x4 } -function u256_to_i32(x1, x2, x3, x4) -> v { +function u256_to_i32(x1, x2, x3, x4) -> v:i32 { if i64.ne(0, i64.or(i64.or(x1, x2), x3)) { invalid() } if i64.ne(0, i64.shr_u(x4, 32)) { invalid() } - v := x4 + v := i32.wrap_i64(x4) } function u256_to_byte(x1, x2, x3, x4) -> v { @@ -785,14 +787,14 @@ function u256_to_byte(x1, x2, x3, x4) -> v { v := x4 } -function u256_to_i32ptr(x1, x2, x3, x4) -> v { +function u256_to_i32ptr(x1, x2, x3, x4) -> v:i32 { v := u256_to_i32(x1, x2, x3, x4) } -function to_internal_i32ptr(x1, x2, x3, x4) -> r { - let p := u256_to_i32ptr(x1, x2, x3, x4) - r := i64.add(p, 64) - if i64.lt_u(r, p) { invalid() } +function to_internal_i32ptr(x1, x2, x3, x4) -> r:i32 { + let p:i32 := u256_to_i32ptr(x1, x2, x3, x4) + r := i32.add(p, 64:i32) + if i32.lt_u(r, p) { invalid() } } function u256_to_address(x1, x2, x3, x4) -> r1, r2, r3 { @@ -809,13 +811,13 @@ function keccak256(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 { } function address() -> z1, z2, z3, z4 { - eth.getAddress(0) - z1, z2, z3, z4 := mload_internal(0) + eth.getAddress(0:i32) + z1, z2, z3, z4 := mload_internal(0:i32) } function balance(x1, x2, x3, x4) -> z1, z2, z3, z4 { - mstore_address(0, x1, x2, x3, x4) - eth.getExternalBalance(12, 48) - z1, z2, z3, z4 := mload_internal(32) + mstore_address(0:i32, x1, x2, x3, x4) + eth.getExternalBalance(12:i32, 48:i32) + z1, z2, z3, z4 := mload_internal(32:i32) } function selfbalance() -> z1, z2, z3, z4 { // TODO: not part of current Ewasm spec @@ -826,23 +828,23 @@ function chainid() -> z1, z2, z3, z4 { unreachable() } function origin() -> z1, z2, z3, z4 { - eth.getTxOrigin(0) - z1, z2, z3, z4 := mload_internal(0) + eth.getTxOrigin(0:i32) + z1, z2, z3, z4 := mload_internal(0:i32) } function caller() -> z1, z2, z3, z4 { - eth.getCaller(0) - z1, z2, z3, z4 := mload_internal(0) + eth.getCaller(0:i32) + z1, z2, z3, z4 := mload_internal(0:i32) } function callvalue() -> z1, z2, z3, z4 { - eth.getCallValue(0) - z1, z2, z3, z4 := mload_internal(0) + eth.getCallValue(0:i32) + z1, z2, z3, z4 := mload_internal(0:i32) } function calldataload(x1, x2, x3, x4) -> z1, z2, z3, z4 { - eth.callDataCopy(0, u256_to_i32(x1, x2, x3, x4), 32) - z1, z2, z3, z4 := mload_internal(0) + eth.callDataCopy(0:i32, u256_to_i32(x1, x2, x3, x4), 32:i32) + z1, z2, z3, z4 := mload_internal(0:i32) } function calldatasize() -> z1, z2, z3, z4 { - z4 := eth.getCallDataSize() + z4 := i64.extend_i32_u(eth.getCallDataSize()) } function calldatacopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) { eth.callDataCopy( @@ -854,7 +856,7 @@ function calldatacopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) { // Needed? function codesize() -> z1, z2, z3, z4 { - z4 := eth.getCodeSize() + z4 := i64.extend_i32_u(eth.getCodeSize()) } function codecopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) { eth.codeCopy( @@ -869,29 +871,29 @@ function datacopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) { } function gasprice() -> z1, z2, z3, z4 { - eth.getTxGasPrice(0) - z1, z2, z3, z4 := mload_internal(0) + eth.getTxGasPrice(0:i32) + z1, z2, z3, z4 := mload_internal(0:i32) } -function extcodesize_internal(x1, x2, x3, x4) -> r { - mstore_address(0, x1, x2, x3, x4) - r := eth.getExternalCodeSize(12) +function extcodesize_internal(x1, x2, x3, x4) -> r:i32 { + mstore_address(0:i32, x1, x2, x3, x4) + r := eth.getExternalCodeSize(12:i32) } function extcodesize(x1, x2, x3, x4) -> z1, z2, z3, z4 { - z4 := extcodesize_internal(x1, x2, x3, x4) + z4 := i64.extend_i32_u(extcodesize_internal(x1, x2, x3, x4)) } function extcodehash(x1, x2, x3, x4) -> z1, z2, z3, z4 { // TODO: not part of current Ewasm spec unreachable() } function extcodecopy(a1, a2, a3, a4, p1, p2, p3, p4, o1, o2, o3, o4, l1, l2, l3, l4) { - mstore_address(0, a1, a2, a3, a4) - let codeOffset := u256_to_i32(o1, o2, o3, o4) - let codeLength := u256_to_i32(l1, l2, l3, l4) - eth.externalCodeCopy(12, to_internal_i32ptr(p1, p2, p3, p4), codeOffset, codeLength) + mstore_address(0:i32, a1, a2, a3, a4) + let codeOffset:i32 := u256_to_i32(o1, o2, o3, o4) + let codeLength:i32 := u256_to_i32(l1, l2, l3, l4) + eth.externalCodeCopy(12:i32, to_internal_i32ptr(p1, p2, p3, p4), codeOffset, codeLength) } function returndatasize() -> z1, z2, z3, z4 { - z4 := eth.getReturnDataSize() + z4 := i64.extend_i32_u(eth.getReturnDataSize()) } function returndatacopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) { eth.returnDataCopy( @@ -902,14 +904,14 @@ function returndatacopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) { } function blockhash(x1, x2, x3, x4) -> z1, z2, z3, z4 { - let r := eth.getBlockHash(u256_to_i64(x1, x2, x3, x4), 0) - if i64.eqz(r) { - z1, z2, z3, z4 := mload_internal(0) + let r:i32 := eth.getBlockHash(u256_to_i64(x1, x2, x3, x4), 0:i32) + if i32.eqz(r) { + z1, z2, z3, z4 := mload_internal(0:i32) } } function coinbase() -> z1, z2, z3, z4 { - eth.getBlockCoinbase(0) - z1, z2, z3, z4 := mload_internal(0) + eth.getBlockCoinbase(0:i32) + z1, z2, z3, z4 := mload_internal(0:i32) } function timestamp() -> z1, z2, z3, z4 { z4 := eth.getBlockTimestamp() @@ -918,8 +920,8 @@ function number() -> z1, z2, z3, z4 { z4 := eth.getBlockNumber() } function difficulty() -> z1, z2, z3, z4 { - eth.getBlockDifficulty(0) - z1, z2, z3, z4 := mload_internal(0) + eth.getBlockDifficulty(0:i32) + z1, z2, z3, z4 := mload_internal(0:i32) } function gaslimit() -> z1, z2, z3, z4 { z4 := eth.getBlockGasLimit() @@ -947,56 +949,56 @@ function endian_swap(x) -> y { y := i64.or(hi, lo) } function save_temp_mem_32() -> t1, t2, t3, t4 { - t1 := i64.load(0) - t2 := i64.load(8) - t3 := i64.load(16) - t4 := i64.load(24) + t1 := i64.load(0:i32) + t2 := i64.load(8:i32) + t3 := i64.load(16:i32) + t4 := i64.load(24:i32) } function restore_temp_mem_32(t1, t2, t3, t4) { - i64.store(0, t1) - i64.store(8, t2) - i64.store(16, t3) - i64.store(24, t4) + i64.store(0:i32, t1) + i64.store(8:i32, t2) + i64.store(16:i32, t3) + i64.store(24:i32, t4) } function save_temp_mem_64() -> t1, t2, t3, t4, t5, t6, t7, t8 { - t1 := i64.load(0) - t2 := i64.load(8) - t3 := i64.load(16) - t4 := i64.load(24) - t5 := i64.load(32) - t6 := i64.load(40) - t7 := i64.load(48) - t8 := i64.load(54) + t1 := i64.load(0:i32) + t2 := i64.load(8:i32) + t3 := i64.load(16:i32) + t4 := i64.load(24:i32) + t5 := i64.load(32:i32) + t6 := i64.load(40:i32) + t7 := i64.load(48:i32) + t8 := i64.load(54:i32) } function restore_temp_mem_64(t1, t2, t3, t4, t5, t6, t7, t8) { - i64.store(0, t1) - i64.store(8, t2) - i64.store(16, t3) - i64.store(24, t4) - i64.store(32, t5) - i64.store(40, t6) - i64.store(48, t7) - i64.store(54, t8) + i64.store(0:i32, t1) + i64.store(8:i32, t2) + i64.store(16:i32, t3) + i64.store(24:i32, t4) + i64.store(32:i32, t5) + i64.store(40:i32, t6) + i64.store(48:i32, t7) + i64.store(54:i32, t8) } function mload(x1, x2, x3, x4) -> z1, z2, z3, z4 { z1, z2, z3, z4 := mload_internal(to_internal_i32ptr(x1, x2, x3, x4)) } -function mload_internal(pos) -> z1, z2, z3, z4 { +function mload_internal(pos:i32) -> z1, z2, z3, z4 { z1 := endian_swap(i64.load(pos)) - z2 := endian_swap(i64.load(i64.add(pos, 8))) - z3 := endian_swap(i64.load(i64.add(pos, 16))) - z4 := endian_swap(i64.load(i64.add(pos, 24))) + z2 := endian_swap(i64.load(i32.add(pos, 8:i32))) + z3 := endian_swap(i64.load(i32.add(pos, 16:i32))) + z4 := endian_swap(i64.load(i32.add(pos, 24:i32))) } function mstore(x1, x2, x3, x4, y1, y2, y3, y4) { mstore_internal(to_internal_i32ptr(x1, x2, x3, x4), y1, y2, y3, y4) } -function mstore_internal(pos, y1, y2, y3, y4) { +function mstore_internal(pos:i32, y1, y2, y3, y4) { i64.store(pos, endian_swap(y1)) - i64.store(i64.add(pos, 8), endian_swap(y2)) - i64.store(i64.add(pos, 16), endian_swap(y3)) - i64.store(i64.add(pos, 24), endian_swap(y4)) + i64.store(i32.add(pos, 8:i32), endian_swap(y2)) + i64.store(i32.add(pos, 16:i32), endian_swap(y3)) + i64.store(i32.add(pos, 24:i32), endian_swap(y4)) } -function mstore_address(pos, a1, a2, a3, a4) { +function mstore_address(pos:i32, a1, a2, a3, a4) { a1, a2, a3 := u256_to_address(a1, a2, a3, a4) mstore_internal(pos, 0, a1, a2, a3) } @@ -1010,15 +1012,15 @@ function msize() -> z1, z2, z3, z4 { unreachable() } function sload(x1, x2, x3, x4) -> z1, z2, z3, z4 { - mstore_internal(0, x1, x2, x3, x4) - eth.storageLoad(0, 32) - z1, z2, z3, z4 := mload_internal(32) + mstore_internal(0:i32, x1, x2, x3, x4) + eth.storageLoad(0:i32, 32:i32) + z1, z2, z3, z4 := mload_internal(32:i32) } function sstore(x1, x2, x3, x4, y1, y2, y3, y4) { - mstore_internal(0, x1, x2, x3, x4) - mstore_internal(32, y1, y2, y3, y4) - eth.storageStore(0, 32) + mstore_internal(0:i32, x1, x2, x3, x4) + mstore_internal(32:i32, y1, y2, y3, y4) + eth.storageStore(0:i32, 32:i32) } // Needed? @@ -1034,7 +1036,7 @@ function log0(p1, p2, p3, p4, s1, s2, s3, s4) { eth.log( to_internal_i32ptr(p1, p2, p3, p4), u256_to_i32(s1, s2, s3, s4), - 0, 0, 0, 0, 0 + 0:i32, 0:i32, 0:i32, 0:i32, 0:i32 ) } function log1( @@ -1044,9 +1046,9 @@ function log1( eth.log( to_internal_i32ptr(p1, p2, p3, p4), u256_to_i32(s1, s2, s3, s4), - 1, + 1:i32, to_internal_i32ptr(t1_1, t1_2, t1_3, t1_4), - 0, 0, 0 + 0:i32, 0:i32, 0:i32 ) } function log2( @@ -1057,10 +1059,10 @@ function log2( eth.log( to_internal_i32ptr(p1, p2, p3, p4), u256_to_i32(s1, s2, s3, s4), - 2, + 2:i32, to_internal_i32ptr(t1_1, t1_2, t1_3, t1_4), to_internal_i32ptr(t2_1, t2_2, t2_3, t2_4), - 0, 0 + 0:i32, 0:i32 ) } function log3( @@ -1072,11 +1074,11 @@ function log3( eth.log( to_internal_i32ptr(p1, p2, p3, p4), u256_to_i32(s1, s2, s3, s4), - 3, + 3:i32, to_internal_i32ptr(t1_1, t1_2, t1_3, t1_4), to_internal_i32ptr(t2_1, t2_2, t2_3, t2_4), to_internal_i32ptr(t3_1, t3_2, t3_3, t3_4), - 0 + 0:i32 ) } function log4( @@ -1089,7 +1091,7 @@ function log4( eth.log( to_internal_i32ptr(p1, p2, p3, p4), u256_to_i32(s1, s2, s3, s4), - 4, + 4:i32, to_internal_i32ptr(t1_1, t1_2, t1_3, t1_4), to_internal_i32ptr(t2_1, t2_2, t2_3, t2_4), to_internal_i32ptr(t3_1, t3_2, t3_3, t3_4), @@ -1103,11 +1105,11 @@ function create( z1, z2, z3, z4 ) -> a1, a2, a3, a4 { let v1, v2 := u256_to_u128(x1, x2, x3, x4) - mstore_internal(0, 0, 0, v1, v2) + mstore_internal(0:i32, 0, 0, v1, v2) - let r := eth.create(0, to_internal_i32ptr(y1, y2, y3, y4), u256_to_i32(z1, z2, z3, z4), 32) - if i64.eqz(r) { - a1, a2, a3, a4 := mload_internal(32) + let r:i32 := eth.create(0:i32, to_internal_i32ptr(y1, y2, y3, y4), u256_to_i32(z1, z2, z3, z4), 32:i32) + if i32.eqz(r) { + a1, a2, a3, a4 := mload_internal(32:i32) } } function call( @@ -1120,12 +1122,12 @@ function call( g1, g2, g3, g4 ) -> x1, x2, x3, x4 { let g := u256_to_i64(a1, a2, a3, a4) - mstore_address(0, b1, b2, b3, b4) + mstore_address(0:i32, b1, b2, b3, b4) let v1, v2 := u256_to_u128(c1, c2, c3, c4) - mstore_internal(32, 0, 0, v1, v2) + mstore_internal(32:i32, 0, 0, v1, v2) - x4 := eth.call(g, 12, 32, to_internal_i32ptr(d1, d2, d3, d4), u256_to_i32(e1, e2, e3, e4)) + x4 := i64.extend_i32_u(eth.call(g, 12:i32, 32:i32, to_internal_i32ptr(d1, d2, d3, d4), u256_to_i32(e1, e2, e3, e4))) } function callcode( a1, a2, a3, a4, @@ -1136,18 +1138,18 @@ function callcode( f1, f2, f3, f4, g1, g2, g3, g4 ) -> x1, x2, x3, x4 { - mstore_address(0, b1, b2, b3, b4) + mstore_address(0:i32, b1, b2, b3, b4) let v1, v2 := u256_to_u128(c1, c2, c3, c4) - mstore_internal(32, 0, 0, v1, v2) + mstore_internal(32:i32, 0, 0, v1, v2) - x4 := eth.callCode( + x4 := i64.extend_i32_u(eth.callCode( u256_to_i64(a1, a2, a3, a4), - 12, - 32, + 12:i32, + 32:i32, to_internal_i32ptr(d1, d2, d3, d4), u256_to_i32(e1, e2, e3, e4) - ) + )) } function delegatecall( a1, a2, a3, a4, @@ -1157,14 +1159,14 @@ function delegatecall( e1, e2, e3, e4, f1, f2, f3, f4 ) -> x1, x2, x3, x4 { - mstore_address(0, b1, b2, b3, b4) + mstore_address(0:i32, b1, b2, b3, b4) - x4 := eth.callDelegate( + x4 := i64.extend_i32_u(eth.callDelegate( u256_to_i64(a1, a2, a3, a4), - 12, + 12:i32, to_internal_i32ptr(c1, c2, c3, c4), u256_to_i32(d1, d2, d3, d4) - ) + )) } function staticcall( a1, a2, a3, a4, @@ -1174,14 +1176,14 @@ function staticcall( e1, e2, e3, e4, f1, f2, f3, f4 ) -> x1, x2, x3, x4 { - mstore_address(0, b1, b2, b3, b4) + mstore_address(0:i32, b1, b2, b3, b4) - x4 := eth.callStatic( + x4 := i64.extend_i32_u(eth.callStatic( u256_to_i64(a1, a2, a3, a4), - 12, + 12:i32, to_internal_i32ptr(c1, c2, c3, c4), u256_to_i32(d1, d2, d3, d4) - ) + )) } function create2( a1, a2, a3, a4, @@ -1193,9 +1195,9 @@ function create2( unreachable() } function selfdestruct(a1, a2, a3, a4) { - mstore_address(0, a1, a2, a3, a4) + mstore_address(0:i32, a1, a2, a3, a4) // In EVM, addresses are padded to 32 bytes, so discard the first 12. - eth.selfDestruct(12) + eth.selfDestruct(12:i32) } function return(x1, x2, x3, x4, y1, y2, y3, y4) { @@ -1213,7 +1215,8 @@ function revert(x1, x2, x3, x4, y1, y2, y3, y4) { function invalid() { unreachable() } -})"}; +} +)"}; } diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index 896059870..947326306 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -20,70 +20,87 @@ #include +#include + using namespace std; using namespace solidity::yul; WasmDialect::WasmDialect() { - defaultType = "i64"_yulstring; - boolType = "i64"_yulstring; - types = {"i64"_yulstring, "i32"_yulstring}; + YulString i64 = "i64"_yulstring; + YulString i32 = "i32"_yulstring; + defaultType = i64; + boolType = i32; + types = {i64, i32}; - for (auto const& name: { - "i64.add", - "i64.sub", - "i64.mul", - "i64.div_u", - "i64.rem_u", - "i64.and", - "i64.or", - "i64.xor", - "i64.shl", - "i64.shr_u", - "i64.eq", - "i64.ne", - "i64.lt_u", - "i64.gt_u", - "i64.le_u", - "i64.ge_u" - }) - addFunction(name, 2, 1); + for (auto t: types) + for (auto const& name: { + "add", + "sub", + "mul", + "div_u", + "rem_u", + "and", + "or", + "xor", + "shl", + "shr_u", + }) + addFunction(t.str() + "." + name, {t, t}, {t}); - m_functions["i64.lt_u"_yulstring].returns.front() = "i32"_yulstring; - m_functions["i64.gt_u"_yulstring].returns.front() = "i32"_yulstring; - m_functions["i64.le_u"_yulstring].returns.front() = "i32"_yulstring; - m_functions["i64.ge_u"_yulstring].returns.front() = "i32"_yulstring; - m_functions["i64.eq"_yulstring].returns.front() = "i32"_yulstring; - m_functions["i64.ne"_yulstring].returns.front() = "i32"_yulstring; + for (auto t: types) + for (auto const& name: { + "eq", + "ne", + "lt_u", + "gt_u", + "le_u", + "ge_u" + }) + addFunction(t.str() + "." + name, {t, t}, {i32}); - addFunction("i64.eqz", 1, 1); - m_functions["i64.eqz"_yulstring].returns.front() = "i32"_yulstring; + addFunction("i32.eqz", {i32}, {i32}); + addFunction("i64.eqz", {i64}, {i32}); - addFunction("i64.clz", 1, 1); + addFunction("i32.clz", {i32}, {i32}); + addFunction("i64.clz", {i64}, {i64}); - addFunction("i64.store", 2, 0, false); - m_functions["i64.store"_yulstring].parameters.front() = "i32"_yulstring; + addFunction("i32.wrap_i64", {i64}, {i32}); + + addFunction("i64.extend_i32_u", {i32}, {i64}); + + addFunction("i32.store", {i32, i32}, {}, false); + m_functions["i32.store"_yulstring].sideEffects.invalidatesStorage = false; + addFunction("i64.store", {i32, i64}, {}, false); m_functions["i64.store"_yulstring].sideEffects.invalidatesStorage = false; - addFunction("i64.store8", 2, 0, false); - m_functions["i64.store8"_yulstring].parameters.front() = "i32"_yulstring; + addFunction("i32.store8", {i32, i32}, {}, false); + m_functions["i32.store8"_yulstring].sideEffects.invalidatesStorage = false; + addFunction("i64.store8", {i32, i64}, {}, false); m_functions["i64.store8"_yulstring].sideEffects.invalidatesStorage = false; - addFunction("i64.load", 1, 1, false); - m_functions["i64.load"_yulstring].parameters.front() = "i32"_yulstring; + addFunction("i32.load", {i32}, {i32}, false); + m_functions["i32.load"_yulstring].sideEffects.invalidatesStorage = false; + m_functions["i32.load"_yulstring].sideEffects.invalidatesMemory = false; + m_functions["i32.load"_yulstring].sideEffects.sideEffectFree = true; + m_functions["i32.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true; + addFunction("i64.load", {i32}, {i64}, false); m_functions["i64.load"_yulstring].sideEffects.invalidatesStorage = false; m_functions["i64.load"_yulstring].sideEffects.invalidatesMemory = false; m_functions["i64.load"_yulstring].sideEffects.sideEffectFree = true; m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true; - addFunction("drop", 1, 0); + // Drop is actually overloaded for all types, but Yul does not support that. + // We could introduce "i32.drop". + addFunction("drop", {i64}, {}); - addFunction("unreachable", 0, 0, false); + addFunction("nop", {}, {}); + addFunction("unreachable", {}, {}, false); m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false; m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false; - addFunction("datasize", 1, 1, true, true); - addFunction("dataoffset", 1, 1, true, true); + addFunction("datasize", {i64}, {i64}, true, true); + addFunction("dataoffset", {i64}, {i64}, true, true); addEthereumExternals(); } @@ -167,8 +184,8 @@ void WasmDialect::addEthereumExternals() void WasmDialect::addFunction( string _name, - size_t _params, - size_t _returns, + vector _params, + vector _returns, bool _movable, bool _literalArguments ) @@ -176,8 +193,9 @@ void WasmDialect::addFunction( YulString name{move(_name)}; BuiltinFunction& f = m_functions[name]; f.name = name; - f.parameters.resize(_params); - f.returns.resize(_returns); + f.parameters = std::move(_params); + yulAssert(_returns.size() <= 1, "The Wasm 1.0 specification only allows up to 1 return value."); + f.returns = std::move(_returns); f.sideEffects = _movable ? SideEffects{} : SideEffects::worst(); f.isMSize = false; f.literalArguments = _literalArguments; diff --git a/libyul/backends/wasm/WasmDialect.h b/libyul/backends/wasm/WasmDialect.h index 66b70864e..cf1a76d9d 100644 --- a/libyul/backends/wasm/WasmDialect.h +++ b/libyul/backends/wasm/WasmDialect.h @@ -56,7 +56,13 @@ struct WasmDialect: public Dialect private: void addEthereumExternals(); - void addFunction(std::string _name, size_t _params, size_t _returns, bool _movable = true, bool _literalArguments = false); + void addFunction( + std::string _name, + std::vector _params, + std::vector _returns, + bool _movable = true, + bool _literalArguments = false + ); std::map m_functions; }; diff --git a/test/cmdlineTests/evm_to_wasm/output b/test/cmdlineTests/evm_to_wasm/output index 1c31f9488..4e83eea9d 100644 --- a/test/cmdlineTests/evm_to_wasm/output +++ b/test/cmdlineTests/evm_to_wasm/output @@ -15,9 +15,9 @@ object "object" { function main() { let _1 := 0 - mstore_internal(_1, _1, _1, _1, _1) - mstore_internal(32, _1, _1, _1, 1) - eth.storageStore(_1, 32) + mstore_internal(0:i32, _1, _1, _1, _1) + mstore_internal(32:i32, _1, _1, _1, 1) + eth.storageStore(0:i32, 32:i32) } function endian_swap_16(x) -> y { @@ -33,19 +33,19 @@ object "object" { let hi := i64.shl(endian_swap_32(x), 32) y := i64.or(hi, endian_swap_32(i64.shr_u(x, 32))) } - function mstore_internal(pos, y1, y2, y3, y4) + function mstore_internal(pos:i32, y1, y2, y3, y4) { i64.store(pos, endian_swap(y1)) - i64.store(i64.add(pos, 8), endian_swap(y2)) - i64.store(i64.add(pos, 16), endian_swap(y3)) - i64.store(i64.add(pos, 24), endian_swap(y4)) + i64.store(i32.add(pos, 8:i32), endian_swap(y2)) + i64.store(i32.add(pos, 16:i32), endian_swap(y3)) + i64.store(i32.add(pos, 24:i32), endian_swap(y4)) } } } Binary representation: -0061736d0100000001160460000060017e017e60057e7e7e7e7e0060027f7f0002190108657468657265756d0c73746f7261676553746f7265000303060500010101020503010001060100071102066d656d6f72790200046d61696e00010ab501052801017e420021002000200020002000200010054220200020002000420110052000a74220a710000b1c01017e20004208864280fe0383200042088842ff018384210120010b1b01027e20001002421086210220022000421088100284210120010b1b01027e20001003422086210220022000422088100384210120010b3501007e2000a720011004370300200042087ca720021004370300200042107ca720031004370300200042187ca7200410043703000b +0061736d0100000001160460000060017e017e60057e7e7e7e7e0060027f7f0002190108657468657265756d0c73746f7261676553746f7265000303060500010101020503010001060100071102066d656d6f72790200046d61696e00010abe01052801017e420021004200200020002000200010054220200020002000420110054200a74220a710000b1c01017e20004208864280fe0383200042088842ff018384210120010b1b01027e20001002421086210220022000421088100284210120010b1b01027e20001003422086210220022000422088100384210120010b3e01007e2000a7200110043703002000a74208a76aada7200210043703002000a74210a76aada7200310043703002000a74218a76aada7200410043703000b Text representation: (module @@ -56,9 +56,9 @@ Text representation: (func $main (local $_1 i64) (local.set $_1 (i64.const 0)) - (call $mstore_internal (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) + (call $mstore_internal (i64.const 0) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (call $mstore_internal (i64.const 32) (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 1)) - (call $eth.storageStore (i32.wrap_i64 (local.get $_1)) (i32.wrap_i64 (i64.const 32))) + (call $eth.storageStore (i32.wrap_i64 (i64.const 0)) (i32.wrap_i64 (i64.const 32))) ) (func $endian_swap_16 @@ -96,9 +96,9 @@ Text representation: (param $y3 i64) (param $y4 i64) (i64.store (i32.wrap_i64 (local.get $pos)) (call $endian_swap (local.get $y1))) - (i64.store (i32.wrap_i64 (i64.add (local.get $pos) (i64.const 8))) (call $endian_swap (local.get $y2))) - (i64.store (i32.wrap_i64 (i64.add (local.get $pos) (i64.const 16))) (call $endian_swap (local.get $y3))) - (i64.store (i32.wrap_i64 (i64.add (local.get $pos) (i64.const 24))) (call $endian_swap (local.get $y4))) + (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $pos)) (i32.wrap_i64 (i64.const 8))))) (call $endian_swap (local.get $y2))) + (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $pos)) (i32.wrap_i64 (i64.const 16))))) (call $endian_swap (local.get $y3))) + (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $pos)) (i32.wrap_i64 (i64.const 24))))) (call $endian_swap (local.get $y4))) ) ) diff --git a/test/cmdlineTests/standard_eWasm_requested/output.json b/test/cmdlineTests/standard_eWasm_requested/output.json index d273d0b85..0d796bce5 100644 --- a/test/cmdlineTests/standard_eWasm_requested/output.json +++ b/test/cmdlineTests/standard_eWasm_requested/output.json @@ -10,21 +10,23 @@ (local $p i64) (local $r i64) (local $hi i64) - (local $y i64) (local $hi_1 i64) + (local $y i64) + (local $hi_2 i64) (local $_2 i64) (local.set $_1 (i64.const 0)) (local.set $p (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 64))) - (local.set $r (i64.add (local.get $p) (i64.const 64))) - (if (i64.ne (i64.extend_i32_u (i64.lt_u (local.get $r) (local.get $p))) (i64.const 0)) (then + (local.set $r (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $p)) (i32.wrap_i64 (i64.const 64))))) + (if (i64.ne (i64.extend_i32_u (i32.lt_u (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (local.get $p)))) (i64.const 0)) (then (unreachable))) - (local.set $hi (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (local.get $_1) (i64.const 8)) (i64.const 65280)) (i64.and (i64.shr_u (local.get $_1) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (local.get $_1) (i64.const 16)))) (i64.const 32))) - (local.set $y (i64.or (local.get $hi) (call $endian_swap_32 (i64.shr_u (local.get $_1) (i64.const 32))))) + (local.set $hi (i64.shl (call $endian_swap_16 (local.get $_1)) (i64.const 16))) + (local.set $hi_1 (i64.shl (i64.or (local.get $hi) (call $endian_swap_16 (i64.shr_u (local.get $_1) (i64.const 16)))) (i64.const 32))) + (local.set $y (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (local.get $_1) (i64.const 32))))) (i64.store (i32.wrap_i64 (local.get $r)) (local.get $y)) - (i64.store (i32.wrap_i64 (i64.add (local.get $r) (i64.const 8))) (local.get $y)) - (i64.store (i32.wrap_i64 (i64.add (local.get $r) (i64.const 16))) (local.get $y)) - (local.set $hi_1 (i64.shl (call $endian_swap_32 (i64.const 128)) (i64.const 32))) - (i64.store (i32.wrap_i64 (i64.add (local.get $r) (i64.const 24))) (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32))))) + (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 8))))) (local.get $y)) + (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 16))))) (local.get $y)) + (local.set $hi_2 (i64.shl (call $endian_swap_32 (i64.const 128)) (i64.const 32))) + (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 24))))) (i64.or (local.get $hi_2) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32))))) (local.set $_2 (datasize \"C_2_deployed\")) (call $eth.codeCopy (i32.wrap_i64 (call $to_internal_i32ptr (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))) (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 $_2)))) (call $eth.finish (i32.wrap_i64 (call $to_internal_i32ptr (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_2)))) @@ -41,7 +43,7 @@ (unreachable))) (if (i64.ne (i64.extend_i32_u (i64.ne (i64.const 0) (i64.shr_u (local.get $x4) (i64.const 32)))) (i64.const 0)) (then (unreachable))) - (local.set $v (local.get $x4)) + (local.set $v (i64.extend_i32_u (i32.wrap_i64 (local.get $x4)))) (local.get $v) ) @@ -54,8 +56,8 @@ (local $r i64) (local $p i64) (local.set $p (call $u256_to_i32 (local.get $x1) (local.get $x2) (local.get $x3) (local.get $x4))) - (local.set $r (i64.add (local.get $p) (i64.const 64))) - (if (i64.ne (i64.extend_i32_u (i64.lt_u (local.get $r) (local.get $p))) (i64.const 0)) (then + (local.set $r (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $p)) (i32.wrap_i64 (i64.const 64))))) + (if (i64.ne (i64.extend_i32_u (i32.lt_u (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (local.get $p)))) (i64.const 0)) (then (unreachable))) (local.get $r) ) diff --git a/test/tools/yulInterpreter/EwasmBuiltinInterpreter.cpp b/test/tools/yulInterpreter/EwasmBuiltinInterpreter.cpp index 7abd43112..e4c190e9c 100644 --- a/test/tools/yulInterpreter/EwasmBuiltinInterpreter.cpp +++ b/test/tools/yulInterpreter/EwasmBuiltinInterpreter.cpp @@ -93,66 +93,25 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a ); return 0; } - else if (_fun == "drop"_yulstring) + else if (_fun == "drop"_yulstring || _fun == "nop"_yulstring) return {}; + else if (_fun == "i32.wrap_i64"_yulstring) + return arg.at(0) & uint32_t(-1); + else if (_fun == "i64.extend_i32_u"_yulstring) + // Return the same as above because everything is u256 anyway. + return arg.at(0) & uint32_t(-1); else if (_fun == "unreachable"_yulstring) { logTrace(evmasm::Instruction::INVALID, {}); throw ExplicitlyTerminated(); } - else if (_fun == "i64.add"_yulstring) - return arg[0] + arg[1]; - else if (_fun == "i64.sub"_yulstring) - return arg[0] - arg[1]; - else if (_fun == "i64.mul"_yulstring) - return arg[0] * arg[1]; - else if (_fun == "i64.div_u"_yulstring) - { - if (arg[1] == 0) - throw ExplicitlyTerminated(); - else - return arg[0] / arg[1]; - } - else if (_fun == "i64.rem_u"_yulstring) - { - if (arg[1] == 0) - throw ExplicitlyTerminated(); - else - return arg[0] % arg[1]; - } - else if (_fun == "i64.and"_yulstring) - return arg[0] & arg[1]; - else if (_fun == "i64.or"_yulstring) - return arg[0] | arg[1]; - else if (_fun == "i64.xor"_yulstring) - return arg[0] ^ arg[1]; - else if (_fun == "i64.shl"_yulstring) - return arg[0] << arg[1]; - else if (_fun == "i64.shr_u"_yulstring) - return arg[0] >> arg[1]; - else if (_fun == "i64.eq"_yulstring) - return arg[0] == arg[1] ? 1 : 0; - else if (_fun == "i64.ne"_yulstring) - return arg[0] != arg[1] ? 1 : 0; - else if (_fun == "i64.eqz"_yulstring) - return arg[0] == 0 ? 1 : 0; - else if (_fun == "i64.clz"_yulstring) - return clz(arg[0]); - else if (_fun == "i64.lt_u"_yulstring) - return arg[0] < arg[1] ? 1 : 0; - else if (_fun == "i64.gt_u"_yulstring) - return arg[0] > arg[1] ? 1 : 0; - else if (_fun == "i64.le_u"_yulstring) - return arg[0] <= arg[1] ? 1 : 0; - else if (_fun == "i64.ge_u"_yulstring) - return arg[0] >= arg[1] ? 1 : 0; else if (_fun == "i64.store"_yulstring) { accessMemory(arg[0], 8); writeMemoryWord(arg[0], arg[1]); return 0; } - else if (_fun == "i64.store8"_yulstring) + else if (_fun == "i64.store8"_yulstring || _fun == "i32.store8"_yulstring) { accessMemory(arg[0], 1); writeMemoryByte(arg[0], static_cast(arg[1] & 0xff)); @@ -163,12 +122,112 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a accessMemory(arg[0], 8); return readMemoryWord(arg[0]); } - else if (_fun == "eth.getAddress"_yulstring) + else if (_fun == "i32.store"_yulstring) + { + accessMemory(arg[0], 4); + writeMemoryHalfWord(arg[0], arg[1]); + return 0; + } + else if (_fun == "i32.load"_yulstring) + { + accessMemory(arg[0], 4); + return readMemoryHalfWord(arg[0]); + } + + + string prefix = _fun.str(); + string suffix; + auto dot = prefix.find("."); + if (dot != string::npos) + { + suffix = prefix.substr(dot + 1); + prefix.resize(dot); + } + + if (prefix == "i32") + { + vector halfWordArgs; + for (uint64_t a: arg) + halfWordArgs.push_back(uint32_t(a & uint32_t(-1))); + return evalWasmBuiltin(suffix, halfWordArgs); + } + else if (prefix == "i64") + return evalWasmBuiltin(suffix, arg); + else if (prefix == "eth") + return evalEthBuiltin(suffix, arg); + + yulAssert(false, "Unknown builtin: " + _fun.str() + " (or implementation did not return)"); + + return 0; +} + +template +u256 EwasmBuiltinInterpreter::evalWasmBuiltin(string const& _fun, vector const& _arguments) +{ + vector const& arg = _arguments; + + if (_fun == "add") + return arg[0] + arg[1]; + else if (_fun == "sub") + return arg[0] - arg[1]; + else if (_fun == "mul") + return arg[0] * arg[1]; + else if (_fun == "div_u") + { + if (arg[1] == 0) + throw ExplicitlyTerminated(); + else + return arg[0] / arg[1]; + } + else if (_fun == "rem_u") + { + if (arg[1] == 0) + throw ExplicitlyTerminated(); + else + return arg[0] % arg[1]; + } + else if (_fun == "and") + return arg[0] & arg[1]; + else if (_fun == "or") + return arg[0] | arg[1]; + else if (_fun == "xor") + return arg[0] ^ arg[1]; + else if (_fun == "shl") + return arg[0] << arg[1]; + else if (_fun == "shr_u") + return arg[0] >> arg[1]; + else if (_fun == "eq") + return arg[0] == arg[1] ? 1 : 0; + else if (_fun == "ne") + return arg[0] != arg[1] ? 1 : 0; + else if (_fun == "eqz") + return arg[0] == 0 ? 1 : 0; + else if (_fun == "clz") + return clz(arg[0]); + else if (_fun == "lt_u") + return arg[0] < arg[1] ? 1 : 0; + else if (_fun == "gt_u") + return arg[0] > arg[1] ? 1 : 0; + else if (_fun == "le_u") + return arg[0] <= arg[1] ? 1 : 0; + else if (_fun == "ge_u") + return arg[0] >= arg[1] ? 1 : 0; + + yulAssert(false, "Unknown builtin: " + _fun + " (or implementation did not return)"); + + return 0; +} + +u256 EwasmBuiltinInterpreter::evalEthBuiltin(string const& _fun, vector const& _arguments) +{ + vector const& arg = _arguments; + + if (_fun == "getAddress") { writeAddress(arg[0], m_state.address); return 0; } - else if (_fun == "eth.getExternalBalance"_yulstring) + else if (_fun == "getExternalBalance") { // TODO this does not read the address, but is consistent with // EVM interpreter implementation. @@ -176,7 +235,7 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a writeU128(arg[1], m_state.balance); return 0; } - else if (_fun == "eth.getBlockHash"_yulstring) + else if (_fun == "getBlockHash") { if (arg[0] >= m_state.blockNumber || arg[0] + 256 < m_state.blockNumber) return 1; @@ -186,14 +245,14 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a return 0; } } - else if (_fun == "eth.call"_yulstring) + else if (_fun == "call") { // TODO read args from memory // TODO use readAddress to read address. logTrace(evmasm::Instruction::CALL, {}); return arg[0] & 1; } - else if (_fun == "eth.callDataCopy"_yulstring) + else if (_fun == "callDataCopy") { if (arg[1] + arg[2] < arg[1] || arg[1] + arg[2] > m_state.calldata.size()) throw ExplicitlyTerminated(); @@ -204,51 +263,51 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a ); return {}; } - else if (_fun == "eth.getCallDataSize"_yulstring) + else if (_fun == "getCallDataSize") return m_state.calldata.size(); - else if (_fun == "eth.callCode"_yulstring) + else if (_fun == "callCode") { // TODO read args from memory // TODO use readAddress to read address. logTrace(evmasm::Instruction::CALLCODE, {}); return arg[0] & 1; } - else if (_fun == "eth.callDelegate"_yulstring) + else if (_fun == "callDelegate") { // TODO read args from memory // TODO use readAddress to read address. logTrace(evmasm::Instruction::DELEGATECALL, {}); return arg[0] & 1; } - else if (_fun == "eth.callStatic"_yulstring) + else if (_fun == "callStatic") { // TODO read args from memory // TODO use readAddress to read address. logTrace(evmasm::Instruction::STATICCALL, {}); return arg[0] & 1; } - else if (_fun == "eth.storageStore"_yulstring) + else if (_fun == "storageStore") { m_state.storage[h256(readU256(arg[0]))] = readU256((arg[1])); return 0; } - else if (_fun == "eth.storageLoad"_yulstring) + else if (_fun == "storageLoad") { writeU256(arg[1], m_state.storage[h256(readU256(arg[0]))]); return 0; } - else if (_fun == "eth.getCaller"_yulstring) + else if (_fun == "getCaller") { // TODO should this only write 20 bytes? writeAddress(arg[0], m_state.caller); return 0; } - else if (_fun == "eth.getCallValue"_yulstring) + else if (_fun == "getCallValue") { writeU128(arg[0], m_state.callvalue); return 0; } - else if (_fun == "eth.codeCopy"_yulstring) + else if (_fun == "codeCopy") { if (accessMemory(arg[0], arg[2])) copyZeroExtended( @@ -257,26 +316,26 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a ); return 0; } - else if (_fun == "eth.getCodeSize"_yulstring) + else if (_fun == "getCodeSize") return m_state.code.size(); - else if (_fun == "eth.getBlockCoinbase"_yulstring) + else if (_fun == "getBlockCoinbase") { writeAddress(arg[0], m_state.coinbase); return 0; } - else if (_fun == "eth.create"_yulstring) + else if (_fun == "create") { // TODO access memory // TODO use writeAddress to store resulting address logTrace(evmasm::Instruction::CREATE, {}); return 0xcccccc + arg[1]; } - else if (_fun == "eth.getBlockDifficulty"_yulstring) + else if (_fun == "getBlockDifficulty") { writeU256(arg[0], m_state.difficulty); return 0; } - else if (_fun == "eth.externalCodeCopy"_yulstring) + else if (_fun == "externalCodeCopy") { // TODO use readAddress to read address. if (accessMemory(arg[1], arg[3])) @@ -287,19 +346,19 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a ); return 0; } - else if (_fun == "eth.getExternalCodeSize"_yulstring) + else if (_fun == "getExternalCodeSize") // Generate "random" code length. Make sure it fits the page size. return u256(keccak256(h256(readAddress(arg[0])))) & 0xfff; - else if (_fun == "eth.getGasLeft"_yulstring) + else if (_fun == "getGasLeft") return 0x99; - else if (_fun == "eth.getBlockGasLimit"_yulstring) + else if (_fun == "getBlockGasLimit") return uint64_t(m_state.gaslimit); - else if (_fun == "eth.getTxGasPrice"_yulstring) + else if (_fun == "getTxGasPrice") { writeU128(arg[0], m_state.gasprice); return 0; } - else if (_fun == "eth.log"_yulstring) + else if (_fun == "log") { uint64_t numberOfTopics = arg[2]; if (numberOfTopics > 4) @@ -307,14 +366,14 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a logTrace(evmasm::logInstruction(numberOfTopics), {}); return 0; } - else if (_fun == "eth.getBlockNumber"_yulstring) + else if (_fun == "getBlockNumber") return m_state.blockNumber; - else if (_fun == "eth.getTxOrigin"_yulstring) + else if (_fun == "getTxOrigin") { writeAddress(arg[0], m_state.origin); return 0; } - else if (_fun == "eth.finish"_yulstring) + else if (_fun == "finish") { bytes data; if (accessMemory(arg[0], arg[1])) @@ -322,7 +381,7 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a logTrace(evmasm::Instruction::RETURN, {}, data); throw ExplicitlyTerminated(); } - else if (_fun == "eth.revert"_yulstring) + else if (_fun == "revert") { bytes data; if (accessMemory(arg[0], arg[1])) @@ -330,9 +389,9 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a logTrace(evmasm::Instruction::REVERT, {}, data); throw ExplicitlyTerminated(); } - else if (_fun == "eth.getReturnDataSize"_yulstring) + else if (_fun == "getReturnDataSize") return m_state.returndata.size(); - else if (_fun == "eth.returnDataCopy"_yulstring) + else if (_fun == "returnDataCopy") { if (arg[1] + arg[2] < arg[1] || arg[1] + arg[2] > m_state.returndata.size()) throw ExplicitlyTerminated(); @@ -343,16 +402,16 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _a ); return {}; } - else if (_fun == "eth.selfDestruct"_yulstring) + else if (_fun == "selfDestruct") { // TODO use readAddress to read address. logTrace(evmasm::Instruction::SELFDESTRUCT, {}); throw ExplicitlyTerminated(); } - else if (_fun == "eth.getBlockTimestamp"_yulstring) + else if (_fun == "getBlockTimestamp") return m_state.timestamp; - yulAssert(false, "Unknown builtin: " + _fun.str() + " (or implementation did not return)"); + yulAssert(false, "Unknown builtin: " + _fun + " (or implementation did not return)"); return 0; } @@ -388,12 +447,26 @@ uint64_t EwasmBuiltinInterpreter::readMemoryWord(uint64_t _offset) return r; } +uint32_t EwasmBuiltinInterpreter::readMemoryHalfWord(uint64_t _offset) +{ + uint32_t r = 0; + for (size_t i = 0; i < 4; i++) + r |= uint64_t(m_state.memory[_offset + i]) << (i * 8); + return r; +} + void EwasmBuiltinInterpreter::writeMemoryWord(uint64_t _offset, uint64_t _value) { for (size_t i = 0; i < 8; i++) m_state.memory[_offset + i] = uint8_t((_value >> (i * 8)) & 0xff); } +void EwasmBuiltinInterpreter::writeMemoryHalfWord(uint64_t _offset, uint32_t _value) +{ + for (size_t i = 0; i < 4; i++) + m_state.memory[_offset + i] = uint8_t((_value >> (i * 8)) & 0xff); +} + void EwasmBuiltinInterpreter::writeMemoryByte(uint64_t _offset, uint8_t _value) { m_state.memory[_offset] = _value; diff --git a/test/tools/yulInterpreter/EwasmBuiltinInterpreter.h b/test/tools/yulInterpreter/EwasmBuiltinInterpreter.h index c6819743c..62b6bc024 100644 --- a/test/tools/yulInterpreter/EwasmBuiltinInterpreter.h +++ b/test/tools/yulInterpreter/EwasmBuiltinInterpreter.h @@ -71,6 +71,16 @@ public: u256 evalBuiltin(YulString _fun, std::vector const& _arguments); private: + template + u256 evalWasmBuiltin( + std::string const& _fun, + std::vector const& _arguments + ); + u256 evalEthBuiltin( + std::string const& _fun, + std::vector const& _arguments + ); + /// Checks if the memory access is not too large for the interpreter and adjusts /// msize accordingly. /// @returns false if the amount of bytes read is lager than 0xffff @@ -78,12 +88,18 @@ private: /// @returns the memory contents at the provided address. /// Does not adjust msize, use @a accessMemory for that bytes readMemory(uint64_t _offset, uint64_t _size = 32); - /// @returns the memory contents at the provided address (little-endian). + /// @returns the memory contents (8 bytes) at the provided address (little-endian). /// Does not adjust msize, use @a accessMemory for that uint64_t readMemoryWord(uint64_t _offset); + /// @returns the memory contents (4 bytes) at the provided address (little-endian). + /// Does not adjust msize, use @a accessMemory for that + uint32_t readMemoryHalfWord(uint64_t _offset); /// Writes a word to memory (little-endian) /// Does not adjust msize, use @a accessMemory for that void writeMemoryWord(uint64_t _offset, uint64_t _value); + /// Writes a 4-byte value to memory (little-endian) + /// Does not adjust msize, use @a accessMemory for that + void writeMemoryHalfWord(uint64_t _offset, uint32_t _value); /// Writes a byte to memory /// Does not adjust msize, use @a accessMemory for that void writeMemoryByte(uint64_t _offset, uint8_t _value);