diff --git a/src/builtins.ts b/src/builtins.ts index 6851c6d2..ec2d592d 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -1,7 +1,9 @@ import { Compiler, Target, - ConversionKind + ConversionKind, + + makeSmallIntegerWrap } from "./compiler"; import { @@ -15,7 +17,9 @@ import { } from "./ast"; import { - Type, TypeKind + Type, + TypeKind, + TypeFlags } from "./types"; import { @@ -511,23 +515,10 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty case TypeKind.I8: case TypeKind.I16: - ret = module.createBinary(BinaryOp.ShrI32, - module.createBinary(BinaryOp.ShlI32, - module.createBinary(BinaryOp.RotlI32, arg0, arg1), - module.createI32(compiler.currentType.smallIntegerShift) - ), - module.createI32(compiler.currentType.smallIntegerShift) - ); - break; - case TypeKind.U8: case TypeKind.U16: case TypeKind.BOOL: - ret = module.createBinary(BinaryOp.AndI32, - module.createBinary(BinaryOp.RotlI32, arg0, arg1), - module.createI32(compiler.currentType.smallIntegerMask) - ); - break; + ret = makeSmallIntegerWrap(module.createBinary(BinaryOp.RotlI32, arg0, arg1), compiler.currentType, module); case TypeKind.I32: case TypeKind.U32: @@ -584,22 +575,10 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty case TypeKind.I8: case TypeKind.I16: - ret = module.createBinary(BinaryOp.ShrI32, - module.createBinary(BinaryOp.ShlI32, - module.createBinary(BinaryOp.RotrI32, arg0, arg1), - module.createI32(compiler.currentType.smallIntegerShift) - ), - module.createI32(compiler.currentType.smallIntegerShift) - ); - break; - case TypeKind.U8: case TypeKind.U16: case TypeKind.BOOL: - ret = module.createBinary(BinaryOp.AndI32, - module.createBinary(BinaryOp.RotrI32, arg0, arg1), - module.createI32(compiler.currentType.smallIntegerMask) - ); + ret = makeSmallIntegerWrap(module.createBinary(BinaryOp.RotrI32, arg0, arg1), compiler.currentType, module); break; case TypeKind.I32: @@ -1249,7 +1228,7 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty case TypeKind.F32: if (typeArguments) { - if (!(typeArguments[1].isAnyInteger && typeArguments[1].size == 32)) { + if (!(typeArguments[1].is(TypeFlags.INTEGER) && typeArguments[1].size == 32)) { compiler.error(DiagnosticCode.Type_0_cannot_be_reinterpreted_as_type_1, reportNode.range, typeArguments[0].toString(), typeArguments[1].toString()); return module.createUnreachable(); } @@ -1261,7 +1240,7 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty case TypeKind.F64: if (typeArguments) { - if (!(typeArguments[1].isLongInteger && !typeArguments[1].isReference)) { + if (!(typeArguments[1].is(TypeFlags.LONG | TypeFlags.INTEGER) && !typeArguments[1].isReference)) { compiler.error(DiagnosticCode.Type_0_cannot_be_reinterpreted_as_type_1, reportNode.range, typeArguments[0].toString(), typeArguments[1].toString()); return module.createUnreachable(); } @@ -1390,7 +1369,7 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty } arg0 = compiler.compileExpression(operands[0], usizeType); compiler.currentType = typeArguments[0]; - return module.createLoad(typeArguments[0].size >>> 3, typeArguments[0].isAnySignedInteger, arg0, typeArguments[0].toNativeType()); + return module.createLoad(typeArguments[0].size >>> 3, typeArguments[0].is(TypeFlags.SIGNED | TypeFlags.INTEGER), arg0, typeArguments[0].toNativeType()); case "store": // store(offset: usize, value: T) -> void compiler.currentType = Type.void; diff --git a/src/compiler.ts b/src/compiler.ts index 4e18caaa..e4193d11 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -110,6 +110,7 @@ import { import { Type, TypeKind, + TypeFlags, typesToNativeTypes } from "./types"; @@ -1196,147 +1197,125 @@ export class Compiler extends DiagnosticEmitter { if (toType.kind == TypeKind.VOID) return this.module.createDrop(expr); - var fromFloat = fromType.isAnyFloat; - var toFloat = toType.isAnyFloat; - var mod = this.module; var losesInformation = false; - if (fromFloat) { + if (fromType.is(TypeFlags.FLOAT)) { // float to float - if (toFloat) { + if (toType.is(TypeFlags.FLOAT)) { if (fromType.kind == TypeKind.F32) { // f32 to f64 if (toType.kind == TypeKind.F64) expr = mod.createUnary(UnaryOp.PromoteF32, expr); + // otherwise f32 to f32 + // f64 to f32 } else if (toType.kind == TypeKind.F32) { losesInformation = true; expr = mod.createUnary(UnaryOp.DemoteF64, expr); } + // otherwise f64 to f64 + // float to int - } else { + } else if (toType.is(TypeFlags.INTEGER)) { losesInformation = true; // f32 to int if (fromType.kind == TypeKind.F32) { - if (toType.isAnySignedInteger) { - if (toType.isLongInteger) + if (toType.is(TypeFlags.SIGNED)) { + if (toType.is(TypeFlags.LONG)) expr = mod.createUnary(UnaryOp.TruncF32ToI64, expr); else { expr = mod.createUnary(UnaryOp.TruncF32ToI32, expr); - if (toType.isSmallInteger) { - expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift)); - expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift)); - } + if (toType.is(TypeFlags.SMALL)) + expr = makeSmallIntegerWrap(expr, toType, this.module); } } else { - if (toType.isLongInteger) + if (toType.is(TypeFlags.LONG)) expr = mod.createUnary(UnaryOp.TruncF32ToU64, expr); else { expr = mod.createUnary(UnaryOp.TruncF32ToU32, expr); - if (toType.isSmallInteger) - expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask)); + if (toType.is(TypeFlags.SMALL)) + expr = makeSmallIntegerWrap(expr, toType, this.module); } } // f64 to int } else { - if (toType.isAnySignedInteger) { - if (toType.isLongInteger) + if (toType.is(TypeFlags.SIGNED)) { + if (toType.is(TypeFlags.LONG)) expr = mod.createUnary(UnaryOp.TruncF64ToI64, expr); else { expr = mod.createUnary(UnaryOp.TruncF64ToI32, expr); - if (toType.isSmallInteger) { - expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift)); - expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift)); - } + if (toType.is(TypeFlags.SMALL)) + expr = makeSmallIntegerWrap(expr, toType, this.module); } } else { - if (toType.isLongInteger) + if (toType.is(TypeFlags.LONG)) expr = mod.createUnary(UnaryOp.TruncF64ToU64, expr); else { expr = mod.createUnary(UnaryOp.TruncF64ToU32, expr); - if (toType.isSmallInteger) - expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask)); + if (toType.is(TypeFlags.SMALL)) + expr = makeSmallIntegerWrap(expr, toType, this.module); } } } + // float to void + } else { + assert(toType.flags == TypeFlags.NONE); + expr = this.module.createDrop(expr); } // int to float - } else if (toFloat) { + } else if (fromType.is(TypeFlags.INTEGER) && toType.is(TypeFlags.FLOAT)) { // int to f32 if (toType.kind == TypeKind.F32) { - if (fromType.isLongInteger) { + if (fromType.is(TypeFlags.LONG)) { losesInformation = true; - if (fromType.isAnySignedInteger) - expr = mod.createUnary(UnaryOp.ConvertI64ToF32, expr); - else - expr = mod.createUnary(UnaryOp.ConvertU64ToF32, expr); + expr = mod.createUnary(select(UnaryOp.ConvertI64ToF32, UnaryOp.ConvertU64ToF32, fromType.is(TypeFlags.SIGNED)), expr); } else { - if (!fromType.isSmallInteger) - losesInformation = true; - if (fromType.isAnySignedInteger) - expr = mod.createUnary(UnaryOp.ConvertI32ToF32, expr); - else - expr = mod.createUnary(UnaryOp.ConvertU32ToF32, expr); + losesInformation = !fromType.is(TypeFlags.SMALL); + expr = mod.createUnary(select(UnaryOp.ConvertI32ToF32, UnaryOp.ConvertU32ToF32, fromType.is(TypeFlags.SIGNED)), expr); } // int to f64 } else { - if (fromType.isLongInteger) { + if (fromType.is(TypeFlags.LONG)) { losesInformation = true; - if (fromType.isAnySignedInteger) - expr = mod.createUnary(UnaryOp.ConvertI64ToF64, expr); - else - expr = mod.createUnary(UnaryOp.ConvertU64ToF64, expr); + expr = mod.createUnary(select(UnaryOp.ConvertI64ToF64, UnaryOp.ConvertU64ToF64, fromType.is(TypeFlags.SIGNED)), expr); } else - if (fromType.isAnySignedInteger) - expr = mod.createUnary(UnaryOp.ConvertI32ToF64, expr); - else - expr = mod.createUnary(UnaryOp.ConvertU32ToF64, expr); + expr = mod.createUnary(select(UnaryOp.ConvertI32ToF64, UnaryOp.ConvertU32ToF64, fromType.is(TypeFlags.SIGNED)), expr); } // int to int } else { - if (fromType.isLongInteger) { + if (fromType.is(TypeFlags.LONG)) { // i64 to i32 - if (!toType.isLongInteger) { + if (!toType.is(TypeFlags.LONG)) { losesInformation = true; expr = mod.createUnary(UnaryOp.WrapI64, expr); // discards upper bits - if (toType.isSmallInteger) { - if (toType.isAnySignedInteger) { - expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift)); - expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift)); - } else - expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask)); - } + if (toType.is(TypeFlags.SMALL)) + expr = makeSmallIntegerWrap(expr, toType, this.module); } // i32 to i64 - } else if (toType.isLongInteger) { - if (toType.isAnySignedInteger) - expr = mod.createUnary(UnaryOp.ExtendI32, expr); - else - expr = mod.createUnary(UnaryOp.ExtendU32, expr); + } else if (toType.is(TypeFlags.LONG)) { + expr = mod.createUnary(select(UnaryOp.ExtendI32, UnaryOp.ExtendU32, toType.is(TypeFlags.SIGNED)), expr); - // i32 or smaller to even smaller int - } else if (toType.isSmallInteger && fromType.size > toType.size) { + // i32 or smaller to even smaller or same size int with change of sign + } else if (toType.is(TypeFlags.SMALL) && (fromType.size > toType.size || (fromType.size == toType.size && fromType.is(TypeFlags.SIGNED) != toType.is(TypeFlags.SIGNED)))) { losesInformation = true; - if (toType.isAnySignedInteger) { - expr = mod.createBinary(BinaryOp.ShlI32, expr, mod.createI32(toType.smallIntegerShift)); - expr = mod.createBinary(BinaryOp.ShrI32, expr, mod.createI32(toType.smallIntegerShift)); - } else - expr = mod.createBinary(BinaryOp.AndI32, expr, mod.createI32(toType.smallIntegerMask)); + expr = makeSmallIntegerWrap(expr, toType, this.module); } + + // otherwise (smaller) i32/u32 to (same size) i32/u32 } if (losesInformation && conversionKind == ConversionKind.IMPLICIT) @@ -1360,8 +1339,7 @@ export class Compiler extends DiagnosticEmitter { var compound = false; var possiblyOverflows = false; - - var tempLocal: Local; + var tempLocal: Local | null = null switch (expression.operator) { @@ -1905,7 +1883,7 @@ export class Compiler extends DiagnosticEmitter { case Token.LESSTHAN_LESSTHAN_EQUALS: compound = true; case Token.LESSTHAN_LESSTHAN: // retains low bits of small integers - left = this.compileExpression(expression.left, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.isAnyFloat), ConversionKind.NONE, false); + left = this.compileExpression(expression.left, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.is(TypeFlags.FLOAT)), ConversionKind.NONE, false); right = this.compileExpression(expression.right, this.currentType, ConversionKind.IMPLICIT, false); switch (this.currentType.kind) { @@ -1940,7 +1918,7 @@ export class Compiler extends DiagnosticEmitter { case Token.GREATERTHAN_GREATERTHAN_EQUALS: compound = true; case Token.GREATERTHAN_GREATERTHAN: // must wrap small integers - left = this.compileExpression(expression.left, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.isAnyFloat), ConversionKind.NONE); + left = this.compileExpression(expression.left, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.is(TypeFlags.FLOAT)), ConversionKind.NONE); right = this.compileExpression(expression.right, this.currentType, ConversionKind.IMPLICIT); switch (this.currentType.kind) { @@ -1984,7 +1962,7 @@ export class Compiler extends DiagnosticEmitter { case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: compound = true; case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: // modifies low bits of small integers if unsigned - left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.u64 : select(Type.i32, contextualType, contextualType == Type.void), ConversionKind.NONE); + left = this.compileExpression(expression.left, select(Type.u64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.is(TypeFlags.FLOAT)), ConversionKind.NONE); right = this.compileExpression(expression.right, this.currentType, ConversionKind.IMPLICIT); switch (this.currentType.kind) { @@ -2018,7 +1996,7 @@ export class Compiler extends DiagnosticEmitter { case Token.AMPERSAND_EQUALS: compound = true; case Token.AMPERSAND: // retains low bits of small integers - left = this.compileExpression(expression.left, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.isAnyFloat), ConversionKind.NONE, false); + left = this.compileExpression(expression.left, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.is(TypeFlags.FLOAT)), ConversionKind.NONE, false); right = this.compileExpression(expression.right, this.currentType, ConversionKind.IMPLICIT, false); switch (this.currentType.kind) { @@ -2053,7 +2031,7 @@ export class Compiler extends DiagnosticEmitter { case Token.BAR_EQUALS: compound = true; case Token.BAR: // retains low bits of small integers - left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : select(Type.i32, contextualType, contextualType == Type.void), ConversionKind.NONE, false); + left = this.compileExpression(expression.left, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.is(TypeFlags.FLOAT)), ConversionKind.NONE, false); right = this.compileExpression(expression.right, this.currentType, ConversionKind.IMPLICIT, false); switch (this.currentType.kind) { @@ -2088,7 +2066,7 @@ export class Compiler extends DiagnosticEmitter { case Token.CARET_EQUALS: compound = true; case Token.CARET: // retains low bits of small integers - left = this.compileExpression(expression.left, contextualType.isAnyFloat ? Type.i64 : select(Type.i32, contextualType, contextualType == Type.void), ConversionKind.NONE, false); + left = this.compileExpression(expression.left, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.is(TypeFlags.FLOAT)), ConversionKind.NONE, false); right = this.compileExpression(expression.right, this.currentType, ConversionKind.IMPLICIT, false); switch (this.currentType.kind) { @@ -2124,74 +2102,98 @@ export class Compiler extends DiagnosticEmitter { case Token.AMPERSAND_AMPERSAND: // left && right left = this.compileExpression(expression.left, select(Type.i32, contextualType, contextualType == Type.void), ConversionKind.NONE); - right = this.compileExpression(expression.right, this.currentType); + right = this.compileExpression(expression.right, this.currentType, ConversionKind.IMPLICIT, false); - // simplify if left is free of side effects while tolerating one level of nesting, e.g., i32.load(i32.const) - if (condition = this.module.cloneExpression(left, true, 1)) { - expr = this.module.createIf( - this.currentType.isLongInteger - ? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0)) - : this.currentType == Type.f64 - ? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0)) - : this.currentType == Type.f32 - ? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0)) - : condition, // usual case: saves one EQZ when not using EQZ above - right, - left - ); - break; + // clone left if free of side effects while tolerating one level of nesting + expr = this.module.cloneExpression(left, true, 1); + + // if not possible, tee left to a temp. local + if (!expr) { + tempLocal = this.currentFunction.getAndFreeTempLocal(this.currentType); + left = this.module.createTeeLocal(tempLocal.index, left); } - // otherwise use a temporary local for the intermediate value - tempLocal = this.currentFunction.getAndFreeTempLocal(this.currentType); - condition = this.module.createTeeLocal(tempLocal.index, left); - expr = this.module.createIf( - this.currentType.isLongInteger - ? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0)) - : this.currentType == Type.f64 - ? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0)) - : this.currentType == Type.f32 - ? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0)) - : this.module.createTeeLocal(tempLocal.index, left), - right, - this.module.createGetLocal(tempLocal.index, this.currentType.toNativeType()) - ); - break; + // make a condition that checks !left in case an optimizer can take advantage of guaranteed 0 or 1 + // binaryen just switches the arms here, see: https://github.com/WebAssembly/binaryen/issues/1355 + possiblyOverflows = this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER); + condition = makeEqualsZero(left, this.currentType, this.module); - case Token.BAR_BAR: // left || right - left = this.compileExpression(expression.left, select(Type.i32, contextualType, contextualType == Type.void), ConversionKind.NONE); - right = this.compileExpression(expression.right, this.currentType); + // simplify when cloning left without side effects was successful + if (expr) { - // simplify if left is free of side effects while tolerating one level of nesting - if (condition = this.module.cloneExpression(left, true, 1)) { + // if right is also free of side effects, do a select + if (left = this.module.cloneExpression(right, true, 1)) { + expr = this.module.createSelect( + expr, // then cloned left + left, // else cloned right, actually + condition // if !left + ); + + // otherwise make it an if + } else + expr = this.module.createIf( + condition, // if !left + expr, // then cloned left + right // else right + ); + + // otherwise make use of the temp. local + } else { + assert(tempLocal); expr = this.module.createIf( - this.currentType.isLongInteger - ? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0)) - : this.currentType == Type.f64 - ? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0)) - : this.currentType == Type.f32 - ? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0)) - : condition, // usual case: saves one EQZ when not using EQZ above - left, + condition, // if !left + this.module.createGetLocal((tempLocal).index, this.currentType.toNativeType()), right ); - break; + } + break; + + case Token.BAR_BAR: // left || right + left = this.compileExpression(expression.left, select(Type.i32, contextualType, contextualType == Type.void), ConversionKind.NONE); + right = this.compileExpression(expression.right, this.currentType, ConversionKind.IMPLICIT, false); + + // clone left if free of side effects while tolerating one level of nesting + expr = this.module.cloneExpression(left, true, 1); + + // if not possible, tee left to a temp. local + if (!expr) { + tempLocal = this.currentFunction.getAndFreeTempLocal(this.currentType); + left = this.module.createTeeLocal(tempLocal.index, left); } - // otherwise use a temporary local for the intermediate value - tempLocal = this.currentFunction.getAndFreeTempLocal(this.currentType); - condition = this.module.createTeeLocal(tempLocal.index, left); - expr = this.module.createIf( - this.currentType.isLongInteger - ? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0)) - : this.currentType == Type.f64 - ? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0)) - : this.currentType == Type.f32 - ? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0)) - : this.module.createTeeLocal(tempLocal.index, left), - this.module.createGetLocal(tempLocal.index, this.currentType.toNativeType()), - right - ); + // make a condition that checks !left in case an optimizer can take advantage of guaranteed 0 or 1 + // binaryen just switches the arms here, see: https://github.com/WebAssembly/binaryen/issues/1355 + possiblyOverflows = this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER); // if right already did + condition = makeEqualsZero(left, this.currentType, this.module); + + // simplify when cloning left without side effects was successful + if (expr) { + + // if right is also free of side effects, do a select + if (left = this.module.cloneExpression(right, true, 1)) { + expr = this.module.createSelect( + left, // else cloned right, actually + expr, // then cloned left + condition // if !left + ); + + // otherwise make it an if + } else + expr = this.module.createIf( + condition, // if !left + right, // then right + expr // else cloned left + ); + + // otherwise make use of the temp. local + } else { + assert(tempLocal); + expr = this.module.createIf( + condition, // if !left + right, + this.module.createGetLocal((tempLocal).index, this.currentType.toNativeType()) + ); + } break; default: @@ -2199,8 +2201,8 @@ export class Compiler extends DiagnosticEmitter { throw new Error("not implemented"); } if (possiblyOverflows && wrapSmallIntegers) { - assert(this.currentType.isSmallInteger); - expr = wrapSmallInteger(expr, this.currentType, this.module); + assert(this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER)); + expr = makeSmallIntegerWrap(expr, this.currentType, this.module); } return compound ? this.compileAssignmentWithValue(expression.left, expr, contextualType != Type.void) @@ -2577,33 +2579,35 @@ export class Compiler extends DiagnosticEmitter { return this.module.createF64(floatValue); } - case LiteralKind.INTEGER: { + case LiteralKind.INTEGER: var intValue = (expression).value; if (contextualType == Type.bool && (intValue.isZero || intValue.isOne)) - return this.module.createI32(intValue.isZero ? 0 : 1); + return this.module.createI32(select(0, 1, intValue.isZero)); if (contextualType == Type.f64) return this.module.createF64(intValue.toF64()); if (contextualType == Type.f32) return this.module.createF32(intValue.toF64()); - if (contextualType.isLongInteger) + if (contextualType.is(TypeFlags.LONG | TypeFlags.INTEGER)) return this.module.createI64(intValue.lo, intValue.hi); if (!intValue.fitsInI32) { - this.currentType = select(Type.i64, Type.u64, contextualType.isAnySignedInteger); + this.currentType = select(Type.i64, Type.u64, contextualType.is(TypeFlags.SIGNED)); return this.module.createI64(intValue.lo, intValue.hi); } - if (contextualType.isSmallInteger) { - var smallIntValue: i32 = contextualType.isAnySignedInteger - ? intValue.lo << contextualType.smallIntegerShift >> contextualType.smallIntegerShift - : intValue.lo & contextualType.smallIntegerMask; - return this.module.createI32(smallIntValue); + if (contextualType.is(TypeFlags.SMALL | TypeFlags.INTEGER)) { + var shift = contextualType.computeSmallIntegerShift(Type.i32); + var mask = contextualType.computeSmallIntegerMask(Type.i32); + return this.module.createI32(select( + intValue.lo << shift >> shift, + intValue.lo & mask, + contextualType.is(TypeFlags.SIGNED) + )); } if (contextualType == Type.void && !intValue.fitsInI32) { this.currentType = Type.i64; return this.module.createI64(intValue.lo, intValue.hi); } - this.currentType = contextualType.isAnySignedInteger ? Type.i32 : Type.u32; + this.currentType = select(Type.i32, Type.u32, contextualType.is(TypeFlags.SIGNED)); return this.module.createI32(intValue.toI32()); - } // case LiteralKind.OBJECT: // case LiteralKind.REGEXP: @@ -2655,7 +2659,7 @@ export class Compiler extends DiagnosticEmitter { assert((element).memoryOffset >= 0); targetExpr = this.compileExpression(resolved.targetExpression, select(Type.usize64, Type.usize32, this.options.target == Target.WASM64)); this.currentType = (element).type; - return this.module.createLoad((element).type.byteSize, (element).type.isAnySignedInteger, + return this.module.createLoad((element).type.byteSize, (element).type.is(TypeFlags.SIGNED | TypeFlags.INTEGER), targetExpr, (element).type.toNativeType(), (element).memoryOffset @@ -2816,8 +2820,8 @@ export class Compiler extends DiagnosticEmitter { nativeOne ); if (possiblyOverflows) { - assert(this.currentType.isSmallInteger); - setValue = wrapSmallInteger(setValue, this.currentType, this.module); + assert(this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER)); + setValue = makeSmallIntegerWrap(setValue, this.currentType, this.module); } setValue = this.compileAssignmentWithValue(expression.operand, setValue, false); // sets currentType = void this.currentType = tempLocal.type; @@ -2842,7 +2846,7 @@ export class Compiler extends DiagnosticEmitter { return this.module.createUnreachable(); } expr = this.compileExpression(expression.operand, select(Type.i32, contextualType, contextualType == Type.void), ConversionKind.NONE, false); - possiblyOverflows = this.currentType.isSmallInteger; // if operand already did + possiblyOverflows = this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER); // if operand already did break; case Token.MINUS: @@ -2856,7 +2860,6 @@ export class Compiler extends DiagnosticEmitter { case TypeKind.U16: case TypeKind.BOOL: possiblyOverflows = true; // or if operand already did - // fall-through default: expr = this.module.createBinary(BinaryOp.SubI32, this.module.createI32(0), expr); break; @@ -2866,7 +2869,6 @@ export class Compiler extends DiagnosticEmitter { this.error(DiagnosticCode.Operation_not_supported, expression.range); return this.module.createUnreachable(); } - // fall-through case TypeKind.ISIZE: expr = this.module.createBinary(select(BinaryOp.SubI64, BinaryOp.SubI32, this.options.target == Target.WASM64), this.currentType.toNativeZero(this.module), expr); break; @@ -2971,36 +2973,12 @@ export class Compiler extends DiagnosticEmitter { case Token.EXCLAMATION: // must wrap small integers expr = this.compileExpression(expression.operand, select(Type.i32, contextualType, contextualType == Type.void), ConversionKind.NONE); - - switch (this.currentType.kind) { - - default: - expr = this.module.createUnary(UnaryOp.EqzI32, expr); - break; - - case TypeKind.ISIZE: - case TypeKind.USIZE: - expr = this.module.createUnary(select(UnaryOp.EqzI64, UnaryOp.EqzI32, this.options.target == Target.WASM64), expr); - break; - - case TypeKind.I64: - case TypeKind.U64: - expr = this.module.createUnary(UnaryOp.EqzI64, expr); - break; - - case TypeKind.F32: - expr = this.module.createBinary(BinaryOp.EqF32, expr, this.module.createF32(0)); - break; - - case TypeKind.F64: - expr = this.module.createBinary(BinaryOp.EqF64, expr, this.module.createF64(0)); - break; - } + expr = makeEqualsZero(expr, this.currentType, this.module); this.currentType = Type.bool; break; case Token.TILDE: // retains low bits of small integers - expr = this.compileExpression(expression.operand, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.isAnyFloat), select(ConversionKind.NONE, ConversionKind.IMPLICIT, contextualType == Type.void), false); + expr = this.compileExpression(expression.operand, select(Type.i64, select(Type.i32, contextualType, contextualType == Type.void), contextualType.is(TypeFlags.FLOAT)), select(ConversionKind.NONE, ConversionKind.IMPLICIT, contextualType == Type.void), false); switch (this.currentType.kind) { @@ -3036,8 +3014,8 @@ export class Compiler extends DiagnosticEmitter { throw new Error("unary operator expected"); } if (possiblyOverflows && wrapSmallIntegers) { - assert(this.currentType.isSmallInteger); - expr = wrapSmallInteger(expr, this.currentType, this.module); + assert(this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER)); + expr = makeSmallIntegerWrap(expr, this.currentType, this.module); } return compound ? this.compileAssignmentWithValue(expression.operand, expr, contextualType != Type.void) @@ -3075,13 +3053,14 @@ function makeInlineConstant(element: VariableLikeElement, module: Module): Expre case TypeKind.I8: case TypeKind.I16: - var shift = (element.type).smallIntegerShift; + var shift = element.type.computeSmallIntegerShift(Type.i32); return module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() << shift >> shift : 0); case TypeKind.U8: case TypeKind.U16: case TypeKind.BOOL: - return module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() & (element.type).smallIntegerMask: 0); + var mask = element.type.computeSmallIntegerMask(Type.i32); + return module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() & mask : 0); case TypeKind.I32: case TypeKind.U32: @@ -3109,7 +3088,7 @@ function makeInlineConstant(element: VariableLikeElement, module: Module): Expre } /** Wraps a 32-bit integer expression so it evaluates to a valid value in the range of the specified small integer type. */ -export function wrapSmallInteger(expr: ExpressionRef, type: Type, module: Module) { +export function makeSmallIntegerWrap(expr: ExpressionRef, type: Type, module: Module) { switch (type.kind) { case TypeKind.I8: @@ -3152,6 +3131,40 @@ export function wrapSmallInteger(expr: ExpressionRef, type: Type, module: Module module.createI32(0x1) ); break; + + case TypeKind.VOID: + throw new Error("concrete type expected"); + } + return expr; +} + +export function makeEqualsZero(expr: ExpressionRef, type: Type, module: Module): ExpressionRef { + switch (type.kind) { + + default: // any integer up to 32 bits + expr = module.createUnary(UnaryOp.EqzI32, expr); + break; + + case TypeKind.I64: + case TypeKind.U64: + expr = module.createUnary(UnaryOp.EqzI64, expr); + break; + + case TypeKind.ISIZE: + case TypeKind.USIZE: + expr = module.createUnary(select(UnaryOp.EqzI64, UnaryOp.EqzI32, type.size == 64), expr); + break; + + case TypeKind.F32: + expr = module.createBinary(BinaryOp.EqF32, expr, module.createF32(0)); + break; + + case TypeKind.F64: + expr = module.createBinary(BinaryOp.EqF64, expr, module.createF64(0)); + break; + + case TypeKind.VOID: + throw new Error("concrete type expected"); } return expr; } diff --git a/src/types.ts b/src/types.ts index 7e11614a..6d4dcfbd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -43,11 +43,38 @@ export const enum TypeKind { VOID } +/** Indicates capabilities of a type. */ +export const enum TypeFlags { + NONE = 0, + /** Is a signed type that can represent negative values. */ + SIGNED = 1 << 0, + /** Is an unsigned type that cannot represent negative values. */ + UNSIGNED = 1 << 1, + /** Is an integer type. */ + INTEGER = 1 << 2, + /** Is a floating point type. */ + FLOAT = 1 << 3, + /** Is a sized integer type with a target specific bit size. */ + SIZE = 1 << 4, + /** Is a small type that is emulated in a larger type. */ + SMALL = 1 << 5, + /** Is a long type larger than 32-bits. */ + LONG = 1 << 6, + /** Is a value type. */ + VALUE = 1 << 7, + /** Is a reference type. */ + REFERENCE = 1 << 8, + /** Is a nullable type. */ + NULLABLE = 1 << 9 +} + /** Represents a resolved type. */ export class Type { /** Type kind. */ kind: TypeKind; + /** Type flags. */ + flags: TypeFlags; /** Size in bits. */ size: i32; /** Size in bytes. */ @@ -56,171 +83,34 @@ export class Type { classType: Class | null; /** Underlying function type, if a function type. */ functionType: Function | null; - /** Whether nullable or not. */ - isNullable: bool = false; /** Respective nullable type, if non-nullable. */ nullableType: Type | null = null; /** Respective non-nullable type, if nullable. */ nonNullableType: Type; /** Constructs a new resolved type. */ - constructor(kind: TypeKind, size: i32) { + constructor(kind: TypeKind, flags: TypeFlags, size: i32) { this.kind = kind; + this.flags = flags; this.size = size; this.byteSize = ceil(size / 8); this.classType = null; this.nonNullableType = this; } - /** Sign-extending 32-bit shift, if a small signed integer. */ - get smallIntegerShift(): i32 { return 32 - this.size; } - /** Truncating 32-bit mask, if a small unsigned integer. */ - get smallIntegerMask(): i32 { return -1 >>> (32 - this.size); } - - /** Tests if this type is of any integer kind. */ - get isAnyInteger(): bool { - switch (this.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: - case TypeKind.I64: - case TypeKind.ISIZE: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.U32: - case TypeKind.U64: - case TypeKind.USIZE: - case TypeKind.BOOL: - return true; - default: - return false; - } + /** Computes the sign-extending shift in the target type. */ + computeSmallIntegerShift(targetType: Type) { + return targetType.size - this.size; } - /** Tests if this type is of any unsigned integer kind. */ - get isAnyUnsignedInteger(): bool { - switch (this.kind) { - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.U32: - case TypeKind.U64: - case TypeKind.USIZE: - case TypeKind.BOOL: - return true; - default: - return false; - } + /** Computes the truncating mask in the target type. */ + computeSmallIntegerMask(targetType: Type) { + return -1 >>> (targetType.size - this.size); } - /** Tests if this type is of any signed integer kind. */ - get isAnySignedInteger(): bool { - switch (this.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: - case TypeKind.I64: - case TypeKind.ISIZE: - return true; - default: - return false; - } - } - - /** Tests if this type is of any small integer kind. */ - get isSmallInteger(): bool { - switch (this.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: - return true; - default: - return false; - } - } - - /** Tests if this type is of any small signed integer kind. */ - get isSmallSignedInteger(): bool { - switch (this.kind) { - case TypeKind.I8: - case TypeKind.I16: - return true; - default: - return false; - } - } - - /** Tests if this type is of any small unsigned integer kind. */ - get isSmallUnsignedInteger(): bool { - switch (this.kind) { - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: - return true; - default: - return false; - } - } - - /** Tests if this type is of any long integer kind. */ - get isLongInteger(): bool { - switch (this.kind) { - case TypeKind.I64: - case TypeKind.U64: - return true; - case TypeKind.ISIZE: - case TypeKind.USIZE: - return this.size == 64; - default: - return false; - } - } - - /** Tests if this type is of any long signed integer kind. */ - get isLongSignedInteger(): bool { - switch (this.kind) { - case TypeKind.I64: - return true; - case TypeKind.ISIZE: - return this.size == 64; - default: - return false; - } - } - - /** Tests if this type is of any long unsigned integer kind. */ - get isLongUnsignedInteger(): bool { - switch (this.kind) { - case TypeKind.U64: - return true; - case TypeKind.USIZE: - return this.size == 64; - default: - return false; - } - } - - /** Tests if this type is of any size kind, that is `isize` or `usize`. */ - get isAnySize(): bool { - switch (this.kind) { - case TypeKind.ISIZE: - case TypeKind.USIZE: - return true; - default: - return false; - } - } - - /** Tests if this type is of any float kind, i.e., `f32` or `f64`. */ - get isAnyFloat(): bool { - switch (this.kind) { - case TypeKind.F32: - case TypeKind.F64: - return true; - default: - return false; - } + /** Tests if this type has the specified capabilities. */ + is(flags: TypeFlags): bool { + return (this.flags & flags) == flags; } /** Tests if this type is a class type. */ @@ -233,7 +123,7 @@ export class Type { /** Composes a class type from this type and a class. */ asClass(classType: Class): Type { assert(this.kind == TypeKind.USIZE); - var ret = new Type(this.kind, this.size); + var ret = new Type(this.kind, this.flags & ~TypeFlags.VALUE | TypeFlags.REFERENCE, this.size); ret.classType = classType; return ret; } @@ -241,16 +131,17 @@ export class Type { /** Composes a function type from this type and a function. */ asFunction(functionType: Function): Type { assert(this.kind == TypeKind.USIZE && !this.isReference); - var ret = new Type(this.kind, this.size); + var ret = new Type(this.kind, this.flags & ~TypeFlags.VALUE | TypeFlags.REFERENCE, this.size); ret.functionType = functionType; return ret; } /** Composes the respective nullable type of this type. */ asNullable(): Type | null { - assert(this.kind == TypeKind.USIZE && !this.isReference); - if (this.isNullable && !this.nullableType) { - (this.nullableType = new Type(this.kind, this.size)).isNullable = true; + assert(this.kind == TypeKind.USIZE); + if (!this.nullableType) { + assert(!this.is(TypeFlags.NULLABLE) && this.isReference); + this.nullableType = new Type(this.kind, this.flags | TypeFlags.NULLABLE, this.size); this.nullableType.classType = this.classType; this.nullableType.functionType = this.functionType; } @@ -423,37 +314,37 @@ export class Type { // Types /** An 8-bit signed integer. */ - static readonly i8: Type = new Type(TypeKind.I8, 8); + static readonly i8: Type = new Type(TypeKind.I8, TypeFlags.SIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 8); /** A 16-bit signed integer. */ - static readonly i16: Type = new Type(TypeKind.I16, 16); + static readonly i16: Type = new Type(TypeKind.I16, TypeFlags.SIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 16); /** A 32-bit signed integer. */ - static readonly i32: Type = new Type(TypeKind.I32, 32); + static readonly i32: Type = new Type(TypeKind.I32, TypeFlags.SIGNED | TypeFlags.INTEGER | TypeFlags.VALUE, 32); /** A 64-bit signed integer. */ - static readonly i64: Type = new Type(TypeKind.I64, 64); + static readonly i64: Type = new Type(TypeKind.I64, TypeFlags.SIGNED | TypeFlags.LONG | TypeFlags.INTEGER | TypeFlags.VALUE, 64); /** A 32-bit signed size. WASM32 only. */ - static readonly isize32: Type = new Type(TypeKind.ISIZE, 32); + static readonly isize32: Type = new Type(TypeKind.ISIZE, TypeFlags.SIGNED | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 32); /** A 64-bit signed size. WASM64 only. */ - static readonly isize64: Type = new Type(TypeKind.ISIZE, 64); + static readonly isize64: Type = new Type(TypeKind.ISIZE, TypeFlags.SIGNED | TypeFlags.LONG | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 64); /** An 8-bit unsigned integer. */ - static readonly u8: Type = new Type(TypeKind.U8, 8); + static readonly u8: Type = new Type(TypeKind.U8, TypeFlags.UNSIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 8); /** A 16-bit unsigned integer. */ - static readonly u16: Type = new Type(TypeKind.U16, 16); + static readonly u16: Type = new Type(TypeKind.U16, TypeFlags.UNSIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 16); /** A 32-bit unsigned integer. */ - static readonly u32: Type = new Type(TypeKind.U32, 32); + static readonly u32: Type = new Type(TypeKind.U32, TypeFlags.UNSIGNED | TypeFlags.INTEGER | TypeFlags.VALUE, 32); /** A 64-bit unsigned integer. */ - static readonly u64: Type = new Type(TypeKind.U64, 64); + static readonly u64: Type = new Type(TypeKind.U64, TypeFlags.UNSIGNED | TypeFlags.LONG | TypeFlags.INTEGER | TypeFlags.VALUE, 64); /** A 32-bit unsigned size. WASM32 only. */ - static readonly usize32: Type = new Type(TypeKind.USIZE, 32); + static readonly usize32: Type = new Type(TypeKind.USIZE, TypeFlags.UNSIGNED | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 32); /** A 64-bit unsigned size. WASM64 only. */ - static readonly usize64: Type = new Type(TypeKind.USIZE, 64); + static readonly usize64: Type = new Type(TypeKind.USIZE, TypeFlags.UNSIGNED | TypeFlags.LONG | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 64); /** A 1-bit unsigned integer. */ - static readonly bool: Type = new Type(TypeKind.BOOL, 1); + static readonly bool: Type = new Type(TypeKind.BOOL, TypeFlags.UNSIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 1); /** A 32-bit float. */ - static readonly f32: Type = new Type(TypeKind.F32, 32); + static readonly f32: Type = new Type(TypeKind.F32, TypeFlags.SIGNED | TypeFlags.FLOAT | TypeFlags.VALUE, 32); /** A 64-bit float. */ - static readonly f64: Type = new Type(TypeKind.F64, 64); + static readonly f64: Type = new Type(TypeKind.F64, TypeFlags.SIGNED | TypeFlags.LONG | TypeFlags.FLOAT | TypeFlags.VALUE, 64); /** No return type. */ - static readonly void: Type = new Type(TypeKind.VOID, 0); + static readonly void: Type = new Type(TypeKind.VOID, TypeFlags.NONE, 0); } /** Converts an array of types to an array of native types. */ diff --git a/tests/binaryen/optimize-if-eqz.js b/tests/binaryen/optimize-if-eqz.js new file mode 100644 index 00000000..18ea2898 --- /dev/null +++ b/tests/binaryen/optimize-if-eqz.js @@ -0,0 +1,19 @@ +var binaryen = require("binaryen"); + +var mod = new binaryen.Module(); +var funcType = mod.addFunctionType("i", binaryen.i32, [ binaryen.i32 ]); +var func = mod.addFunction("test", funcType, [], + mod.if( + mod.i32.eqz(mod.getLocal(0, binaryen.i32)), + mod.i32.const(0), + mod.getLocal(0, binaryen.i32) + ) +); +mod.addExport("test", "test"); + +console.log(mod.emitText()); +if (!mod.validate()) + console.log("-> does not validate"); + +mod.optimize(); +console.log(mod.emitText()); diff --git a/tests/binaryen/optimize-if-eqz.wast b/tests/binaryen/optimize-if-eqz.wast new file mode 100644 index 00000000..3f04e073 --- /dev/null +++ b/tests/binaryen/optimize-if-eqz.wast @@ -0,0 +1,14 @@ +(module + (type $i (func (param i32) (result i32))) + (memory $0 0) + (export "test" (func $test)) + (func $test (; 0 ;) (type $i) (param $0 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $0) + ) + (i32.const 0) + (get_local $0) + ) + ) +) \ No newline at end of file diff --git a/tests/compiler/fmod.optimized.wast b/tests/compiler/fmod.optimized.wast index 4bd05683..9360d075 100644 --- a/tests/compiler/fmod.optimized.wast +++ b/tests/compiler/fmod.optimized.wast @@ -55,32 +55,38 @@ ) ) (if - (if (result i32) + (i32.and (if (result i32) (tee_local $7 - (i64.eq - (i64.shl - (get_local $5) - (i64.const 1) + (i32.and + (if (result i32) + (tee_local $7 + (i64.eq + (i64.shl + (get_local $5) + (i64.const 1) + ) + (i64.const 0) + ) + ) + (get_local $7) + (f64.ne + (tee_local $9 + (get_local $1) + ) + (get_local $9) + ) ) - (i64.const 0) + (i32.const 1) ) ) (get_local $7) - (tee_local $7 - (f64.ne - (tee_local $9 - (get_local $1) - ) - (get_local $9) - ) + (i32.eq + (get_local $3) + (i32.const 2047) ) ) - (get_local $7) - (i32.eq - (get_local $3) - (i32.const 2047) - ) + (i32.const 1) ) (return (f64.div @@ -434,31 +440,37 @@ ) ) (if - (if (result i32) + (i32.and (if (result i32) (tee_local $3 - (i32.eqz - (i32.shl - (get_local $5) - (i32.const 1) + (i32.and + (if (result i32) + (tee_local $3 + (i32.eqz + (i32.shl + (get_local $5) + (i32.const 1) + ) + ) + ) + (get_local $3) + (f32.ne + (tee_local $8 + (get_local $1) + ) + (get_local $8) + ) ) + (i32.const 1) ) ) (get_local $3) - (tee_local $3 - (f32.ne - (tee_local $8 - (get_local $1) - ) - (get_local $8) - ) + (i32.eq + (get_local $4) + (i32.const 255) ) ) - (get_local $3) - (i32.eq - (get_local $4) - (i32.const 255) - ) + (i32.const 1) ) (return (f32.div diff --git a/tests/compiler/fmod.wast b/tests/compiler/fmod.wast index bc99d431..9fb8771c 100644 --- a/tests/compiler/fmod.wast +++ b/tests/compiler/fmod.wast @@ -70,32 +70,42 @@ ) ) (if - (if (result i32) - (tee_local $8 - (if (result i32) + (i32.and + (if (result i32) + (i32.eqz (tee_local $8 - (i64.eq - (i64.shl - (get_local $3) - (i64.const 1) + (i32.and + (if (result i32) + (i32.eqz + (tee_local $8 + (i64.eq + (i64.shl + (get_local $3) + (i64.const 1) + ) + (i64.const 0) + ) + ) + ) + (f64.ne + (tee_local $7 + (get_local $1) + ) + (get_local $7) + ) + (get_local $8) ) - (i64.const 0) + (i32.const 1) ) ) - (get_local $8) - (f64.ne - (tee_local $7 - (get_local $1) - ) - (get_local $7) - ) ) + (i32.eq + (get_local $4) + (i32.const 2047) + ) + (get_local $8) ) - (get_local $8) - (i32.eq - (get_local $4) - (i32.const 2047) - ) + (i32.const 1) ) (return (f64.div @@ -530,32 +540,42 @@ ) ) (if - (if (result i32) - (tee_local $8 - (if (result i32) + (i32.and + (if (result i32) + (i32.eqz (tee_local $8 - (i32.eq - (i32.shl - (get_local $3) - (i32.const 1) + (i32.and + (if (result i32) + (i32.eqz + (tee_local $8 + (i32.eq + (i32.shl + (get_local $3) + (i32.const 1) + ) + (i32.const 0) + ) + ) + ) + (f32.ne + (tee_local $7 + (get_local $1) + ) + (get_local $7) + ) + (get_local $8) ) - (i32.const 0) + (i32.const 1) ) ) - (get_local $8) - (f32.ne - (tee_local $7 - (get_local $1) - ) - (get_local $7) - ) ) + (i32.eq + (get_local $4) + (i32.const 255) + ) + (get_local $8) ) - (get_local $8) - (i32.eq - (get_local $4) - (i32.const 255) - ) + (i32.const 1) ) (return (f32.div diff --git a/tests/compiler/game-of-life.optimized.wast b/tests/compiler/game-of-life.optimized.wast index 4daed3d6..e3b8f282 100644 --- a/tests/compiler/game-of-life.optimized.wast +++ b/tests/compiler/game-of-life.optimized.wast @@ -209,19 +209,22 @@ ) ) (if - (if (result i32) - (i32.lt_u - (get_local $2) - (i32.const 2) - ) - (i32.lt_u - (get_local $2) - (i32.const 2) - ) - (i32.gt_u - (get_local $2) - (i32.const 3) + (i32.and + (select + (i32.gt_u + (get_local $2) + (i32.const 3) + ) + (i32.lt_u + (get_local $2) + (i32.const 2) + ) + (i32.ge_u + (get_local $2) + (i32.const 2) + ) ) + (i32.const 1) ) (i32.store8 (i32.add diff --git a/tests/compiler/game-of-life.wast b/tests/compiler/game-of-life.wast index 0682552d..459eec86 100644 --- a/tests/compiler/game-of-life.wast +++ b/tests/compiler/game-of-life.wast @@ -235,19 +235,24 @@ ) ) (if - (if (result i32) - (i32.lt_u - (get_local $8) - (i32.const 2) - ) - (i32.lt_u - (get_local $8) - (i32.const 2) - ) - (i32.gt_u - (get_local $8) - (i32.const 3) + (i32.and + (select + (i32.gt_u + (get_local $8) + (i32.const 3) + ) + (i32.lt_u + (get_local $8) + (i32.const 2) + ) + (i32.eqz + (i32.lt_u + (get_local $8) + (i32.const 2) + ) + ) ) + (i32.const 1) ) (i32.store8 (i32.add diff --git a/tests/compiler/logical.wast b/tests/compiler/logical.wast index f5d9c6f6..15b509ca 100644 --- a/tests/compiler/logical.wast +++ b/tests/compiler/logical.wast @@ -13,75 +13,85 @@ (local $1 f64) (drop (if (result i32) + (i32.eqz + (i32.const 0) + ) (i32.const 0) (unreachable) - (i32.const 0) ) ) (drop (if (result f64) - (f64.ne + (f64.eq (f64.const 0) (f64.const 0) ) - (unreachable) (f64.const 0) + (unreachable) ) ) (drop (if (result i32) - (i32.const 1) - (i32.const 1) + (i32.eqz + (i32.const 1) + ) (unreachable) + (i32.const 1) ) ) (drop (if (result f64) - (f64.ne + (f64.eq (f64.const 1) (f64.const 0) ) - (f64.const 1) (unreachable) + (f64.const 1) ) ) (drop (if (result i32) - (tee_local $0 - (if (result i32) - (i32.const 1) - (i32.const 2) - (i32.const 1) + (i32.eqz + (tee_local $0 + (select + (i32.const 1) + (i32.const 2) + (i32.eqz + (i32.const 1) + ) + ) ) ) - (get_local $0) (unreachable) + (get_local $0) ) ) (drop (if (result f64) - (f64.ne + (f64.eq (tee_local $1 - (if (result f64) - (f64.ne + (select + (f64.const 1) + (f64.const 2) + (f64.eq (f64.const 1) (f64.const 0) ) - (f64.const 2) - (f64.const 1) ) ) (f64.const 0) ) - (get_local $1) (unreachable) + (get_local $1) ) ) (set_global $logical/i - (if (result i32) + (select (i32.const 1) (i32.const 2) - (i32.const 1) + (i32.eqz + (i32.const 1) + ) ) ) (if @@ -94,10 +104,12 @@ (unreachable) ) (set_global $logical/i - (if (result i32) - (i32.const 0) - (i32.const 0) + (select (i32.const 1) + (i32.const 0) + (i32.eqz + (i32.const 0) + ) ) ) (if @@ -110,13 +122,12 @@ (unreachable) ) (set_global $logical/I - (if (result i64) - (i64.ne - (i64.const 1) - (i64.const 0) - ) - (i64.const 2) + (select (i64.const 1) + (i64.const 2) + (i64.eqz + (i64.const 1) + ) ) ) (if @@ -129,13 +140,12 @@ (unreachable) ) (set_global $logical/I - (if (result i64) - (i64.ne - (i64.const 0) + (select + (i64.const 1) + (i64.const 0) + (i64.eqz (i64.const 0) ) - (i64.const 0) - (i64.const 1) ) ) (if @@ -148,13 +158,13 @@ (unreachable) ) (set_global $logical/f - (if (result f32) - (f32.ne + (select + (f32.const 1) + (f32.const 2) + (f32.eq (f32.const 1) (f32.const 0) ) - (f32.const 2) - (f32.const 1) ) ) (if @@ -167,13 +177,13 @@ (unreachable) ) (set_global $logical/f - (if (result f32) - (f32.ne + (select + (f32.const 1) + (f32.const 0) + (f32.eq (f32.const 0) (f32.const 0) ) - (f32.const 0) - (f32.const 1) ) ) (if @@ -186,13 +196,13 @@ (unreachable) ) (set_global $logical/F - (if (result f64) - (f64.ne + (select + (f64.const 1) + (f64.const 2) + (f64.eq (f64.const 1) (f64.const 0) ) - (f64.const 2) - (f64.const 1) ) ) (if @@ -205,13 +215,13 @@ (unreachable) ) (set_global $logical/F - (if (result f64) - (f64.ne + (select + (f64.const 1) + (f64.const 0) + (f64.eq (f64.const 0) (f64.const 0) ) - (f64.const 0) - (f64.const 1) ) ) (if diff --git a/tests/compiler/memcpy.optimized.wast b/tests/compiler/memcpy.optimized.wast index 3edef66f..9702bae9 100644 --- a/tests/compiler/memcpy.optimized.wast +++ b/tests/compiler/memcpy.optimized.wast @@ -18,13 +18,13 @@ ) (loop $continue|0 (if - (if (result i32) - (get_local $2) + (select (i32.rem_u (get_local $3) (i32.const 4) ) (get_local $2) + (get_local $2) ) (block (set_local $4 diff --git a/tests/compiler/memcpy.wast b/tests/compiler/memcpy.wast index b7721a09..35465c2e 100644 --- a/tests/compiler/memcpy.wast +++ b/tests/compiler/memcpy.wast @@ -26,13 +26,15 @@ (block $break|0 (loop $continue|0 (if - (if (result i32) + (select (get_local $2) (i32.rem_u (get_local $4) (i32.const 4) ) - (get_local $2) + (i32.eqz + (get_local $2) + ) ) (block (block diff --git a/tests/compiler/showcase.optimized-inlined.wast b/tests/compiler/showcase.optimized-inlined.wast index abfbfd38..e8b98af9 100644 --- a/tests/compiler/showcase.optimized-inlined.wast +++ b/tests/compiler/showcase.optimized-inlined.wast @@ -65,13 +65,13 @@ ) (loop $continue|0 (if - (if (result i32) - (get_local $2) + (select (i32.rem_u (get_local $3) (i32.const 4) ) (get_local $2) + (get_local $2) ) (block (set_local $4 @@ -1687,32 +1687,38 @@ ) ) (if - (if (result i32) + (i32.and (if (result i32) (tee_local $7 - (i64.eq - (i64.shl - (get_local $5) - (i64.const 1) + (i32.and + (if (result i32) + (tee_local $7 + (i64.eq + (i64.shl + (get_local $5) + (i64.const 1) + ) + (i64.const 0) + ) + ) + (get_local $7) + (f64.ne + (tee_local $9 + (get_local $1) + ) + (get_local $9) + ) ) - (i64.const 0) + (i32.const 1) ) ) (get_local $7) - (tee_local $7 - (f64.ne - (tee_local $9 - (get_local $1) - ) - (get_local $9) - ) + (i32.eq + (get_local $3) + (i32.const 2047) ) ) - (get_local $7) - (i32.eq - (get_local $3) - (i32.const 2047) - ) + (i32.const 1) ) (return (f64.div @@ -2066,31 +2072,37 @@ ) ) (if - (if (result i32) + (i32.and (if (result i32) (tee_local $3 - (i32.eqz - (i32.shl - (get_local $5) - (i32.const 1) + (i32.and + (if (result i32) + (tee_local $3 + (i32.eqz + (i32.shl + (get_local $5) + (i32.const 1) + ) + ) + ) + (get_local $3) + (f32.ne + (tee_local $8 + (get_local $1) + ) + (get_local $8) + ) ) + (i32.const 1) ) ) (get_local $3) - (tee_local $3 - (f32.ne - (tee_local $8 - (get_local $1) - ) - (get_local $8) - ) + (i32.eq + (get_local $4) + (i32.const 255) ) ) - (get_local $3) - (i32.eq - (get_local $4) - (i32.const 255) - ) + (i32.const 1) ) (return (f32.div diff --git a/tests/compiler/showcase.optimized.wast b/tests/compiler/showcase.optimized.wast index ae421719..9db1dfac 100644 --- a/tests/compiler/showcase.optimized.wast +++ b/tests/compiler/showcase.optimized.wast @@ -86,13 +86,13 @@ ) (loop $continue|0 (if - (if (result i32) - (get_local $2) + (select (i32.rem_u (get_local $3) (i32.const 4) ) (get_local $2) + (get_local $2) ) (block (set_local $4 @@ -1708,32 +1708,38 @@ ) ) (if - (if (result i32) + (i32.and (if (result i32) (tee_local $7 - (i64.eq - (i64.shl - (get_local $5) - (i64.const 1) + (i32.and + (if (result i32) + (tee_local $7 + (i64.eq + (i64.shl + (get_local $5) + (i64.const 1) + ) + (i64.const 0) + ) + ) + (get_local $7) + (f64.ne + (tee_local $9 + (get_local $1) + ) + (get_local $9) + ) ) - (i64.const 0) + (i32.const 1) ) ) (get_local $7) - (tee_local $7 - (f64.ne - (tee_local $9 - (get_local $1) - ) - (get_local $9) - ) + (i32.eq + (get_local $3) + (i32.const 2047) ) ) - (get_local $7) - (i32.eq - (get_local $3) - (i32.const 2047) - ) + (i32.const 1) ) (return (f64.div @@ -2087,31 +2093,37 @@ ) ) (if - (if (result i32) + (i32.and (if (result i32) (tee_local $3 - (i32.eqz - (i32.shl - (get_local $5) - (i32.const 1) + (i32.and + (if (result i32) + (tee_local $3 + (i32.eqz + (i32.shl + (get_local $5) + (i32.const 1) + ) + ) + ) + (get_local $3) + (f32.ne + (tee_local $8 + (get_local $1) + ) + (get_local $8) + ) ) + (i32.const 1) ) ) (get_local $3) - (tee_local $3 - (f32.ne - (tee_local $8 - (get_local $1) - ) - (get_local $8) - ) + (i32.eq + (get_local $4) + (i32.const 255) ) ) - (get_local $3) - (i32.eq - (get_local $4) - (i32.const 255) - ) + (i32.const 1) ) (return (f32.div diff --git a/tests/compiler/showcase.wast b/tests/compiler/showcase.wast index efba2d99..d2c767ac 100644 --- a/tests/compiler/showcase.wast +++ b/tests/compiler/showcase.wast @@ -134,13 +134,15 @@ (block $break|0 (loop $continue|0 (if - (if (result i32) + (select (get_local $2) (i32.rem_u (get_local $4) (i32.const 4) ) - (get_local $2) + (i32.eqz + (get_local $2) + ) ) (block (block @@ -1988,32 +1990,42 @@ ) ) (if - (if (result i32) - (tee_local $8 - (if (result i32) + (i32.and + (if (result i32) + (i32.eqz (tee_local $8 - (i64.eq - (i64.shl - (get_local $3) - (i64.const 1) + (i32.and + (if (result i32) + (i32.eqz + (tee_local $8 + (i64.eq + (i64.shl + (get_local $3) + (i64.const 1) + ) + (i64.const 0) + ) + ) + ) + (f64.ne + (tee_local $7 + (get_local $1) + ) + (get_local $7) + ) + (get_local $8) ) - (i64.const 0) + (i32.const 1) ) ) - (get_local $8) - (f64.ne - (tee_local $7 - (get_local $1) - ) - (get_local $7) - ) ) + (i32.eq + (get_local $4) + (i32.const 2047) + ) + (get_local $8) ) - (get_local $8) - (i32.eq - (get_local $4) - (i32.const 2047) - ) + (i32.const 1) ) (return (f64.div @@ -2448,32 +2460,42 @@ ) ) (if - (if (result i32) - (tee_local $8 - (if (result i32) + (i32.and + (if (result i32) + (i32.eqz (tee_local $8 - (i32.eq - (i32.shl - (get_local $3) - (i32.const 1) + (i32.and + (if (result i32) + (i32.eqz + (tee_local $8 + (i32.eq + (i32.shl + (get_local $3) + (i32.const 1) + ) + (i32.const 0) + ) + ) + ) + (f32.ne + (tee_local $7 + (get_local $1) + ) + (get_local $7) + ) + (get_local $8) ) - (i32.const 0) + (i32.const 1) ) ) - (get_local $8) - (f32.ne - (tee_local $7 - (get_local $1) - ) - (get_local $7) - ) ) + (i32.eq + (get_local $4) + (i32.const 255) + ) + (get_local $8) ) - (get_local $8) - (i32.eq - (get_local $4) - (i32.const 255) - ) + (i32.const 1) ) (return (f32.div @@ -4209,75 +4231,85 @@ ) (drop (if (result i32) + (i32.eqz + (i32.const 0) + ) (i32.const 0) (unreachable) - (i32.const 0) ) ) (drop (if (result f64) - (f64.ne + (f64.eq (f64.const 0) (f64.const 0) ) - (unreachable) (f64.const 0) + (unreachable) ) ) (drop (if (result i32) - (i32.const 1) - (i32.const 1) + (i32.eqz + (i32.const 1) + ) (unreachable) + (i32.const 1) ) ) (drop (if (result f64) - (f64.ne + (f64.eq (f64.const 1) (f64.const 0) ) - (f64.const 1) (unreachable) + (f64.const 1) ) ) (drop (if (result i32) - (tee_local $0 - (if (result i32) - (i32.const 1) - (i32.const 2) - (i32.const 1) + (i32.eqz + (tee_local $0 + (select + (i32.const 1) + (i32.const 2) + (i32.eqz + (i32.const 1) + ) + ) ) ) - (get_local $0) (unreachable) + (get_local $0) ) ) (drop (if (result f64) - (f64.ne + (f64.eq (tee_local $3 - (if (result f64) - (f64.ne + (select + (f64.const 1) + (f64.const 2) + (f64.eq (f64.const 1) (f64.const 0) ) - (f64.const 2) - (f64.const 1) ) ) (f64.const 0) ) - (get_local $3) (unreachable) + (get_local $3) ) ) (set_global $logical/i - (if (result i32) + (select (i32.const 1) (i32.const 2) - (i32.const 1) + (i32.eqz + (i32.const 1) + ) ) ) (if @@ -4290,10 +4322,12 @@ (unreachable) ) (set_global $logical/i - (if (result i32) - (i32.const 0) - (i32.const 0) + (select (i32.const 1) + (i32.const 0) + (i32.eqz + (i32.const 0) + ) ) ) (if @@ -4306,13 +4340,12 @@ (unreachable) ) (set_global $logical/I - (if (result i64) - (i64.ne - (i64.const 1) - (i64.const 0) - ) - (i64.const 2) + (select (i64.const 1) + (i64.const 2) + (i64.eqz + (i64.const 1) + ) ) ) (if @@ -4325,13 +4358,12 @@ (unreachable) ) (set_global $logical/I - (if (result i64) - (i64.ne - (i64.const 0) + (select + (i64.const 1) + (i64.const 0) + (i64.eqz (i64.const 0) ) - (i64.const 0) - (i64.const 1) ) ) (if @@ -4344,13 +4376,13 @@ (unreachable) ) (set_global $logical/f - (if (result f32) - (f32.ne + (select + (f32.const 1) + (f32.const 2) + (f32.eq (f32.const 1) (f32.const 0) ) - (f32.const 2) - (f32.const 1) ) ) (if @@ -4363,13 +4395,13 @@ (unreachable) ) (set_global $logical/f - (if (result f32) - (f32.ne + (select + (f32.const 1) + (f32.const 0) + (f32.eq (f32.const 0) (f32.const 0) ) - (f32.const 0) - (f32.const 1) ) ) (if @@ -4382,13 +4414,13 @@ (unreachable) ) (set_global $logical/F - (if (result f64) - (f64.ne + (select + (f64.const 1) + (f64.const 2) + (f64.eq (f64.const 1) (f64.const 0) ) - (f64.const 2) - (f64.const 1) ) ) (if @@ -4401,13 +4433,13 @@ (unreachable) ) (set_global $logical/F - (if (result f64) - (f64.ne + (select + (f64.const 1) + (f64.const 0) + (f64.eq (f64.const 0) (f64.const 0) ) - (f64.const 0) - (f64.const 1) ) ) (if diff --git a/tests/compiler/std/heap.optimized.wast b/tests/compiler/std/heap.optimized.wast index d9de4fb8..33ca90f5 100644 --- a/tests/compiler/std/heap.optimized.wast +++ b/tests/compiler/std/heap.optimized.wast @@ -472,13 +472,13 @@ ) (loop $continue|0 (if - (if (result i32) - (get_local $2) + (select (i32.rem_u (get_local $1) (i32.const 4) ) (get_local $2) + (get_local $2) ) (block (set_local $4 diff --git a/tests/compiler/std/heap.wast b/tests/compiler/std/heap.wast index 2f5238fd..af88ec58 100644 --- a/tests/compiler/std/heap.wast +++ b/tests/compiler/std/heap.wast @@ -527,13 +527,15 @@ (block $break|0 (loop $continue|0 (if - (if (result i32) + (select (get_local $2) (i32.rem_u (get_local $1) (i32.const 4) ) - (get_local $2) + (i32.eqz + (get_local $2) + ) ) (block (block @@ -2335,6 +2337,9 @@ (loop $continue|0 (if (if (result i32) + (i32.eqz + (get_local $2) + ) (get_local $2) (i32.eq (i32.load8_u @@ -2344,7 +2349,6 @@ (get_local $1) ) ) - (get_local $2) ) (block (block diff --git a/tests/compiler/while.wast b/tests/compiler/while.wast index 0efdca79..be1c8824 100644 --- a/tests/compiler/while.wast +++ b/tests/compiler/while.wast @@ -160,20 +160,23 @@ (loop $continue|3 (if (if (result i32) - (tee_local $0 - (block (result i32) - (set_local $0 - (get_global $while/n) - ) - (set_global $while/n - (i32.sub - (get_local $0) - (i32.const 1) + (i32.eqz + (tee_local $0 + (block (result i32) + (set_local $0 + (get_global $while/n) ) + (set_global $while/n + (i32.sub + (get_local $0) + (i32.const 1) + ) + ) + (get_local $0) ) - (get_local $0) ) ) + (get_local $0) (block (result i32) (set_global $while/m (i32.add @@ -183,7 +186,6 @@ ) (get_global $while/m) ) - (get_local $0) ) (block (nop)