001    /*
002     * Copyright 2010-2016 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.js.translate.intrinsic.functions.factories;
018    
019    import com.google.common.base.Predicate;
020    import com.google.common.base.Predicates;
021    import com.google.common.collect.ImmutableMap;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.annotations.Nullable;
024    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
025    import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
026    import org.jetbrains.kotlin.js.backend.ast.*;
027    import org.jetbrains.kotlin.js.patterns.DescriptorPredicate;
028    import org.jetbrains.kotlin.js.patterns.NamePredicate;
029    import org.jetbrains.kotlin.js.translate.context.Namer;
030    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
031    import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic;
032    import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsicWithReceiverComputed;
033    import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.RangeToIntrinsic;
034    import org.jetbrains.kotlin.js.translate.operation.OperatorTable;
035    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
036    import org.jetbrains.kotlin.lexer.KtToken;
037    import org.jetbrains.kotlin.name.Name;
038    import org.jetbrains.kotlin.types.expressions.OperatorConventions;
039    import org.jetbrains.kotlin.util.OperatorNameConventions;
040    
041    import java.util.Collections;
042    import java.util.List;
043    
044    import static org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern;
045    
046    public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory {
047        INSTANCE;
048    
049        private static abstract class BinaryOperationIntrinsicBase extends FunctionIntrinsicWithReceiverComputed {
050            @NotNull
051            public abstract JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context);
052    
053            @NotNull
054            @Override
055            public JsExpression apply(
056                    @Nullable JsExpression receiver,
057                    @NotNull List<? extends JsExpression> arguments,
058                    @NotNull TranslationContext context) {
059                assert receiver != null;
060                assert arguments.size() == 1;
061                return doApply(receiver, arguments.get(0), context);
062            }
063        }
064    
065        private static final BinaryOperationIntrinsicBase INT_MULTIPLICATION_INTRINSIC = new BinaryOperationIntrinsicBase() {
066            // IEEE 754 mantissa is 52 bits long. Assuming one argument may be up to 2^31, the second argument should be
067            // not greater than 2^21 in order their product don't exceed 2^52. We preserve two extra bits to be more safe.
068            private static final int SAFE_THRESHOLD = 2 << 19;
069    
070            @NotNull
071            @Override
072            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
073                if (isSafeConstant(left) || isSafeConstant(right)) {
074                    return JsAstUtils.toInt32(JsAstUtils.mul(left, right));
075                }
076                return new JsInvocation(Namer.imul(), left, right);
077            }
078    
079            private boolean isSafeConstant(@NotNull JsExpression expression) {
080                if (!(expression instanceof JsNumberLiteral.JsIntLiteral)) return false;
081                int value = ((JsNumberLiteral.JsIntLiteral) expression).value;
082                return Math.abs(value) < SAFE_THRESHOLD;
083            }
084        };
085    
086        @NotNull
087        private static final BinaryOperationIntrinsicBase BUILTINS_COMPARE_TO_INTRINSIC = new BinaryOperationIntrinsicBase() {
088            @NotNull
089            @Override
090            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
091                return JsAstUtils.compareTo(left, right);
092            }
093        };
094    
095        @NotNull
096        private static final BinaryOperationIntrinsicBase PRIMITIVE_NUMBER_COMPARE_TO_INTRINSIC = new BinaryOperationIntrinsicBase() {
097            @NotNull
098            @Override
099            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
100                return JsAstUtils.primitiveCompareTo(left, right);
101            }
102        };
103    
104        @NotNull
105        private static final NamePredicate BINARY_OPERATIONS = new NamePredicate(OperatorNameConventions.BINARY_OPERATION_NAMES);
106    
107        private static final DescriptorPredicate INT_BINARY_OPERATIONS = pattern("Int.plus|minus(Int)");
108        private static final DescriptorPredicate SIMPLE_INT_MULTIPLICATION = pattern("Byte|Short.times(Byte|Short)");
109        private static final DescriptorPredicate INT_DIVISION = pattern("Byte|Short|Int.div(Byte|Short|Int)");
110        private static final DescriptorPredicate PRIMITIVE_NUMBERS_BINARY_OPERATIONS =
111                pattern(NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS, BINARY_OPERATIONS);
112    
113        private static final DescriptorPredicate PRIMITIVE_NUMBERS_COMPARE_TO_OPERATIONS =
114                pattern(NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS, "compareTo");
115        private static final Predicate<FunctionDescriptor> INT_WITH_BIT_OPERATIONS = Predicates.or(
116                pattern("Int.or|and|xor|shl|shr|ushr"),
117                pattern("Short|Byte.or|and|xor")
118        );
119        private static final DescriptorPredicate BOOLEAN_OPERATIONS = pattern("Boolean.or|and|xor");
120        private static final DescriptorPredicate STRING_PLUS = pattern("String.plus");
121        private static final DescriptorPredicate INT_MULTIPLICATION = pattern("Int.times(Int)");
122    
123        private static final DescriptorPredicate CHAR_RANGE_TO = pattern("Char.rangeTo(Char)");
124        private static final DescriptorPredicate NUMBER_RANGE_TO = pattern("Byte|Short|Int.rangeTo(Byte|Short|Int)");
125    
126        private static final ImmutableMap<String, JsBinaryOperator> BINARY_BITWISE_OPERATIONS = ImmutableMap.<String, JsBinaryOperator>builder()
127                .put("or", JsBinaryOperator.BIT_OR)
128                .put("and", JsBinaryOperator.BIT_AND)
129                .put("xor", JsBinaryOperator.BIT_XOR)
130                .put("shl", JsBinaryOperator.SHL)
131                .put("shr", JsBinaryOperator.SHR)
132                .put("ushr", JsBinaryOperator.SHRU)
133                .build();
134    
135        private static final Predicate<FunctionDescriptor> PREDICATE = Predicates.or(PRIMITIVE_NUMBERS_BINARY_OPERATIONS, BOOLEAN_OPERATIONS,
136                                                                                     STRING_PLUS, INT_WITH_BIT_OPERATIONS,
137                                                                                     PRIMITIVE_NUMBERS_COMPARE_TO_OPERATIONS);
138    
139        @Nullable
140        @Override
141        public FunctionIntrinsic getIntrinsic(@NotNull FunctionDescriptor descriptor) {
142            if (CHAR_RANGE_TO.apply(descriptor)) {
143                return new RangeToIntrinsic(descriptor);
144            }
145    
146            if (PRIMITIVE_NUMBERS_COMPARE_TO_OPERATIONS.apply(descriptor)) {
147                return PRIMITIVE_NUMBER_COMPARE_TO_INTRINSIC;
148            }
149    
150    
151            if (KotlinBuiltIns.isBuiltIn(descriptor) && descriptor.getName().equals(OperatorNameConventions.COMPARE_TO)) {
152                return BUILTINS_COMPARE_TO_INTRINSIC;
153            }
154    
155            if (!PREDICATE.apply(descriptor)) {
156                return null;
157            }
158    
159            if (INT_MULTIPLICATION.apply(descriptor)) {
160                return INT_MULTIPLICATION_INTRINSIC;
161            }
162            if (NUMBER_RANGE_TO.apply(descriptor)) {
163                return new RangeToIntrinsic(descriptor);
164            }
165            if (INT_WITH_BIT_OPERATIONS.apply(descriptor)) {
166                JsBinaryOperator op = BINARY_BITWISE_OPERATIONS.get(descriptor.getName().asString());
167                if (op != null) {
168                    return new OptimizedIntBinaryOperationInstrinsic(op);
169                }
170            }
171            JsBinaryOperator operator = getOperator(descriptor);
172            if (INT_BINARY_OPERATIONS.apply(descriptor)) {
173                return new AdditiveIntBinaryOperationInstrinsic(operator);
174            }
175            if (SIMPLE_INT_MULTIPLICATION.apply(descriptor) || INT_DIVISION.apply(descriptor)) {
176                return new IntBinaryOperationFunctionIntrinsic(operator);
177            }
178            BinaryOperationIntrinsicBase result = new PrimitiveBinaryOperationFunctionIntrinsic(operator);
179    
180            if (pattern("Char.plus|minus(Int)").apply(descriptor)) {
181                return new CharAndIntBinaryOperationFunctionIntrinsic(result);
182            }
183            if (pattern("Char.minus(Char)").apply(descriptor)) {
184                return new CharAndCharBinaryOperationFunctionIntrinsic(result);
185            }
186            if (pattern("String.plus(Any)").apply(descriptor)) {
187                return new StringAndCharBinaryOperationFunctionIntrinsic(result);
188            }
189            return result;
190        }
191    
192        @NotNull
193        private static JsBinaryOperator getOperator(@NotNull FunctionDescriptor descriptor) {
194            // Temporary hack to get '%' for deprecated 'mod' operator
195            Name descriptorName = descriptor.getName().equals(OperatorNameConventions.MOD) ? OperatorNameConventions.REM : descriptor.getName();
196    
197            KtToken token = OperatorConventions.BINARY_OPERATION_NAMES.inverse().get(descriptorName);
198            if (token == null) {
199                token = OperatorConventions.BOOLEAN_OPERATIONS.inverse().get(descriptorName);
200            }
201            if (token == null) {
202                assert descriptorName.asString().equals("xor");
203                return JsBinaryOperator.BIT_XOR;
204            }
205            return OperatorTable.getBinaryOperator(token);
206        }
207    
208        private static class PrimitiveBinaryOperationFunctionIntrinsic extends BinaryOperationIntrinsicBase {
209    
210            @NotNull
211            private final JsBinaryOperator operator;
212    
213            private PrimitiveBinaryOperationFunctionIntrinsic(@NotNull JsBinaryOperator operator) {
214                this.operator = operator;
215            }
216    
217            @NotNull
218            @Override
219            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
220                return new JsBinaryOperation(operator, left, right);
221            }
222        }
223    
224        private static class IntBinaryOperationFunctionIntrinsic extends PrimitiveBinaryOperationFunctionIntrinsic {
225            private IntBinaryOperationFunctionIntrinsic(@NotNull JsBinaryOperator operator) {
226                super(operator);
227            }
228    
229            @NotNull
230            @Override
231            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
232                return JsAstUtils.toInt32(super.doApply(left, right, context));
233            }
234        }
235    
236        private static class OptimizedIntBinaryOperationInstrinsic extends PrimitiveBinaryOperationFunctionIntrinsic {
237            public OptimizedIntBinaryOperationInstrinsic(@NotNull JsBinaryOperator operator) {
238                super(operator);
239            }
240    
241            @NotNull
242            @Override
243            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
244                left = unwrapAdditive(left);
245                right = unwrapAdditive(right);
246                return super.doApply(left, right, context);
247            }
248    
249            @NotNull
250            private static JsExpression unwrapAdditive(@NotNull JsExpression expression) {
251                JsExpression toIntArgument = JsAstUtils.extractToInt32Argument(expression);
252                if (toIntArgument == null) return expression;
253    
254                if (!(toIntArgument instanceof JsBinaryOperation)) return expression;
255                JsBinaryOperator operator = ((JsBinaryOperation) toIntArgument).getOperator();
256                switch (operator) {
257                    case ADD:
258                    case SUB:
259                        return toIntArgument;
260                    default:
261                        return expression;
262                }
263            }
264        }
265    
266        private static class AdditiveIntBinaryOperationInstrinsic extends OptimizedIntBinaryOperationInstrinsic {
267            public AdditiveIntBinaryOperationInstrinsic(@NotNull JsBinaryOperator operator) {
268                super(operator);
269            }
270    
271            @NotNull
272            @Override
273            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
274                return JsAstUtils.toInt32(super.doApply(left, right, context));
275            }
276        }
277    
278        private static class CharAndIntBinaryOperationFunctionIntrinsic extends BinaryOperationIntrinsicBase {
279    
280            @NotNull
281            private final BinaryOperationIntrinsicBase functionIntrinsic;
282    
283            private CharAndIntBinaryOperationFunctionIntrinsic(@NotNull BinaryOperationIntrinsicBase functionIntrinsic) {
284                this.functionIntrinsic = functionIntrinsic;
285            }
286    
287            @NotNull
288            @Override
289            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
290                return JsAstUtils.toChar(functionIntrinsic.doApply(left, right, context));
291            }
292        }
293    
294        private static class CharAndCharBinaryOperationFunctionIntrinsic extends BinaryOperationIntrinsicBase {
295    
296            @NotNull
297            private final BinaryOperationIntrinsicBase functionIntrinsic;
298    
299            private CharAndCharBinaryOperationFunctionIntrinsic(@NotNull BinaryOperationIntrinsicBase functionIntrinsic) {
300                this.functionIntrinsic = functionIntrinsic;
301            }
302    
303            @NotNull
304            @Override
305            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
306                return functionIntrinsic.doApply(left, right, context);
307            }
308        }
309        private static class StringAndCharBinaryOperationFunctionIntrinsic extends BinaryOperationIntrinsicBase {
310    
311            @NotNull
312            private final BinaryOperationIntrinsicBase functionIntrinsic;
313    
314            private StringAndCharBinaryOperationFunctionIntrinsic(@NotNull BinaryOperationIntrinsicBase functionIntrinsic) {
315                this.functionIntrinsic = functionIntrinsic;
316            }
317    
318            @NotNull
319            @Override
320            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
321                return functionIntrinsic.doApply(left, TopLevelFIF.TO_STRING.apply(right, Collections.<JsExpression>emptyList(), context), context);
322            }
323        }
324    }