diff --git a/src/builtins.ts b/src/builtins.ts index d1d498358d..c0478a6dbc 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -2070,11 +2070,8 @@ function builtin_store(ctx: BuiltinContext): ExpressionRef { inType.size < type.size // int to larger int (clear garbage bits) ) ) { - arg1 = compiler.convertExpression(arg1, - inType, type, - false, false, // still clears garbage bits when not wrapping - operands[1] - ); + // either conversion or memory operation clears garbage bits + arg1 = compiler.convertExpression(arg1, inType, type, false, operands[1]); inType = type; } var immOffset = 0; @@ -2102,8 +2099,9 @@ builtins.set(BuiltinNames.store, builtin_store); function builtin_add(ctx: BuiltinContext): ExpressionRef { var compiler = ctx.compiler; var module = compiler.module; - if (checkTypeOptional(ctx, true) | checkArgsRequired(ctx, 2)) + if (checkTypeOptional(ctx, true) | checkArgsRequired(ctx, 2)) { return module.unreachable(); + } var operands = ctx.operands; var typeArguments = ctx.typeArguments; var left = operands[0]; @@ -2111,9 +2109,9 @@ function builtin_add(ctx: BuiltinContext): ExpressionRef { ? compiler.compileExpression( left, typeArguments[0], - Constraints.CONV_IMPLICIT | Constraints.MUST_WRAP + Constraints.CONV_IMPLICIT ) - : compiler.compileExpression(operands[0], Type.auto, Constraints.MUST_WRAP); + : compiler.compileExpression(operands[0], Type.auto); var type = compiler.currentType; if (type.isValue) { let arg1: ExpressionRef; @@ -2121,62 +2119,24 @@ function builtin_add(ctx: BuiltinContext): ExpressionRef { // prefer right type arg1 = compiler.compileExpression( operands[1], - type, - Constraints.MUST_WRAP + type ); if (compiler.currentType != type) { arg0 = compiler.compileExpression( left, (type = compiler.currentType), - Constraints.CONV_IMPLICIT | Constraints.MUST_WRAP + Constraints.CONV_IMPLICIT ); } } else { arg1 = compiler.compileExpression( operands[1], type, - Constraints.CONV_IMPLICIT | Constraints.MUST_WRAP + Constraints.CONV_IMPLICIT ); } - let op: BinaryOp = -1; - switch (type.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: { - return compiler.ensureSmallIntegerWrap( - module.binary(BinaryOp.AddI32, arg0, arg1), - type - ); - } - case TypeKind.I32: - case TypeKind.U32: - { - op = BinaryOp.AddI32; - break; - } - case TypeKind.I64: - case TypeKind.U64: { - op = BinaryOp.AddI64; - break; - } - case TypeKind.ISIZE: - case TypeKind.USIZE: { - op = compiler.options.isWasm64 ? BinaryOp.AddI64 : BinaryOp.AddI32; - break; - } - case TypeKind.F32: { - op = BinaryOp.AddF32; - break; - } - case TypeKind.F64: { - op = BinaryOp.AddF64; - break; - } - } - if (op != -1) { - return module.binary(op, arg0, arg1); + if (type.isNumericValue) { + return compiler.makeAdd(arg0, arg1, type); } } compiler.error( @@ -2193,8 +2153,9 @@ builtins.set(BuiltinNames.add, builtin_add); function builtin_sub(ctx: BuiltinContext): ExpressionRef { var compiler = ctx.compiler; var module = compiler.module; - if (checkTypeOptional(ctx, true) | checkArgsRequired(ctx, 2)) + if (checkTypeOptional(ctx, true) | checkArgsRequired(ctx, 2)) { return module.unreachable(); + } var operands = ctx.operands; var typeArguments = ctx.typeArguments; var left = operands[0]; @@ -2202,9 +2163,9 @@ function builtin_sub(ctx: BuiltinContext): ExpressionRef { ? compiler.compileExpression( left, typeArguments[0], - Constraints.CONV_IMPLICIT | Constraints.MUST_WRAP + Constraints.CONV_IMPLICIT ) - : compiler.compileExpression(operands[0], Type.auto, Constraints.MUST_WRAP); + : compiler.compileExpression(operands[0], Type.auto); var type = compiler.currentType; if (type.isValue) { let arg1: ExpressionRef; @@ -2212,62 +2173,24 @@ function builtin_sub(ctx: BuiltinContext): ExpressionRef { // prefer right type arg1 = compiler.compileExpression( operands[1], - type, - Constraints.MUST_WRAP + type ); if (compiler.currentType != type) { arg0 = compiler.compileExpression( left, (type = compiler.currentType), - Constraints.CONV_IMPLICIT | Constraints.MUST_WRAP + Constraints.CONV_IMPLICIT ); } } else { arg1 = compiler.compileExpression( operands[1], type, - Constraints.CONV_IMPLICIT | Constraints.MUST_WRAP + Constraints.CONV_IMPLICIT ); } - let op: BinaryOp = -1; - switch (type.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: { - return compiler.ensureSmallIntegerWrap( - module.binary(BinaryOp.SubI32, arg0, arg1), - type - ); - } - case TypeKind.I32: - case TypeKind.U32: - { - op = BinaryOp.SubI32; - break; - } - case TypeKind.I64: - case TypeKind.U64: { - op = BinaryOp.SubI64; - break; - } - case TypeKind.ISIZE: - case TypeKind.USIZE: { - op = compiler.options.isWasm64 ? BinaryOp.SubI64 : BinaryOp.SubI32; - break; - } - case TypeKind.F32: { - op = BinaryOp.SubF32; - break; - } - case TypeKind.F64: { - op = BinaryOp.SubF64; - break; - } - } - if (op != -1) { - return module.binary(op, arg0, arg1); + if (type.isNumericValue) { + return compiler.makeSub(arg0, arg1, type); } } compiler.error( @@ -2284,8 +2207,9 @@ builtins.set(BuiltinNames.sub, builtin_sub); function builtin_mul(ctx: BuiltinContext): ExpressionRef { var compiler = ctx.compiler; var module = compiler.module; - if (checkTypeOptional(ctx, true) | checkArgsRequired(ctx, 2)) + if (checkTypeOptional(ctx, true) | checkArgsRequired(ctx, 2)) { return module.unreachable(); + } var operands = ctx.operands; var typeArguments = ctx.typeArguments; var left = operands[0]; @@ -2293,9 +2217,9 @@ function builtin_mul(ctx: BuiltinContext): ExpressionRef { ? compiler.compileExpression( left, typeArguments[0], - Constraints.CONV_IMPLICIT | Constraints.MUST_WRAP + Constraints.CONV_IMPLICIT ) - : compiler.compileExpression(operands[0], Type.auto, Constraints.MUST_WRAP); + : compiler.compileExpression(operands[0], Type.auto); var type = compiler.currentType; if (type.isValue) { let arg1: ExpressionRef; @@ -2303,62 +2227,24 @@ function builtin_mul(ctx: BuiltinContext): ExpressionRef { // prefer right type arg1 = compiler.compileExpression( operands[1], - type, - Constraints.MUST_WRAP + type ); if (compiler.currentType != type) { arg0 = compiler.compileExpression( left, (type = compiler.currentType), - Constraints.CONV_IMPLICIT | Constraints.MUST_WRAP + Constraints.CONV_IMPLICIT ); } } else { arg1 = compiler.compileExpression( operands[1], type, - Constraints.CONV_IMPLICIT | Constraints.MUST_WRAP + Constraints.CONV_IMPLICIT ); } - let op: BinaryOp = -1; - switch (type.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: { - return compiler.ensureSmallIntegerWrap( - module.binary(BinaryOp.MulI32, arg0, arg1), - type - ); - } - case TypeKind.I32: - case TypeKind.U32: - { - op = BinaryOp.MulI32; - break; - } - case TypeKind.I64: - case TypeKind.U64: { - op = BinaryOp.MulI64; - break; - } - case TypeKind.ISIZE: - case TypeKind.USIZE: { - op = compiler.options.isWasm64 ? BinaryOp.MulI64 : BinaryOp.MulI32; - break; - } - case TypeKind.F32: { - op = BinaryOp.MulF32; - break; - } - case TypeKind.F64: { - op = BinaryOp.MulF64; - break; - } - } - if (op != -1) { - return module.binary(op, arg0, arg1); + if (type.isNumericValue) { + return compiler.makeMul(arg0, arg1, type); } } compiler.error( @@ -2458,11 +2344,8 @@ function builtin_atomic_store(ctx: BuiltinContext): ExpressionRef { inType.size < type.size // int to larger int (clear garbage bits) ) ) { - arg1 = compiler.convertExpression(arg1, - inType, type, - false, false, // still clears garbage bits when not wrapping - operands[1] - ); + // either conversion or memory operation clears garbage bits + arg1 = compiler.convertExpression(arg1, inType, type, false, operands[1]); inType = type; } var immOffset = operands.length == 3 ? evaluateImmediateOffset(operands[2], compiler) : 0; // reports @@ -2519,11 +2402,8 @@ function builtin_atomic_binary(ctx: BuiltinContext, op: AtomicRMWOp, opName: str inType.size < type.size // int to larger int (clear garbage bits) ) ) { - arg1 = compiler.convertExpression(arg1, - inType, type, - false, false, // still clears garbage bits when not wrapping - operands[1] - ); + // either conversion or memory operation clears garbage bits + arg1 = compiler.convertExpression(arg1, inType, type, false, operands[1]); inType = type; } var immOffset = operands.length == 3 ? evaluateImmediateOffset(operands[2], compiler) : 0; // reports @@ -2619,16 +2499,9 @@ function builtin_atomic_cmpxchg(ctx: BuiltinContext): ExpressionRef { inType.size < type.size // int to larger int (clear garbage bits) ) ) { - arg1 = compiler.convertExpression(arg1, - inType, type, - false, false, // still clears garbage bits when not wrapping - operands[1] - ); - arg2 = compiler.convertExpression(arg2, - inType, type, - false, false, // still clears garbage bits when not wrapping - operands[2] - ); + // either conversion or memory operation clears garbage bits + arg1 = compiler.convertExpression(arg1, inType, type, false, operands[1]); + arg2 = compiler.convertExpression(arg2, inType, type, false, operands[2]); inType = type; } var immOffset = operands.length == 4 ? evaluateImmediateOffset(operands[3], compiler) : 0; // reports diff --git a/src/compiler.ts b/src/compiler.ts index 7063a56113..01914289ac 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -3544,12 +3544,10 @@ export class Compiler extends DiagnosticEmitter { var wrap = (constraints & Constraints.MUST_WRAP) != 0; if (currentType != contextualType.nonNullableType) { // allow assigning non-nullable to nullable if (constraints & Constraints.CONV_EXPLICIT) { - expr = this.convertExpression(expr, currentType, contextualType, true, wrap, expression); - wrap = false; + expr = this.convertExpression(expr, currentType, contextualType, true, expression); this.currentType = contextualType; } else if (constraints & Constraints.CONV_IMPLICIT) { - expr = this.convertExpression(expr, currentType, contextualType, false, wrap, expression); - wrap = false; + expr = this.convertExpression(expr, currentType, contextualType, false, expression); this.currentType = contextualType; } } @@ -3586,16 +3584,16 @@ export class Compiler extends DiagnosticEmitter { return expr; } + /** Converts an expression's result from one type to another. */ convertExpression( expr: ExpressionRef, /** Original type. */ fromType: Type, /** New type. */ toType: Type, - /** Whether the conversion is explicit.*/ + /** Whether the conversion is explicit. */ explicit: bool, - /** Whether the result should be wrapped, if a small integer. */ - wrap: bool, + /** Report node. */ reportNode: Node ): ExpressionRef { var module = this.module; @@ -3695,7 +3693,6 @@ export class Compiler extends DiagnosticEmitter { if (fromType.kind == TypeKind.F32) { if (toType.isBooleanValue) { expr = this.makeIsTrueish(expr, Type.f32, reportNode); - wrap = false; } else if (toType.isSignedIntegerValue) { if (toType.isLongIntegerValue) { expr = module.unary(UnaryOp.TruncF32ToI64, expr); @@ -3714,7 +3711,6 @@ export class Compiler extends DiagnosticEmitter { } else { if (toType.isBooleanValue) { expr = this.makeIsTrueish(expr, Type.f64, reportNode); - wrap = false; } else if (toType.isSignedIntegerValue) { if (toType.isLongIntegerValue) { expr = module.unary(UnaryOp.TruncF64ToI64, expr); @@ -3784,7 +3780,6 @@ export class Compiler extends DiagnosticEmitter { // i64 to i32 or smaller if (toType.isBooleanValue) { expr = module.binary(BinaryOp.NeI64, expr, module.i64(0)); - wrap = false; } else if (!toType.isLongIntegerValue) { expr = module.unary(UnaryOp.WrapI64, expr); // discards upper bits } @@ -3795,7 +3790,6 @@ export class Compiler extends DiagnosticEmitter { fromType.isSignedIntegerValue ? UnaryOp.ExtendI32 : UnaryOp.ExtendU32, this.ensureSmallIntegerWrap(expr, fromType) // must clear garbage bits ); - wrap = false; // i32 to i32 } else { @@ -3804,7 +3798,6 @@ export class Compiler extends DiagnosticEmitter { // small i32 to larger i32 if (fromType.size < toType.size) { expr = this.ensureSmallIntegerWrap(expr, fromType); // must clear garbage bits - wrap = false; } // same size } else { @@ -3819,9 +3812,7 @@ export class Compiler extends DiagnosticEmitter { } this.currentType = toType; - return wrap - ? this.ensureSmallIntegerWrap(expr, toType) - : expr; + return expr; } private compileAssertionExpression( @@ -3922,18 +3913,11 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "<", leftType.toString() - ); - return this.module.unreachable(); - } rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, true); - if (!commonType) { + if (!commonType || !commonType.isNumericValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, "<", leftType.toString(), rightType.toString() @@ -3941,71 +3925,13 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } - leftExpr = this.convertExpression(leftExpr, - leftType, leftType = commonType, - false, true, // ! - left - ); - rightExpr = this.convertExpression(rightExpr, - rightType, rightType = commonType, - false, true, // ! - right - ); - switch (commonType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: { - expr = module.binary(BinaryOp.LtI32, leftExpr, rightExpr); - break; - } - case TypeKind.I64: { - expr = module.binary(BinaryOp.LtI64, leftExpr, rightExpr); - break; - } - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.LtI64 - : BinaryOp.LtI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.U32: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.LtU32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.LtU64 - : BinaryOp.LtU32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.U64: { - expr = module.binary(BinaryOp.LtU64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.LtF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.LtF64, leftExpr, rightExpr); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; + + expr = this.makeLt(leftExpr, rightExpr, commonType); this.currentType = Type.bool; break; } @@ -4022,31 +3948,11 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, ">", leftType.toString() - ); - return this.module.unreachable(); - } rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, true); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, true, // ! - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, true, // ! - right - ); - rightType = commonType; - } else { + if (!commonType || !commonType.isNumericValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, ">", leftType.toString(), rightType.toString() @@ -4054,61 +3960,13 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } - switch (commonType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: { - expr = module.binary(BinaryOp.GtI32, leftExpr, rightExpr); - break; - } - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.GtI64 - : BinaryOp.GtI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: { - expr = module.binary(BinaryOp.GtI64, leftExpr, rightExpr); - break; - } - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.U32: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.GtU32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.GtU64 - : BinaryOp.GtU32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.U64: { - expr = module.binary(BinaryOp.GtU64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.GtF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.GtF64, leftExpr, rightExpr); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; + + expr = this.makeGt(leftExpr, rightExpr, commonType); this.currentType = Type.bool; break; } @@ -4125,28 +3983,11 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "<=", leftType.toString() - ); - return this.module.unreachable(); - } rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; - if (commonType = Type.commonDenominator(leftType, rightType, true)) { - leftExpr = this.convertExpression(leftExpr, - leftType, leftType = commonType, - false, true, // ! - left - ); - rightExpr = this.convertExpression(rightExpr, - rightType, rightType = commonType, - false, true, // ! - right - ); - } else { + commonType = Type.commonDenominator(leftType, rightType, true); + if (!commonType || !commonType.isNumericValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, "<=", leftType.toString(), rightType.toString() @@ -4154,61 +3995,13 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } - switch (commonType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: { - expr = module.binary(BinaryOp.LeI32, leftExpr, rightExpr); - break; - } - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.LeI64 - : BinaryOp.LeI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: { - expr = module.binary(BinaryOp.LeI64, leftExpr, rightExpr); - break; - } - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.U32: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.LeU32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.LeU64 - : BinaryOp.LeU32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.U64: { - expr = module.binary(BinaryOp.LeU64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.LeF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.LeF64, leftExpr, rightExpr); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; + + expr = this.makeLe(leftExpr, rightExpr, commonType); this.currentType = Type.bool; break; } @@ -4225,28 +4018,11 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, ">=", leftType.toString() - ); - return this.module.unreachable(); - } rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; - if (commonType = Type.commonDenominator(leftType, rightType, true)) { - leftExpr = this.convertExpression(leftExpr, - leftType, leftType = commonType, - false, true, // ! - left - ); - rightExpr = this.convertExpression(rightExpr, - rightType, rightType = commonType, - false, true, // ! - right - ); - } else { + commonType = Type.commonDenominator(leftType, rightType, true); + if (!commonType || !commonType.isNumericValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, ">=", leftType.toString(), rightType.toString() @@ -4254,72 +4030,19 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } - switch (commonType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: { - expr = module.binary(BinaryOp.GeI32, leftExpr, rightExpr); - break; - } - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.GeI64 - : BinaryOp.GeI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: { - expr = module.binary(BinaryOp.GeI64, leftExpr, rightExpr); - break; - } - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.U32: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.GeU32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.GeU64 - : BinaryOp.GeU32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.U64: { - expr = module.binary(BinaryOp.GeU64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.GeF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.GeF64, leftExpr, rightExpr); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; + + expr = this.makeGe(leftExpr, rightExpr, commonType); this.currentType = Type.bool; break; } case Token.EQUALS_EQUALS_EQUALS: case Token.EQUALS_EQUALS: { - - // NOTE that this favors correctness, in terms of emitting a binary expression, over - // checking for a possible use of unary EQZ. while the most classic of all optimizations, - // that's not what the source told us to do. for reference, `!left` emits unary EQZ. - leftExpr = this.compileExpression(left, contextualType); leftType = this.currentType; @@ -4339,20 +4062,7 @@ export class Compiler extends DiagnosticEmitter { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, false); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, true, // ! - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, true, // ! - right - ); - rightType = commonType; - } else { + if (!commonType) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, operatorTokenToString(expression.operator), leftType.toString(), rightType.toString() @@ -4360,65 +4070,13 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } - switch (commonType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.U32: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.EqI32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.EqI64 - : BinaryOp.EqI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.EqI64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.EqF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.EqF64, leftExpr, rightExpr); - break; - } - case TypeKind.V128: { - expr = module.unary(UnaryOp.AllTrueI8x16, - module.binary(BinaryOp.EqI8x16, leftExpr, rightExpr) - ); - break; - } - case TypeKind.FUNCREF: - case TypeKind.EXTERNREF: - case TypeKind.EXNREF: - case TypeKind.ANYREF: { - this.error( - DiagnosticCode.Operation_0_cannot_be_applied_to_type_1, - expression.range, - "ref.eq", - commonType.toString() - ); - expr = module.unreachable(); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; + + expr = this.makeEq(leftExpr, rightExpr, commonType, expression); this.currentType = Type.bool; break; } @@ -4443,20 +4101,7 @@ export class Compiler extends DiagnosticEmitter { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, false); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, true, // ! - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, true, // ! - right - ); - rightType = commonType; - } else { + if (!commonType) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, operatorTokenToString(expression.operator), leftType.toString(), rightType.toString() @@ -4464,65 +4109,13 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } - switch (commonType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.U32: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.NeI32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.NeI64 - : BinaryOp.NeI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.NeI64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.NeF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.NeF64, leftExpr, rightExpr); - break; - } - case TypeKind.V128: { - expr = module.unary(UnaryOp.AnyTrueI8x16, - module.binary(BinaryOp.NeI8x16, leftExpr, rightExpr) - ); - break; - } - case TypeKind.FUNCREF: - case TypeKind.EXTERNREF: - case TypeKind.EXNREF: - case TypeKind.ANYREF: { - this.error( - DiagnosticCode.Operation_0_cannot_be_applied_to_type_1, - expression.range, - "ref.eq", - commonType.toString() - ); - expr = module.unreachable(); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; + + expr = this.makeNe(leftExpr, rightExpr, commonType, expression); this.currentType = Type.bool; break; } @@ -4543,31 +4136,21 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "+", leftType.toString() - ); - return this.module.unreachable(); - } - if (compound) { + if (!leftType.isNumericValue) { + this.error( + DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, + expression.range, "+", leftType.toString() + ); + return module.unreachable(); + } rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); + rightType = commonType = this.currentType; } else { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; - if (commonType = Type.commonDenominator(leftType, rightType, false)) { - leftExpr = this.convertExpression(leftExpr, - leftType, leftType = commonType, - false, false, - left - ); - rightExpr = this.convertExpression(rightExpr, - rightType, rightType = commonType, - false, false, - right - ); - } else { + commonType = Type.commonDenominator(leftType, rightType, false); + if (!commonType || !commonType.isNumericValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, "+", leftType.toString(), rightType.toString() @@ -4575,47 +4158,12 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; } - switch (this.currentType.kind) { - case TypeKind.I8: // addition might overflow - case TypeKind.I16: // ^ - case TypeKind.U8: // ^ - case TypeKind.U16: // ^ - case TypeKind.BOOL: // ^ - case TypeKind.I32: - case TypeKind.U32: { - expr = module.binary(BinaryOp.AddI32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.AddI64 - : BinaryOp.AddI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.AddI64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.AddF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.AddF64, leftExpr, rightExpr); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + expr = this.makeAdd(leftExpr, rightExpr, commonType); break; } case Token.MINUS_EQUALS: compound = true; @@ -4632,35 +4180,22 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "-", leftType.toString() - ); - return this.module.unreachable(); - } if (compound) { + if (!leftType.isNumericValue) { + this.error( + DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, + expression.range, "-", leftType.toString() + ); + return module.unreachable(); + } rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); - rightType = this.currentType; + rightType = commonType = this.currentType; } else { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, false); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, false, - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, false, - right - ); - rightType = commonType; - } else { + if (!commonType || !leftType.isNumericValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, "-", leftType.toString(), rightType.toString() @@ -4668,47 +4203,12 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; } - switch (this.currentType.kind) { - case TypeKind.I8: // subtraction might overflow - case TypeKind.I16: // ^ - case TypeKind.U8: // ^ - case TypeKind.U16: // ^ - case TypeKind.BOOL: // ^ - case TypeKind.I32: - case TypeKind.U32: { - expr = module.binary(BinaryOp.SubI32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.SubI64 - : BinaryOp.SubI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.SubI64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.SubF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.SubF64, leftExpr, rightExpr); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + expr = this.makeSub(leftExpr, rightExpr, commonType); break; } case Token.ASTERISK_EQUALS: compound = true; @@ -4725,35 +4225,22 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "*", leftType.toString() - ); - return this.module.unreachable(); - } if (compound) { - leftExpr = this.ensureSmallIntegerWrap(leftExpr, leftType); + if (!leftType.isNumericValue) { + this.error( + DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, + expression.range, "*", leftType.toString() + ); + return module.unreachable(); + } rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); + rightType = commonType = this.currentType; } else { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, false); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, false, - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, false, - right - ); - rightType = commonType; - } else { + if (!commonType || !commonType.isNumericValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, "*", leftType.toString(), rightType.toString() @@ -4761,47 +4248,12 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; } - switch (this.currentType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: - case TypeKind.I32: - case TypeKind.U32: { - expr = module.binary(BinaryOp.MulI32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.MulI64 - : BinaryOp.MulI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.MulI64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.MulF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.MulF64, leftExpr, rightExpr); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + expr = this.makeMul(leftExpr, rightExpr, commonType); break; } case Token.ASTERISK_ASTERISK_EQUALS: compound = true; @@ -4818,35 +4270,22 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "**", leftType.toString() - ); - return this.module.unreachable(); - } + if (compound) { - leftExpr = this.ensureSmallIntegerWrap(leftExpr, leftType); + if (!leftType.isNumericValue) { + this.error( + DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, + expression.range, "**", leftType.toString() + ); + return module.unreachable(); + } rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); rightType = commonType = this.currentType; } else { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, false); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, true, // ! - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, true, // ! - right - ); - rightType = commonType; - } else { + if (!commonType || !commonType.isNumericValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, "**", leftType.toString(), rightType.toString() @@ -4854,677 +4293,184 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; } + expr = this.makePow(leftExpr, rightExpr, commonType, expression); + break; + } + case Token.SLASH_EQUALS: compound = true; + case Token.SLASH: { + leftExpr = this.compileExpression(left, contextualType); + leftType = this.currentType; - let instance: Function | null; - switch (commonType.kind) { - case TypeKind.BOOL: { - expr = module.select( - module.i32(1), - module.binary(BinaryOp.EqI32, rightExpr, module.i32(0)), - leftExpr - ); + // check operator overload + let classReference = leftType.getClassOrWrapper(this.program); + if (classReference) { + let overload = classReference.lookupOverload(OperatorKind.DIV); + if (overload) { + expr = this.compileBinaryOverload(overload, left, leftExpr, right, expression); break; } - case TypeKind.I8: - case TypeKind.U8: - case TypeKind.I16: - case TypeKind.U16: - case TypeKind.I32: - case TypeKind.U32: { - instance = this.i32PowInstance; - if (!instance) { - let prototype = this.program.lookupGlobal(CommonNames.ipow32); - if (!prototype) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "ipow32" - ); - expr = module.unreachable(); - break; - } - assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); - this.i32PowInstance = instance = this.resolver.resolveFunction(prototype, null); - } - if (!instance || !this.compileFunction(instance)) { - expr = module.unreachable(); - } else { - expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression); - if (commonType.size != 32) { - expr = this.ensureSmallIntegerWrap(expr, commonType); - } - } - break; + } + + if (compound) { + if (!leftType.isNumericValue) { + this.error( + DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, + expression.range, "/", leftType.toString() + ); + return module.unreachable(); } - case TypeKind.I64: - case TypeKind.U64: { - instance = this.i64PowInstance; - if (!instance) { - let prototype = this.program.lookupGlobal(CommonNames.ipow64); - if (!prototype) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "ipow64" - ); - expr = module.unreachable(); - break; - } - assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); - this.i64PowInstance = instance = this.resolver.resolveFunction(prototype, null); - } - if (!instance || !this.compileFunction(instance)) { - expr = module.unreachable(); - } else { - expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression); - } - break; - } - case TypeKind.ISIZE: - case TypeKind.USIZE: { - let isWasm64 = this.options.isWasm64; - instance = isWasm64 ? this.i64PowInstance : this.i32PowInstance; - if (!instance) { - let prototype = this.program.lookupGlobal(isWasm64 ? CommonNames.ipow64 : CommonNames.ipow32); - if (!prototype) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, isWasm64 ? "ipow64" : "ipow32" - ); - expr = module.unreachable(); - break; - } - assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); - instance = this.resolver.resolveFunction(prototype, null); - if (isWasm64) { - this.i64PowInstance = instance; - } else { - this.i32PowInstance = instance; - } - } - if (!instance || !this.compileFunction(instance)) { - expr = module.unreachable(); - } else { - expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression); - } - break; - } - case TypeKind.F32: { - instance = this.f32PowInstance; - if (!instance) { - let namespace = this.program.lookupGlobal(CommonNames.Mathf); - if (!namespace) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "Mathf" - ); - expr = module.unreachable(); - break; - } - let namespaceMembers = namespace.members; - if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "Mathf.pow" - ); - expr = module.unreachable(); - break; - } - let prototype = assert(namespaceMembers.get(CommonNames.pow)); - assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); - this.f32PowInstance = instance = this.resolver.resolveFunction(prototype, null); - } - if (!instance || !this.compileFunction(instance)) { - expr = module.unreachable(); - } else { - expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression); - } - break; - } - // Math.pow otherwise (result is f64) - case TypeKind.F64: { - instance = this.f64PowInstance; - if (!instance) { - let namespace = this.program.lookupGlobal(CommonNames.Math); - if (!namespace) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "Math" - ); - expr = module.unreachable(); - break; - } - let namespaceMembers = namespace.members; - if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "Math.pow" - ); - expr = module.unreachable(); - break; - } - let prototype = assert(namespaceMembers.get(CommonNames.pow)); - assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); - this.f64PowInstance = instance = this.resolver.resolveFunction(prototype, null); - } - if (!instance || !this.compileFunction(instance)) { - expr = module.unreachable(); - } else { - expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression); - } - break; - } - default: { - assert(false); - expr = module.unreachable(); - break; + rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); + rightType = commonType = this.currentType; + } else { + rightExpr = this.compileExpression(right, leftType); + rightType = this.currentType; + commonType = Type.commonDenominator(leftType, rightType, false); + if (!commonType || !commonType.isNumericValue) { + this.error( + DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, + expression.range, "/", leftType.toString(), rightType.toString() + ); + this.currentType = contextualType; + return module.unreachable(); } + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; } + expr = this.makeDiv(leftExpr, rightExpr, commonType); break; } - case Token.SLASH_EQUALS: compound = true; - case Token.SLASH: { + case Token.PERCENT_EQUALS: compound = true; + case Token.PERCENT: { leftExpr = this.compileExpression(left, contextualType); leftType = this.currentType; // check operator overload let classReference = leftType.getClassOrWrapper(this.program); if (classReference) { - let overload = classReference.lookupOverload(OperatorKind.DIV); + let overload = classReference.lookupOverload(OperatorKind.REM); if (overload) { expr = this.compileBinaryOverload(overload, left, leftExpr, right, expression); break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "/", leftType.toString() - ); - return this.module.unreachable(); - } if (compound) { - leftExpr = this.ensureSmallIntegerWrap(leftExpr, leftType); + if (!leftType.isNumericValue) { + this.error( + DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, + expression.range, "%", leftType.toString() + ); + return module.unreachable(); + } rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); - rightType = this.currentType; + rightType = commonType = this.currentType; } else { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, false); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, true, // ! - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, true, // ! - right - ); - rightType = commonType; - } else { + if (!commonType || !commonType.isNumericValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, - expression.range, "/", leftType.toString(), rightType.toString() + expression.range, "%", leftType.toString(), rightType.toString() ); this.currentType = contextualType; return module.unreachable(); } + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; } - switch (this.currentType.kind) { - case TypeKind.I8: // signed div on signed small integers might overflow, e.g. -128/-1 - case TypeKind.I16: // ^ - case TypeKind.I32: { - expr = module.binary(BinaryOp.DivI32, leftExpr, rightExpr); - break; - } - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.DivI64 - : BinaryOp.DivI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: { - expr = module.binary(BinaryOp.DivI64, leftExpr, rightExpr); - break; - } - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.DivU32, leftExpr, rightExpr); - break; - } - case TypeKind.U32: { - expr = module.binary(BinaryOp.DivU32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.DivU64 - : BinaryOp.DivU32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.U64: { - expr = module.binary(BinaryOp.DivU64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - expr = module.binary(BinaryOp.DivF32, leftExpr, rightExpr); - break; - } - case TypeKind.F64: { - expr = module.binary(BinaryOp.DivF64, leftExpr, rightExpr); - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + expr = this.makeRem(leftExpr, rightExpr, commonType, expression); break; } - case Token.PERCENT_EQUALS: compound = true; - case Token.PERCENT: { - leftExpr = this.compileExpression(left, contextualType); + case Token.LESSTHAN_LESSTHAN_EQUALS: compound = true; + case Token.LESSTHAN_LESSTHAN: { + leftExpr = this.compileExpression(left, contextualType.intType); leftType = this.currentType; // check operator overload let classReference = leftType.getClassOrWrapper(this.program); if (classReference) { - let overload = classReference.lookupOverload(OperatorKind.REM); + let overload = classReference.lookupOverload(OperatorKind.BITWISE_SHL); if (overload) { expr = this.compileBinaryOverload(overload, left, leftExpr, right, expression); break; } } - if (!leftType.isValue) { + if (!leftType.isIntegerValue) { this.error( DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "%", leftType.toString() + expression.range, "<<", leftType.toString() ); - return this.module.unreachable(); + return module.unreachable(); } + rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); + rightType = this.currentType; - if (compound) { - leftExpr = this.ensureSmallIntegerWrap(leftExpr, leftType); - rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); - rightType = this.currentType; - } else { - rightExpr = this.compileExpression(right, leftType); - rightType = this.currentType; - commonType = Type.commonDenominator(leftType, rightType, false); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, true, // ! - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, true, // ! - right - ); - rightType = commonType; - } else { - this.error( - DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, - expression.range, "%", leftType.toString(), rightType.toString() - ); - this.currentType = contextualType; - return module.unreachable(); - } - } - switch (this.currentType.kind) { - case TypeKind.I8: - case TypeKind.I16: { - expr = module.binary(BinaryOp.RemI32, leftExpr, rightExpr); - break; - } - case TypeKind.I32: { - expr = module.binary(BinaryOp.RemI32, leftExpr, rightExpr); - break; - } - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.RemI64 - : BinaryOp.RemI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.I64: { - expr = module.binary(BinaryOp.RemI64, leftExpr, rightExpr); - break; - } - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.RemU32, leftExpr, rightExpr); - break; - } - case TypeKind.U32: { - expr = module.binary(BinaryOp.RemU32, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.RemU64 - : BinaryOp.RemU32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.U64: { - expr = module.binary(BinaryOp.RemU64, leftExpr, rightExpr); - break; - } - case TypeKind.F32: { - let instance = this.f32ModInstance; - if (!instance) { - let namespace = this.program.lookupGlobal(CommonNames.Mathf); - if (!namespace) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "Mathf" - ); - expr = module.unreachable(); - break; - } - let namespaceMembers = namespace.members; - if (!namespaceMembers || !namespaceMembers.has(CommonNames.mod)) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "Mathf.mod" - ); - expr = module.unreachable(); - break; - } - let prototype = assert(namespaceMembers.get(CommonNames.mod)); - assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); - this.f32ModInstance = instance = this.resolver.resolveFunction(prototype, null); - } - if (!instance || !this.compileFunction(instance)) { - expr = module.unreachable(); - } else { - expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression); - } - break; - } - case TypeKind.F64: { - let instance = this.f64ModInstance; - if (!instance) { - let namespace = this.program.lookupGlobal(CommonNames.Math); - if (!namespace) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "Math" - ); - expr = module.unreachable(); - break; - } - let namespaceMembers = namespace.members; - if (!namespaceMembers || !namespaceMembers.has(CommonNames.mod)) { - this.error( - DiagnosticCode.Cannot_find_name_0, - expression.range, "Math.mod" - ); - expr = module.unreachable(); - break; - } - let prototype = assert(namespaceMembers.get(CommonNames.mod)); - assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); - this.f64ModInstance = instance = this.resolver.resolveFunction(prototype, null); - } - if (!instance || !this.compileFunction(instance)) { - expr = module.unreachable(); - } else { - expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], expression); - } - break; - } - default: { - assert(false); - expr = module.unreachable(); - } - } + expr = this.makeShl(leftExpr, rightExpr, rightType); break; } - case Token.LESSTHAN_LESSTHAN_EQUALS: compound = true; - case Token.LESSTHAN_LESSTHAN: { + case Token.GREATERTHAN_GREATERTHAN_EQUALS: compound = true; + case Token.GREATERTHAN_GREATERTHAN: { leftExpr = this.compileExpression(left, contextualType.intType); leftType = this.currentType; // check operator overload let classReference = leftType.getClassOrWrapper(this.program); if (classReference) { - let overload = classReference.lookupOverload(OperatorKind.BITWISE_SHL); + let overload = classReference.lookupOverload(OperatorKind.BITWISE_SHR); if (overload) { expr = this.compileBinaryOverload(overload, left, leftExpr, right, expression); break; } } - if (!leftType.isValue) { + if (!leftType.isIntegerValue) { this.error( DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "<<", leftType.toString() + expression.range, ">>", leftType.toString() ); return this.module.unreachable(); } rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); rightType = this.currentType; - switch (this.currentType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: - case TypeKind.I32: - case TypeKind.U32: { - expr = module.binary(BinaryOp.ShlI32, leftExpr, rightExpr); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.ShlI64, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.ShlI64 - : BinaryOp.ShlI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.F32: - case TypeKind.F64: { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "<<", this.currentType.toString() - ); - return module.unreachable(); - } - default: { - assert(false); - expr = module.unreachable(); - } - } + + expr = this.makeShr(leftExpr, rightExpr, rightType); break; } - case Token.GREATERTHAN_GREATERTHAN_EQUALS: compound = true; - case Token.GREATERTHAN_GREATERTHAN: { + case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: compound = true; + case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: { leftExpr = this.compileExpression(left, contextualType.intType); leftType = this.currentType; // check operator overload let classReference = leftType.getClassOrWrapper(this.program); if (classReference) { - let overload = classReference.lookupOverload(OperatorKind.BITWISE_SHR); + let overload = classReference.lookupOverload(OperatorKind.BITWISE_SHR_U); if (overload) { expr = this.compileBinaryOverload(overload, left, leftExpr, right, expression); break; } } - if (!leftType.isValue) { + if (!leftType.isIntegerValue) { this.error( DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, ">>", leftType.toString() + expression.range, ">>>", leftType.toString() ); - return this.module.unreachable(); + return module.unreachable(); } - - leftExpr = this.ensureSmallIntegerWrap(leftExpr, leftType); // must clear garbage bits rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); rightType = this.currentType; - switch (this.currentType.kind) { - case TypeKind.I8: - case TypeKind.I16: { - expr = module.binary(BinaryOp.ShrI32, leftExpr, rightExpr); - break; - } - case TypeKind.I32: { - expr = module.binary(BinaryOp.ShrI32, leftExpr, rightExpr); - break; - } - case TypeKind.I64: { - expr = module.binary(BinaryOp.ShrI64, leftExpr, rightExpr); - break; - } - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.ShrI64 - : BinaryOp.ShrI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.ShrU32, leftExpr, rightExpr); - break; - } - case TypeKind.U32: { - expr = module.binary(BinaryOp.ShrU32, leftExpr, rightExpr); - break; - } - case TypeKind.U64: { - expr = module.binary(BinaryOp.ShrU64, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.ShrU64 - : BinaryOp.ShrU32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.F32: - case TypeKind.F64: { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, ">>", this.currentType.toString() - ); - return module.unreachable(); - } - default: { - assert(false); - expr = module.unreachable(); - } - } - break; - } - case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN_EQUALS: compound = true; - case Token.GREATERTHAN_GREATERTHAN_GREATERTHAN: { - leftExpr = this.compileExpression(left, contextualType.intType); - leftType = this.currentType; - - // check operator overload - let classReference = leftType.getClassOrWrapper(this.program); - if (classReference) { - let overload = classReference.lookupOverload(OperatorKind.BITWISE_SHR_U); - if (overload) { - expr = this.compileBinaryOverload(overload, left, leftExpr, right, expression); - break; - } - } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, ">>>", leftType.toString() - ); - return this.module.unreachable(); - } - leftExpr = this.ensureSmallIntegerWrap(leftExpr, leftType); // must clear garbage bits - rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); - rightType = this.currentType; - switch (this.currentType.kind) { - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: { // assumes that unsigned shr on unsigned small integers does not overflow - expr = module.binary(BinaryOp.ShrU32, leftExpr, rightExpr); - } - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: - case TypeKind.U32: { - expr = module.binary(BinaryOp.ShrU32, leftExpr, rightExpr); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.ShrU64, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.ShrU64 - : BinaryOp.ShrU32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.F32: - case TypeKind.F64: { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, ">>>", this.currentType.toString() - ); - return module.unreachable(); - } - default: { - assert(false); - expr = module.unreachable(); - } - } + expr = this.makeShru(leftExpr, rightExpr, rightType); break; } case Token.AMPERSAND_EQUALS: compound = true; @@ -5541,32 +4487,22 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "&", leftType.toString() - ); - return this.module.unreachable(); - } if (compound) { + if (!leftType.isIntegerValue) { + this.error( + DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, + expression.range, "&", leftType.toString() + ); + return module.unreachable(); + } rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); - rightType = this.currentType; + rightType = commonType = this.currentType; } else { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; - if (commonType = Type.commonDenominator(leftType, rightType, false)) { - leftExpr = this.convertExpression(leftExpr, - leftType, leftType = commonType, - false, false, - left - ); - rightExpr = this.convertExpression(rightExpr, - rightType, rightType = commonType, - false, false, - right - ); - } else { + commonType = Type.commonDenominator(leftType, rightType, false); + if (!commonType || !commonType.isIntegerValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, "&", leftType.toString(), rightType.toString() @@ -5574,47 +4510,12 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; } - switch (this.currentType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.I32: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: - case TypeKind.U32: { - expr = module.binary(BinaryOp.AndI32, leftExpr, rightExpr); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.AndI64, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.AndI64 - : BinaryOp.AndI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.F32: - case TypeKind.F64: { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "&", this.currentType.toString() - ); - return module.unreachable(); - } - default: { - assert(false); - expr = module.unreachable(); - } - } + expr = this.makeAnd(leftExpr, rightExpr, commonType); break; } case Token.BAR_EQUALS: compound = true; @@ -5631,35 +4532,22 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "|", leftType.toString() - ); - return this.module.unreachable(); - } if (compound) { + if (!leftType.isIntegerValue) { + this.error( + DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, + expression.range, "|", leftType.toString() + ); + return module.unreachable(); + } rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); - rightType = this.currentType; + rightType = commonType = this.currentType; } else { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, false); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, false, - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, false, - right - ); - rightType = commonType; - } else { + if (!commonType || !commonType.isIntegerValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, "|", leftType.toString(), rightType.toString() @@ -5667,50 +4555,12 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; } - switch (this.currentType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.OrI32, leftExpr, rightExpr); - break; - } - case TypeKind.I32: - case TypeKind.U32: { - expr = module.binary(BinaryOp.OrI32, leftExpr, rightExpr); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.OrI64, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.OrI64 - : BinaryOp.OrI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.F32: - case TypeKind.F64: { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "|", this.currentType.toString() - ); - return module.unreachable(); - } - default: { - assert(false); - expr = module.unreachable(); - } - } + expr = this.makeOr(leftExpr, rightExpr, commonType); break; } case Token.CARET_EQUALS: compound = true; @@ -5727,35 +4577,22 @@ export class Compiler extends DiagnosticEmitter { break; } } - if (!leftType.isValue) { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "^", leftType.toString() - ); - return this.module.unreachable(); - } if (compound) { + if (!leftType.isIntegerValue) { + this.error( + DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, + expression.range, "^", leftType.toString() + ); + return module.unreachable(); + } rightExpr = this.compileExpression(right, leftType, Constraints.CONV_IMPLICIT); - rightType = this.currentType; + rightType = commonType = this.currentType; } else { rightExpr = this.compileExpression(right, leftType); rightType = this.currentType; commonType = Type.commonDenominator(leftType, rightType, false); - if (commonType) { - leftExpr = this.convertExpression(leftExpr, - leftType, commonType, - false, false, - left - ); - leftType = commonType; - rightExpr = this.convertExpression(rightExpr, - rightType, commonType, - false, false, - right - ); - rightType = commonType; - } else { + if (!commonType || !commonType.isIntegerValue) { this.error( DiagnosticCode.Operator_0_cannot_be_applied_to_types_1_and_2, expression.range, "^", leftType.toString(), rightType.toString() @@ -5763,50 +4600,12 @@ export class Compiler extends DiagnosticEmitter { this.currentType = contextualType; return module.unreachable(); } + leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); + leftType = commonType; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); + rightType = commonType; } - switch (this.currentType.kind) { - case TypeKind.I8: - case TypeKind.I16: - case TypeKind.U8: - case TypeKind.U16: - case TypeKind.BOOL: { - expr = module.binary(BinaryOp.XorI32, leftExpr, rightExpr); - break; - } - case TypeKind.I32: - case TypeKind.U32: { - expr = module.binary(BinaryOp.XorI32, leftExpr, rightExpr); - break; - } - case TypeKind.I64: - case TypeKind.U64: { - expr = module.binary(BinaryOp.XorI64, leftExpr, rightExpr); - break; - } - case TypeKind.USIZE: - case TypeKind.ISIZE: { - expr = module.binary( - this.options.isWasm64 - ? BinaryOp.XorI64 - : BinaryOp.XorI32, - leftExpr, - rightExpr - ); - break; - } - case TypeKind.F32: - case TypeKind.F64: { - this.error( - DiagnosticCode.The_0_operator_cannot_be_applied_to_type_1, - expression.range, "^", this.currentType.toString() - ); - return module.unreachable(); - } - default: { - assert(false); - expr = module.unreachable(); - } - } + expr = this.makeXor(leftExpr, rightExpr, commonType); break; } @@ -5982,77 +4781,1087 @@ export class Compiler extends DiagnosticEmitter { rightFlow.freeScopedLocals(); this.currentFlow = flow; - expr = module.if( - this.makeIsTrueish(leftExpr, leftType, left), - retainLeftInThen - ? this.makeRetain( - module.local_get(temp.index, leftType.toNativeType()), - leftType - ) - : module.local_get(temp.index, leftType.toNativeType()), - rightExpr + expr = module.if( + this.makeIsTrueish(leftExpr, leftType, left), + retainLeftInThen + ? this.makeRetain( + module.local_get(temp.index, leftType.toNativeType()), + leftType + ) + : module.local_get(temp.index, leftType.toNativeType()), + rightExpr + ); + if (leftAutoreleaseSkipped || rightAutoreleaseSkipped) this.skippedAutoreleases.add(expr); + if (temp) flow.freeTempLocal(temp); + + // basic values can use more aggressive optimizations + } else { + rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType); + rightFlow.freeScopedLocals(); + this.currentFlow = flow; + + // simplify if cloning left without side effects is possible + if (expr = module.cloneExpression(leftExpr, true, 0)) { + expr = module.if( + this.makeIsTrueish(leftExpr, leftType, left), + expr, + rightExpr + ); + + // if not possible, tee left to a temp. local + } else { + let temp = flow.getTempLocal(leftType); + if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED); + if (flow.isNonnull(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.NONNULL); + expr = module.if( + this.makeIsTrueish(module.local_tee(temp.index, leftExpr), leftType, left), + module.local_get(temp.index, leftType.toNativeType()), + rightExpr + ); + flow.freeTempLocal(temp); + } + } + this.currentType = leftType; + } + break; + } + default: { + assert(false); + expr = this.module.unreachable(); + } + } + if (!compound) return expr; + var resolver = this.resolver; + var target = resolver.lookupExpression(left, this.currentFlow); + if (!target) return module.unreachable(); + var targetType = resolver.getTypeOfElement(target); + if (!targetType) targetType = Type.void; + if (!this.currentType.isStrictlyAssignableTo(targetType)) { + this.error( + DiagnosticCode.Type_0_is_not_assignable_to_type_1, + expression.range, this.currentType.toString(), targetType.toString() + ); + return module.unreachable(); + } + return this.makeAssignment( + target, + expr, // TODO: delay release above if possible? + this.currentType, + right, + resolver.currentThisExpression, + resolver.currentElementExpression, + contextualType != Type.void + ); + } + + makeLt(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Cares about garbage bits and signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I32: { + return module.binary(BinaryOp.LtI32, leftExpr, rightExpr); + } + case TypeKind.I64: { + return module.binary(BinaryOp.LtI64, leftExpr, rightExpr); + } + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.LtI64 + : BinaryOp.LtI32, + leftExpr, + rightExpr + ); + } + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.U32: { + return module.binary(BinaryOp.LtU32, leftExpr, rightExpr); + } + case TypeKind.USIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.LtU64 + : BinaryOp.LtU32, + leftExpr, + rightExpr + ); + } + case TypeKind.U64: { + return module.binary(BinaryOp.LtU64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.LtF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.LtF64, leftExpr, rightExpr); + } + } + assert(false); + return module.unreachable(); + } + + makeGt(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Cares about garbage bits and signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I32: { + return module.binary(BinaryOp.GtI32, leftExpr, rightExpr); + } + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.GtI64 + : BinaryOp.GtI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: { + return module.binary(BinaryOp.GtI64, leftExpr, rightExpr); + } + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.U32: { + return module.binary(BinaryOp.GtU32, leftExpr, rightExpr); + } + case TypeKind.USIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.GtU64 + : BinaryOp.GtU32, + leftExpr, + rightExpr + ); + } + case TypeKind.U64: { + return module.binary(BinaryOp.GtU64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.GtF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.GtF64, leftExpr, rightExpr); + } + } + assert(false); + return module.unreachable(); + } + + makeLe(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Cares about garbage bits and signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I32: { + return module.binary(BinaryOp.LeI32, leftExpr, rightExpr); + } + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.LeI64 + : BinaryOp.LeI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: { + return module.binary(BinaryOp.LeI64, leftExpr, rightExpr); + } + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.U32: { + return module.binary(BinaryOp.LeU32, leftExpr, rightExpr); + } + case TypeKind.USIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.LeU64 + : BinaryOp.LeU32, + leftExpr, + rightExpr + ); + } + case TypeKind.U64: { + return module.binary(BinaryOp.LeU64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.LeF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.LeF64, leftExpr, rightExpr); + } + } + assert(false); + return module.unreachable(); + } + + makeGe(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Cares about garbage bits and signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I32: { + return module.binary(BinaryOp.GeI32, leftExpr, rightExpr); + } + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.GeI64 + : BinaryOp.GeI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: { + return module.binary(BinaryOp.GeI64, leftExpr, rightExpr); + } + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.U32: { + return module.binary(BinaryOp.GeU32, leftExpr, rightExpr); + } + case TypeKind.USIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.GeU64 + : BinaryOp.GeU32, + leftExpr, + rightExpr + ); + } + case TypeKind.U64: { + return module.binary(BinaryOp.GeU64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.GeF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.GeF64, leftExpr, rightExpr); + } + } + assert(false); + return module.unreachable(); + } + + makeEq(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type, reportNode: Node): ExpressionRef { + // Cares about garbage bits + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I32: + case TypeKind.U32: { + return module.binary(BinaryOp.EqI32, leftExpr, rightExpr); + } + case TypeKind.ISIZE: + case TypeKind.USIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.EqI64 + : BinaryOp.EqI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.EqI64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.EqF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.EqF64, leftExpr, rightExpr); + } + case TypeKind.V128: { + return module.unary(UnaryOp.AllTrueI8x16, + module.binary(BinaryOp.EqI8x16, leftExpr, rightExpr) + ); + } + case TypeKind.FUNCREF: + case TypeKind.EXTERNREF: + case TypeKind.EXNREF: + case TypeKind.ANYREF: { + this.error( + DiagnosticCode.Operation_0_cannot_be_applied_to_type_1, + reportNode.range, + "ref.eq", + type.toString() + ); + return module.unreachable(); + } + } + assert(false); + return module.unreachable(); + } + + makeNe(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type, reportNode: Node): ExpressionRef { + // Cares about garbage bits + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I32: + case TypeKind.U32: { + return module.binary(BinaryOp.NeI32, leftExpr, rightExpr); + } + case TypeKind.USIZE: + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.NeI64 + : BinaryOp.NeI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.NeI64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.NeF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.NeF64, leftExpr, rightExpr); + } + case TypeKind.V128: { + return module.unary(UnaryOp.AnyTrueI8x16, + module.binary(BinaryOp.NeI8x16, leftExpr, rightExpr) + ); + } + case TypeKind.FUNCREF: + case TypeKind.EXTERNREF: + case TypeKind.EXNREF: + case TypeKind.ANYREF: { + this.error( + DiagnosticCode.Operation_0_cannot_be_applied_to_type_1, + reportNode.range, + "ref.eq", + type.toString() + ); + return module.unreachable(); + } + } + assert(false); + return module.unreachable(); + } + + makeAdd(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Does not care about garbage bits or signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: + case TypeKind.I32: + case TypeKind.U32: { + return module.binary(BinaryOp.AddI32, leftExpr, rightExpr); + } + case TypeKind.USIZE: + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.AddI64 + : BinaryOp.AddI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.AddI64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.AddF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.AddF64, leftExpr, rightExpr); + } + } + assert(false); + return module.unreachable(); + } + + makeSub(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Does not care about garbage bits or signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: + case TypeKind.I32: + case TypeKind.U32: { + return module.binary(BinaryOp.SubI32, leftExpr, rightExpr); + } + case TypeKind.USIZE: + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.SubI64 + : BinaryOp.SubI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.SubI64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.SubF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.SubF64, leftExpr, rightExpr); + } + } + assert(false); + return module.unreachable(); + } + + makeMul(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Does not care about garbage bits or signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: + case TypeKind.I32: + case TypeKind.U32: { + return module.binary(BinaryOp.MulI32, leftExpr, rightExpr); + } + case TypeKind.USIZE: + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.MulI64 + : BinaryOp.MulI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.MulI64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.MulF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.MulF64, leftExpr, rightExpr); + } + } + assert(false); + return module.unreachable(); + } + + makePow(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type, reportNode: Node): ExpressionRef { + // Cares about garbage bits + let module = this.module; + switch (type.kind) { + case TypeKind.BOOL: { + return module.select( + module.i32(1), + module.binary(BinaryOp.EqI32, rightExpr, module.i32(0)), + leftExpr + ); + } + case TypeKind.I8: + case TypeKind.U8: + case TypeKind.I16: + case TypeKind.U16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I32: + case TypeKind.U32: { + let instance = this.i32PowInstance; + if (!instance) { + let prototype = this.program.lookupGlobal(CommonNames.ipow32); + if (!prototype) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "ipow32" + ); + return module.unreachable(); + } + assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); + this.i32PowInstance = instance = this.resolver.resolveFunction(prototype, null); + } + if (!instance || !this.compileFunction(instance)) { + return module.unreachable(); + } + let expr = this.makeCallDirect(instance, [ leftExpr, rightExpr ], reportNode); + if (type.size < 32) { + // TODO: this is necessary because i32PowInstance is generic, and deals with 32-bit integers, + // so its flow does not indicate whether returned SMIs are wrapped. worth to avoid? + expr = this.ensureSmallIntegerWrap(expr, type); + } + return expr; + } + case TypeKind.I64: + case TypeKind.U64: { + let instance = this.i64PowInstance; + if (!instance) { + let prototype = this.program.lookupGlobal(CommonNames.ipow64); + if (!prototype) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "ipow64" + ); + return module.unreachable(); + } + assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); + this.i64PowInstance = instance = this.resolver.resolveFunction(prototype, null); + } + if (!instance || !this.compileFunction(instance)) { + return module.unreachable(); + } + return this.makeCallDirect(instance, [ leftExpr, rightExpr ], reportNode); + } + case TypeKind.ISIZE: + case TypeKind.USIZE: { + let isWasm64 = this.options.isWasm64; + let instance = isWasm64 ? this.i64PowInstance : this.i32PowInstance; + if (!instance) { + let prototype = this.program.lookupGlobal(isWasm64 ? CommonNames.ipow64 : CommonNames.ipow32); + if (!prototype) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, isWasm64 ? "ipow64" : "ipow32" + ); + return module.unreachable(); + } + assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); + instance = this.resolver.resolveFunction(prototype, null); + if (isWasm64) { + this.i64PowInstance = instance; + } else { + this.i32PowInstance = instance; + } + } + if (!instance || !this.compileFunction(instance)) { + return module.unreachable(); + } + return this.makeCallDirect(instance, [ leftExpr, rightExpr ], reportNode); + } + case TypeKind.F32: { + let instance = this.f32PowInstance; + if (!instance) { + let namespace = this.program.lookupGlobal(CommonNames.Mathf); + if (!namespace) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "Mathf" + ); + return module.unreachable(); + } + let namespaceMembers = namespace.members; + if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "Mathf.pow" + ); + return module.unreachable(); + } + let prototype = assert(namespaceMembers.get(CommonNames.pow)); + assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); + this.f32PowInstance = instance = this.resolver.resolveFunction(prototype, null); + } + if (!instance || !this.compileFunction(instance)) { + return module.unreachable(); + } + return this.makeCallDirect(instance, [ leftExpr, rightExpr ], reportNode); + } + // Math.pow otherwise (result is f64) + case TypeKind.F64: { + let instance = this.f64PowInstance; + if (!instance) { + let namespace = this.program.lookupGlobal(CommonNames.Math); + if (!namespace) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "Math" + ); + return module.unreachable(); + } + let namespaceMembers = namespace.members; + if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "Math.pow" + ); + return module.unreachable(); + } + let prototype = assert(namespaceMembers.get(CommonNames.pow)); + assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); + this.f64PowInstance = instance = this.resolver.resolveFunction(prototype, null); + } + if (!instance || !this.compileFunction(instance)) { + return module.unreachable(); + } + return this.makeCallDirect(instance, [ leftExpr, rightExpr ], reportNode); + } + } + assert(false); + return module.unreachable(); + } + + makeDiv(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Cares about garbage bits and signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I32: { + return module.binary(BinaryOp.DivI32, leftExpr, rightExpr); + } + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.DivI64 + : BinaryOp.DivI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: { + return module.binary(BinaryOp.DivI64, leftExpr, rightExpr); + } + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.U32: { + return module.binary(BinaryOp.DivU32, leftExpr, rightExpr); + } + case TypeKind.USIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.DivU64 + : BinaryOp.DivU32, + leftExpr, + rightExpr + ); + } + case TypeKind.U64: { + return module.binary(BinaryOp.DivU64, leftExpr, rightExpr); + } + case TypeKind.F32: { + return module.binary(BinaryOp.DivF32, leftExpr, rightExpr); + } + case TypeKind.F64: { + return module.binary(BinaryOp.DivF64, leftExpr, rightExpr); + } + } + assert(false); + return module.unreachable(); + } + + makeRem(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type, reportNode: Node): ExpressionRef { + // Cares about garbage bits and signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I32: { + return module.binary(BinaryOp.RemI32, leftExpr, rightExpr); + } + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.RemI64 + : BinaryOp.RemI32, + leftExpr, + rightExpr + ); + } + case TypeKind.I64: { + return module.binary(BinaryOp.RemI64, leftExpr, rightExpr); + } + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.U32: { + return module.binary(BinaryOp.RemU32, leftExpr, rightExpr); + } + case TypeKind.USIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.RemU64 + : BinaryOp.RemU32, + leftExpr, + rightExpr + ); + } + case TypeKind.U64: { + return module.binary(BinaryOp.RemU64, leftExpr, rightExpr); + } + case TypeKind.F32: { + let instance = this.f32ModInstance; + if (!instance) { + let namespace = this.program.lookupGlobal(CommonNames.Mathf); + if (!namespace) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "Mathf" ); - if (leftAutoreleaseSkipped || rightAutoreleaseSkipped) this.skippedAutoreleases.add(expr); - if (temp) flow.freeTempLocal(temp); + return module.unreachable(); + } + let namespaceMembers = namespace.members; + if (!namespaceMembers || !namespaceMembers.has(CommonNames.mod)) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "Mathf.mod" + ); + return module.unreachable(); + } + let prototype = assert(namespaceMembers.get(CommonNames.mod)); + assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); + this.f32ModInstance = instance = this.resolver.resolveFunction(prototype, null); + } + if (!instance || !this.compileFunction(instance)) { + return module.unreachable(); + } + return this.makeCallDirect(instance, [ leftExpr, rightExpr ], reportNode); + } + case TypeKind.F64: { + let instance = this.f64ModInstance; + if (!instance) { + let namespace = this.program.lookupGlobal(CommonNames.Math); + if (!namespace) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "Math" + ); + return module.unreachable(); + } + let namespaceMembers = namespace.members; + if (!namespaceMembers || !namespaceMembers.has(CommonNames.mod)) { + this.error( + DiagnosticCode.Cannot_find_name_0, + reportNode.range, "Math.mod" + ); + return module.unreachable(); + } + let prototype = assert(namespaceMembers.get(CommonNames.mod)); + assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); + this.f64ModInstance = instance = this.resolver.resolveFunction(prototype, null); + } + if (!instance || !this.compileFunction(instance)) { + return module.unreachable(); + } + return this.makeCallDirect(instance, [ leftExpr, rightExpr ], reportNode); + } + } + assert(false); + return module.unreachable(); + } - // basic values can use more aggressive optimizations - } else { - rightExpr = this.performAutoreleasesWithValue(rightFlow, rightExpr, rightType); - rightFlow.freeScopedLocals(); - this.currentFlow = flow; + makeShl(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Cares about garbage bits on the RHS, but only for types smaller than 5 bits + var module = this.module; + switch (type.kind) { + case TypeKind.BOOL: { + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.I32: + case TypeKind.U32: { + return module.binary(BinaryOp.ShlI32, leftExpr, rightExpr); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.ShlI64, leftExpr, rightExpr); + } + case TypeKind.USIZE: + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.ShlI64 + : BinaryOp.ShlI32, + leftExpr, + rightExpr + ); + } + } + assert(false); + return module.unreachable(); + } - // simplify if cloning left without side effects is possible - if (expr = module.cloneExpression(leftExpr, true, 0)) { - expr = module.if( - this.makeIsTrueish(leftExpr, leftType, left), - expr, - rightExpr - ); + makeShr(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Cares about garbage bits on the LHS, but on the RHS only for types smaller than 5 bits, + // and signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + // falls through + } + case TypeKind.I32: { + return module.binary(BinaryOp.ShrI32, leftExpr, rightExpr); + } + case TypeKind.I64: { + return module.binary(BinaryOp.ShrI64, leftExpr, rightExpr); + } + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.ShrI64 + : BinaryOp.ShrI32, + leftExpr, + rightExpr + ); + } + case TypeKind.BOOL: { + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through + } + case TypeKind.U8: + case TypeKind.U16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + // falls through + } + case TypeKind.U32: { + return module.binary(BinaryOp.ShrU32, leftExpr, rightExpr); + } + case TypeKind.U64: { + return module.binary(BinaryOp.ShrU64, leftExpr, rightExpr); + } + case TypeKind.USIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.ShrU64 + : BinaryOp.ShrU32, + leftExpr, + rightExpr + ); + } + } + assert(false); + return module.unreachable(); + } - // if not possible, tee left to a temp. local - } else { - let temp = flow.getTempLocal(leftType); - if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED); - if (flow.isNonnull(leftExpr, leftType)) flow.setLocalFlag(temp.index, LocalFlags.NONNULL); - expr = module.if( - this.makeIsTrueish(module.local_tee(temp.index, leftExpr), leftType, left), - module.local_get(temp.index, leftType.toNativeType()), - rightExpr - ); - flow.freeTempLocal(temp); - } - } - this.currentType = leftType; - } - break; + makeShru(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Cares about garbage bits on the LHS, but on the RHS only for types smaller than 5 bits + var module = this.module; + switch (type.kind) { + case TypeKind.BOOL: { + rightExpr = this.ensureSmallIntegerWrap(rightExpr, type); + // falls through } - default: { - assert(false); - expr = this.module.unreachable(); + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: { + leftExpr = this.ensureSmallIntegerWrap(leftExpr, type); + // falls through + } + case TypeKind.I32: + case TypeKind.U32: { + return module.binary(BinaryOp.ShrU32, leftExpr, rightExpr); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.ShrU64, leftExpr, rightExpr); + } + case TypeKind.USIZE: + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.ShrU64 + : BinaryOp.ShrU32, + leftExpr, + rightExpr + ); } } - if (!compound) return expr; - var resolver = this.resolver; - var target = resolver.lookupExpression(left, this.currentFlow); - if (!target) return module.unreachable(); - var targetType = resolver.getTypeOfElement(target); - if (!targetType) targetType = Type.void; - if (!this.currentType.isStrictlyAssignableTo(targetType)) { - this.error( - DiagnosticCode.Type_0_is_not_assignable_to_type_1, - expression.range, this.currentType.toString(), targetType.toString() - ); - return module.unreachable(); + assert(false); + return module.unreachable(); + } + + makeAnd(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Does not care about garbage bits or signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.I32: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: + case TypeKind.U32: { + return module.binary(BinaryOp.AndI32, leftExpr, rightExpr); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.AndI64, leftExpr, rightExpr); + } + case TypeKind.USIZE: + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.AndI64 + : BinaryOp.AndI32, + leftExpr, + rightExpr + ); + } } - return this.makeAssignment( - target, - expr, // TODO: delay release above if possible? - this.currentType, - right, - resolver.currentThisExpression, - resolver.currentElementExpression, - contextualType != Type.void - ); + assert(false); + return module.unreachable(); + } + + makeOr(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Does not care about garbage bits or signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + return module.binary(BinaryOp.OrI32, leftExpr, rightExpr); + } + case TypeKind.I32: + case TypeKind.U32: { + return module.binary(BinaryOp.OrI32, leftExpr, rightExpr); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.OrI64, leftExpr, rightExpr); + } + case TypeKind.USIZE: + case TypeKind.ISIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.OrI64 + : BinaryOp.OrI32, + leftExpr, + rightExpr + ); + } + } + assert(false); + return module.unreachable(); + } + + makeXor(leftExpr: ExpressionRef, rightExpr: ExpressionRef, type: Type): ExpressionRef { + // Does not care about garbage bits or signedness + var module = this.module; + switch (type.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: + case TypeKind.BOOL: { + return module.binary(BinaryOp.XorI32, leftExpr, rightExpr); + } + case TypeKind.I32: + case TypeKind.U32: { + return module.binary(BinaryOp.XorI32, leftExpr, rightExpr); + } + case TypeKind.I64: + case TypeKind.U64: { + return module.binary(BinaryOp.XorI64, leftExpr, rightExpr); + } + case TypeKind.ISIZE: + case TypeKind.USIZE: { + return module.binary( + this.options.isWasm64 + ? BinaryOp.XorI64 + : BinaryOp.XorI32, + leftExpr, + rightExpr + ); + } + } + assert(false); + return module.unreachable(); } private compileUnaryOverload( @@ -6193,7 +6002,7 @@ export class Compiler extends DiagnosticEmitter { var valueType = this.currentType; return this.makeAssignment( target, - this.convertExpression(valueExpr, valueType, targetType, false, false, valueExpression), + this.convertExpression(valueExpr, valueType, targetType, false, valueExpression), valueType, valueExpression, thisExpression, @@ -7920,7 +7729,7 @@ export class Compiler extends DiagnosticEmitter { operands.push( this.convertExpression( module.global_get(global.internalName, global.type.toNativeType()), - global.type, parameterTypes[i], false, false, initializer + global.type, parameterTypes[i], false, initializer ) ); } @@ -9787,18 +9596,10 @@ export class Compiler extends DiagnosticEmitter { this.currentType = ctxType; return module.unreachable(); } - ifThenExpr = this.convertExpression( - ifThenExpr, - ifThenType, commonType, - false, false, - ifThen - ); - ifElseExpr = this.convertExpression( - ifElseExpr, - ifElseType, commonType, - false, false, - ifElse - ); + ifThenExpr = this.convertExpression(ifThenExpr, ifThenType, commonType, false, ifThen); + ifThenType = commonType; + ifElseExpr = this.convertExpression(ifElseExpr, ifElseType, commonType, false, ifElse); + ifElseType = commonType; this.currentType = commonType; if (ifThenAutoreleaseSkipped != ifElseAutoreleaseSkipped) { // unify to both skipped @@ -10390,11 +10191,7 @@ export class Compiler extends DiagnosticEmitter { return module.unreachable(); } - expr = this.convertExpression(expr, - this.currentType, this.currentType.intType, - false, false, - expression.operand - ); + expr = this.convertExpression(expr, this.currentType, this.currentType.intType, false, expression.operand); switch (this.currentType.kind) { case TypeKind.I8: @@ -10486,7 +10283,7 @@ export class Compiler extends DiagnosticEmitter { } default: { expr = this.compileExpression(operand, Type.auto); // may trigger an error - expr = this.convertExpression(expr, this.currentType, Type.void, true, false, operand); + expr = this.convertExpression(expr, this.currentType, Type.void, true, operand); } } typeString = "undefined"; @@ -10505,7 +10302,7 @@ export class Compiler extends DiagnosticEmitter { default: { expr = this.compileExpression(operand, Type.auto); let type = this.currentType; - expr = this.convertExpression(expr, type, Type.void, true, false, operand); + expr = this.convertExpression(expr, type, Type.void, true, operand); if (type.isReference) { let signatureReference = type.getSignature(); if (signatureReference) {