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 }