diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt index 4a9f5b9e5d3..7fd12c2a6ce 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt @@ -103,9 +103,6 @@ internal class TemporaryVariableElimination(private val function: JsFunction) { private val namesWithSideEffects = mutableSetOf() - // Unused variables that are safe to remove - private val variablesToRemove = mutableSetOf() - fun apply(): Boolean { analyze() perform() @@ -263,13 +260,8 @@ internal class TemporaryVariableElimination(private val function: JsFunction) { } } } - else { - if (shouldConsiderUnused(name)) { - variablesToRemove += name - } - if (sideEffects) { - invalidateTemporaries() - } + else if (sideEffects) { + invalidateTemporaries() } } @@ -506,7 +498,7 @@ internal class TemporaryVariableElimination(private val function: JsFunction) { hasChanges = true } - val ranges = x.vars.splitToRanges { it.name in variablesToRemove } + val ranges = x.vars.splitToRanges { shouldConsiderUnused(it.name) } if (ranges.size == 1 && !ranges[0].second) return super.visit(x, ctx) hasChanges = true @@ -536,7 +528,7 @@ internal class TemporaryVariableElimination(private val function: JsFunction) { val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression) if (assignment != null) { val (name, value) = assignment - if (name in variablesToRemove) { + if (shouldConsiderUnused(name)) { hasChanges = true ctx.replaceMe(JsExpressionStatement(value).run { synthetic = true @@ -571,6 +563,17 @@ internal class TemporaryVariableElimination(private val function: JsFunction) { override fun visit(x: JsBreak, ctx: JsContext<*>) = false override fun visit(x: JsContinue, ctx: JsContext<*>) = false + + override fun endVisit(x: JsBinaryOperation, ctx: JsContext) { + val assignment = JsAstUtils.decomposeAssignmentToVariable(x) + if (assignment != null) { + val name = assignment.first + if (shouldConsiderUnused(name)) { + ctx.replaceMe(x.arg2) + } + } + super.endVisit(x, ctx) + } }.accept(root) } @@ -613,7 +616,7 @@ internal class TemporaryVariableElimination(private val function: JsFunction) { } is JsLiteral.JsValueLiteral -> expr.toString().length < 10 is JsInvocation -> expr.sideEffects == SideEffectKind.PURE && isTrivial(expr.qualifier) && expr.arguments.all { isTrivial(it) } - is JsArrayAccess -> isTrivial(expr.arrayExpression) && isTrivial(expr.indexExpression) + is JsArrayAccess -> isTrivial(expr.arrayExpression) && isTrivial(expr.indexExpression) && expr.sideEffects == SideEffectKind.PURE else -> false } } \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index b4df700bcc8..03e43b84829 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -5999,6 +5999,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("intIncDecOverflow.kt") + public void testIntIncDecOverflow() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/number/intIncDecOverflow.kt"); + doTest(fileName); + } + @TestMetadata("intOverflow.kt") public void testIntOverflow() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/number/intOverflow.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java index d2f68f6b3ad..9e3eb24355c 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java @@ -44,10 +44,7 @@ import org.jetbrains.kotlin.js.translate.test.JSTester; import org.jetbrains.kotlin.js.translate.test.QUnitTester; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.js.translate.utils.mutator.AssignToExpressionMutator; -import org.jetbrains.kotlin.psi.KtDeclarationWithBody; -import org.jetbrains.kotlin.psi.KtExpression; -import org.jetbrains.kotlin.psi.KtFile; -import org.jetbrains.kotlin.psi.KtNamedFunction; +import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; @@ -107,9 +104,11 @@ public final class Translation { CompileTimeConstant compileTimeValue = ConstantExpressionEvaluator.getConstant(expression, context.bindingContext()); if (compileTimeValue != null) { KotlinType type = context.bindingContext().getType(expression); - if (type != null && KotlinBuiltIns.isLong(type)) { - JsExpression constantResult = translateConstant(compileTimeValue, expression, context); - if (constantResult != null) return constantResult; + if (type != null) { + if (KotlinBuiltIns.isLong(type) || (KotlinBuiltIns.isInt(type) && expression instanceof KtUnaryExpression)) { + JsExpression constantResult = translateConstant(compileTimeValue, expression, context); + if (constantResult != null) return constantResult; + } } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/PrimitiveUnaryOperationFIF.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/PrimitiveUnaryOperationFIF.java index 234ccfe14f6..78cdf227a8c 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/PrimitiveUnaryOperationFIF.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/PrimitiveUnaryOperationFIF.java @@ -36,7 +36,6 @@ import org.jetbrains.kotlin.util.OperatorNameConventions; import java.util.List; -import static org.jetbrains.kotlin.js.patterns.NamePredicate.PRIMITIVE_NUMBERS; import static org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern; public enum PrimitiveUnaryOperationFIF implements FunctionIntrinsicFactory { @@ -61,11 +60,66 @@ public enum PrimitiveUnaryOperationFIF implements FunctionIntrinsicFactory { @NotNull private static final Predicate PATTERN = Predicates.and(PRIMITIVE_UNARY_OPERATION_NAMES, NO_PARAMETERS); - @NotNull - private static final DescriptorPredicate INC_OPERATION_FOR_PRIMITIVE_NUMBER = pattern(PRIMITIVE_NUMBERS, "inc"); + private static final DescriptorPredicate INC_OPERATION_FOR_INT = pattern("Int.inc"); + private static final DescriptorPredicate DEC_OPERATION_FOR_INT = pattern("Int.dec"); + private static final DescriptorPredicate INC_OPERATION_FOR_BYTE = pattern("Byte.inc"); + private static final DescriptorPredicate DEC_OPERATION_FOR_BYTE = pattern("Byte.dec"); + private static final DescriptorPredicate INC_OPERATION_FOR_SHORT = pattern("Short.inc"); + private static final DescriptorPredicate DEC_OPERATION_FOR_SHORT = pattern("Short.dec"); @NotNull - private static final DescriptorPredicate DEC_OPERATION_FOR_PRIMITIVE_NUMBER = pattern(PRIMITIVE_NUMBERS, "dec"); + private static final DescriptorPredicate INC_OPERATION_FOR_PRIMITIVE_NUMBER = pattern("Float|Double.inc()"); + + @NotNull + private static final DescriptorPredicate DEC_OPERATION_FOR_PRIMITIVE_NUMBER = pattern("Float|Double.dec()"); + + private static class IntOverflowIntrinsic extends FunctionIntrinsic { + private final FunctionIntrinsic underlyingIntrinsic; + + public IntOverflowIntrinsic(FunctionIntrinsic underlyingIntrinsic) { + this.underlyingIntrinsic = underlyingIntrinsic; + } + + @NotNull + @Override + public JsExpression apply( + @Nullable JsExpression receiver, @NotNull List arguments, @NotNull TranslationContext context + ) { + return JsAstUtils.toInt32(underlyingIntrinsic.apply(receiver, arguments, context)); + } + } + + private static class ShortOverflowIntrinsic extends FunctionIntrinsic { + private final FunctionIntrinsic underlyingIntrinsic; + + public ShortOverflowIntrinsic(FunctionIntrinsic underlyingIntrinsic) { + this.underlyingIntrinsic = underlyingIntrinsic; + } + + @NotNull + @Override + public JsExpression apply( + @Nullable JsExpression receiver, @NotNull List arguments, @NotNull TranslationContext context + ) { + return JsAstUtils.toShort(underlyingIntrinsic.apply(receiver, arguments, context)); + } + } + + private static class ByteOverflowIntrinsic extends FunctionIntrinsic { + private final FunctionIntrinsic underlyingIntrinsic; + + public ByteOverflowIntrinsic(FunctionIntrinsic underlyingIntrinsic) { + this.underlyingIntrinsic = underlyingIntrinsic; + } + + @NotNull + @Override + public JsExpression apply( + @Nullable JsExpression receiver, @NotNull List arguments, @NotNull TranslationContext context + ) { + return JsAstUtils.toByte(underlyingIntrinsic.apply(receiver, arguments, context)); + } + } @NotNull private static final FunctionIntrinsic NUMBER_INC_INTRINSIC = new FunctionIntrinsic() { @@ -184,6 +238,25 @@ public enum PrimitiveUnaryOperationFIF implements FunctionIntrinsicFactory { return CHAR_DEC; } + if (INC_OPERATION_FOR_INT.apply(descriptor)) { + return new IntOverflowIntrinsic(NUMBER_INC_INTRINSIC); + } + if (DEC_OPERATION_FOR_INT.apply(descriptor)) { + return new IntOverflowIntrinsic(NUMBER_DEC_INTRINSIC); + } + if (INC_OPERATION_FOR_SHORT.apply(descriptor)) { + return new ShortOverflowIntrinsic(NUMBER_INC_INTRINSIC); + } + if (DEC_OPERATION_FOR_SHORT.apply(descriptor)) { + return new ShortOverflowIntrinsic(NUMBER_DEC_INTRINSIC); + } + if (INC_OPERATION_FOR_BYTE.apply(descriptor)) { + return new ByteOverflowIntrinsic(NUMBER_INC_INTRINSIC); + } + if (DEC_OPERATION_FOR_BYTE.apply(descriptor)) { + return new ByteOverflowIntrinsic(NUMBER_DEC_INTRINSIC); + } + if (INC_OPERATION_FOR_PRIMITIVE_NUMBER.apply(descriptor)) { return NUMBER_INC_INTRINSIC; } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/DynamicIncrementTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/DynamicIncrementTranslator.java new file mode 100644 index 00000000000..c0fc1a971a4 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/DynamicIncrementTranslator.java @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.translate.operation; + +import com.google.dart.compiler.backend.js.ast.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.js.translate.context.TranslationContext; +import org.jetbrains.kotlin.lexer.KtToken; +import org.jetbrains.kotlin.lexer.KtTokens; +import org.jetbrains.kotlin.psi.KtUnaryExpression; + +import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken; +import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.isPrefix; +import static org.jetbrains.kotlin.js.translate.utils.TranslationUtils.isSimpleNameExpressionNotDelegatedLocalVar; + +public class DynamicIncrementTranslator extends IncrementTranslator { + + @NotNull + public static JsExpression doTranslate(@NotNull KtUnaryExpression expression, + @NotNull TranslationContext context) { + return (new DynamicIncrementTranslator(expression, context)) + .translate(); + } + + private DynamicIncrementTranslator(@NotNull KtUnaryExpression expression, + @NotNull TranslationContext context) { + super(expression, context); + } + + @NotNull + private JsExpression translate() { + if (isSimpleNameExpressionNotDelegatedLocalVar(expression.getBaseExpression(), context())) { + return primitiveExpressionIncrement(); + } + return translateIncrementExpression(); + } + + @NotNull + private JsExpression primitiveExpressionIncrement() { + JsUnaryOperator operator = OperatorTable.getUnaryOperator(getOperationToken(expression)); + JsExpression getExpression = accessTranslator.translateAsGet(); + if (isPrefix(expression)) { + return new JsPrefixOperation(operator, getExpression); + } + else { + return new JsPostfixOperation(operator, getExpression); + } + } + + @Override + @NotNull + protected JsExpression operationExpression(@NotNull JsExpression receiver) { + return unaryAsBinary(receiver); + } + + @NotNull + private JsBinaryOperation unaryAsBinary(@NotNull JsExpression leftExpression) { + JsNumberLiteral oneLiteral = program().getNumberLiteral(1); + KtToken token = getOperationToken(expression); + if (token.equals(KtTokens.PLUSPLUS)) { + return new JsBinaryOperation(JsBinaryOperator.ADD, leftExpression, oneLiteral); + } + if (token.equals(KtTokens.MINUSMINUS)) { + return new JsBinaryOperation(JsBinaryOperator.SUB, leftExpression, oneLiteral); + } + throw new AssertionError("This method should be called only for increment and decrement operators"); + } + +} diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/IncrementTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/IncrementTranslator.java index b89c96c83a2..217d806d3d6 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/IncrementTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/IncrementTranslator.java @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.js.translate.operation; import com.google.dart.compiler.backend.js.ast.JsBinaryOperation; import com.google.dart.compiler.backend.js.ast.JsBinaryOperator; import com.google.dart.compiler.backend.js.ast.JsExpression; +import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties; import com.google.dart.compiler.util.AstUtil; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; @@ -49,8 +50,11 @@ public abstract class IncrementTranslator extends AbstractTranslator { @NotNull public static JsExpression translate(@NotNull KtUnaryExpression expression, @NotNull TranslationContext context) { - if (hasCorrespondingFunctionIntrinsic(context, expression) || isDynamic(context, expression)) { - return IntrinsicIncrementTranslator.doTranslate(expression, context); + if (isDynamic(context, expression)) { + return DynamicIncrementTranslator.doTranslate(expression, context); + } + if (hasCorrespondingFunctionIntrinsic(context, expression)) { + return new IntrinsicIncrementTranslator(expression, context).translateIncrementExpression(); } return (new OverloadedIncrementTranslator(expression, context)).translateIncrementExpression(); } @@ -84,7 +88,9 @@ public abstract class IncrementTranslator extends AbstractTranslator { JsExpression getExpression = accessTranslator.translateAsGet(); JsExpression reassignment = variableReassignment(getExpression); JsExpression getNewValue = accessTranslator.translateAsGet(); - return new JsBinaryOperation(JsBinaryOperator.COMMA, reassignment, getNewValue); + JsExpression result = new JsBinaryOperation(JsBinaryOperator.COMMA, reassignment, getNewValue); + MetadataProperties.setSynthetic(result, true); + return result; } //TODO: decide if this expression can be optimised in case of direct access (not property) @@ -93,10 +99,10 @@ public abstract class IncrementTranslator extends AbstractTranslator { // code fragment: expr(a++) // generate: expr( (t1 = a, t2 = t1, a = t1.inc(), t2) ) TemporaryVariable t1 = context().declareTemporary(accessTranslator.translateAsGet()); - TemporaryVariable t2 = context().declareTemporary(t1.reference()); JsExpression variableReassignment = variableReassignment(t1.reference()); - return AstUtil.newSequence(t1.assignmentExpression(), t2.assignmentExpression(), - variableReassignment, t2.reference()); + JsExpression result = AstUtil.newSequence(t1.assignmentExpression(), variableReassignment, t1.reference()); + MetadataProperties.setSynthetic(result, true); + return result; } @NotNull diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/IntrinsicIncrementTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/IntrinsicIncrementTranslator.java index b029eed3feb..cfb70a09600 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/IntrinsicIncrementTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/operation/IntrinsicIncrementTranslator.java @@ -18,67 +18,37 @@ package org.jetbrains.kotlin.js.translate.operation; import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.descriptors.CallableDescriptor; +import org.jetbrains.kotlin.descriptors.FunctionDescriptor; +import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator; import org.jetbrains.kotlin.js.translate.context.TranslationContext; +import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic; import org.jetbrains.kotlin.lexer.KtToken; import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.psi.KtUnaryExpression; +import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; + +import java.util.Collections; import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken; import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.isPrefix; -import static org.jetbrains.kotlin.js.translate.utils.TranslationUtils.isSimpleNameExpressionNotDelegatedLocalVar; public final class IntrinsicIncrementTranslator extends IncrementTranslator { - @NotNull - public static JsExpression doTranslate(@NotNull KtUnaryExpression expression, - @NotNull TranslationContext context) { - return (new IntrinsicIncrementTranslator(expression, context)) - .translate(); - } + private final ResolvedCall resolvedCall; - private IntrinsicIncrementTranslator(@NotNull KtUnaryExpression expression, - @NotNull TranslationContext context) { + public IntrinsicIncrementTranslator(@NotNull KtUnaryExpression expression, + @NotNull TranslationContext context) { super(expression, context); - } - - @NotNull - private JsExpression translate() { - if (isSimpleNameExpressionNotDelegatedLocalVar(expression.getBaseExpression(), context())) { - return primitiveExpressionIncrement(); - } - return translateIncrementExpression(); - } - - @NotNull - private JsExpression primitiveExpressionIncrement() { - JsUnaryOperator operator = OperatorTable.getUnaryOperator(getOperationToken(expression)); - JsExpression getExpression = accessTranslator.translateAsGet(); - if (isPrefix(expression)) { - return new JsPrefixOperation(operator, getExpression); - } - else { - return new JsPostfixOperation(operator, getExpression); - } + this.resolvedCall = CallUtilKt.getFunctionResolvedCallWithAssert(expression, context.bindingContext()); } @Override @NotNull protected JsExpression operationExpression(@NotNull JsExpression receiver) { - return unaryAsBinary(receiver); + FunctionIntrinsic intrinsic = context().intrinsics().getFunctionIntrinsic(resolvedCall.getResultingDescriptor()); + return intrinsic.apply(receiver, Collections.emptyList(), context()); } - - @NotNull - private JsBinaryOperation unaryAsBinary(@NotNull JsExpression leftExpression) { - JsNumberLiteral oneLiteral = program().getNumberLiteral(1); - KtToken token = getOperationToken(expression); - if (token.equals(KtTokens.PLUSPLUS)) { - return new JsBinaryOperation(JsBinaryOperator.ADD, leftExpression, oneLiteral); - } - if (token.equals(KtTokens.MINUSMINUS)) { - return new JsBinaryOperation(JsBinaryOperator.SUB, leftExpression, oneLiteral); - } - throw new AssertionError("This method should be called only for increment and decrement operators"); - } - } diff --git a/js/js.translator/testData/box/number/intIncDecOverflow.kt b/js/js.translator/testData/box/number/intIncDecOverflow.kt new file mode 100644 index 00000000000..66b7c61fca0 --- /dev/null +++ b/js/js.translator/testData/box/number/intIncDecOverflow.kt @@ -0,0 +1,24 @@ +package foo + + +fun box(): String { + var b: Byte = 0x7F + b++ + if (b.toInt() != -0x80) return "fail1a: $b" + b-- + if (b.toInt() != 0x7F) return "fail1b: $b" + + var s: Short = 0x7FFF + s++ + if (s.toInt() != -0x8000) return "fail2a: $s" + s-- + if (s.toInt() != 0x7FFF) return "fail2b: $s" + + var i: Int = 0x7FFFFFFF + i++ + if (i != -0x80000000) return "fail3a: $i" + i-- + if (i != 0x7FFFFFFF) return "fail3b: $i" + + return "OK" +} \ No newline at end of file