[JS IR BE] Support integer operation overflow

This commit is contained in:
Svyatoslav Kuzmich
2018-10-04 13:31:57 +03:00
parent 8013a56286
commit bad9534abd
22 changed files with 283 additions and 99 deletions
@@ -143,6 +143,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
val jsCompareTo = getInternalFunction("compareTo")
val jsEquals = getInternalFunction("equals")
val jsImul = getInternalFunction("imul")
// Coroutines
val jsCoroutineContext = context.symbolTable.referenceSimpleFunction(context.coroutineContextProperty.getter!!)
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.Variance
@@ -233,6 +234,10 @@ object JsIrBuilder {
fun buildTypeOperator(type: IrType, operator: IrTypeOperator, argument: IrExpression, toType: IrType, symbol: IrClassifierSymbol) =
IrTypeOperatorCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, operator, toType, symbol, argument)
fun buildImplicitCast(value: IrExpression, toType: IrType) =
JsIrBuilder.buildTypeOperator(toType, IrTypeOperator.IMPLICIT_CAST, value, toType, toType.classifierOrFail)
fun buildNull(type: IrType) = IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type)
fun buildBoolean(type: IrType, v: Boolean) = IrConstImpl.boolean(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, v)
fun buildInt(type: IrType, v: Int) = IrConstImpl.int(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, v)
@@ -267,6 +272,16 @@ fun IrClass.simpleFunctions(): List<IrSimpleFunction> = this.declarations.flatMa
}
// TODO extract to common place?
fun irCall(
call: IrCall,
newFunction: IrFunction,
dispatchReceiverAsFirstArgument: Boolean = false,
firstArgumentAsDispatchReceiver: Boolean = false
): IrCall =
irCall(call, newFunction.symbol, dispatchReceiverAsFirstArgument, firstArgumentAsDispatchReceiver)
fun irCall(
call: IrCall,
newSymbol: IrFunctionSymbol,
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.types.isLong
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -19,6 +20,7 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
class CallsLowering(val context: JsIrBackendContext) : FileLoweringPass {
private val transformers = listOf(
NumberOperatorCallsTransformer(context),
NumberConversionCallsTransformer(context),
DynamicCallsTransformer(context),
EqualityAndComparisonCallsTransformer(context),
PrimitiveContainerMemberCallTransformer(context),
@@ -0,0 +1,98 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.ir.irCall
import org.jetbrains.kotlin.ir.backend.js.utils.ConversionNames
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.name.Name
class NumberConversionCallsTransformer(context: JsIrBackendContext) : CallsTransformer {
private val intrinsics = context.intrinsics
private val irBuiltIns = context.irBuiltIns
private val memberToTransformer = MemberToTransformer().apply {
// Conversion rules are ported from NumberAndCharConversionFIF
// TODO: Add Char and Number conversions
irBuiltIns.byteType.let {
add(it, ConversionNames.TO_BYTE, ::useDispatchReceiver)
add(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(it, ConversionNames.TO_INT, ::useDispatchReceiver)
add(it, ConversionNames.TO_SHORT, ::useDispatchReceiver)
add(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
for (type in listOf(irBuiltIns.floatType, irBuiltIns.doubleType)) {
add(type, ConversionNames.TO_BYTE, intrinsics.jsNumberToByte)
add(type, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(type, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(type, ConversionNames.TO_INT, intrinsics.jsNumberToInt)
add(type, ConversionNames.TO_SHORT, intrinsics.jsNumberToShort)
add(type, ConversionNames.TO_LONG, intrinsics.jsNumberToLong)
}
irBuiltIns.intType.let {
add(it, ConversionNames.TO_BYTE, intrinsics.jsToByte)
add(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(it, ConversionNames.TO_INT, ::useDispatchReceiver)
add(it, ConversionNames.TO_SHORT, intrinsics.jsToShort)
add(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
irBuiltIns.shortType.let {
add(it, ConversionNames.TO_BYTE, intrinsics.jsToByte)
add(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(it, ConversionNames.TO_INT, ::useDispatchReceiver)
add(it, ConversionNames.TO_SHORT, ::useDispatchReceiver)
add(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
for (type in arrayOf(irBuiltIns.byteType, irBuiltIns.intType)) {
add(type, ConversionNames.TO_CHAR) {
irCall(it, intrinsics.charClassSymbol.constructors.single(), dispatchReceiverAsFirstArgument = true)
}
}
for (type in arrayOf(irBuiltIns.floatType, irBuiltIns.doubleType)) {
add(type, ConversionNames.TO_CHAR) {
JsIrBuilder.buildCall(intrinsics.charClassSymbol.constructors.single()).apply {
putValueArgument(0, irCall(it, intrinsics.jsNumberToInt, dispatchReceiverAsFirstArgument = true))
}
}
}
add(irBuiltIns.charType, ConversionNames.TO_CHAR) { it.dispatchReceiver!! }
}
override fun transformCall(call: IrCall): IrExpression {
val function = call.symbol.owner
function.dispatchReceiverParameter?.also {
val key = SimpleMemberKey(it.type, function.name)
memberToTransformer[key]?.also {
return it(call)
}
}
return call
}
private fun useDispatchReceiver(call: IrCall): IrExpression {
return JsIrBuilder.buildImplicitCast(call.dispatchReceiver!!, call.type)
}
}
@@ -8,29 +8,29 @@ package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.ir.irCall
import org.jetbrains.kotlin.ir.backend.js.utils.ConversionNames
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.originalKotlinType
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.name.Name
class NumberOperatorCallsTransformer(context: JsIrBackendContext) : CallsTransformer {
private val intrinsics = context.intrinsics
private val irBuiltIns = context.irBuiltIns
private val primitiveNumbers =
irBuiltIns.run { listOf(intType, shortType, byteType, floatType, doubleType) }
private fun buildInt(v: Int) = JsIrBuilder.buildInt(irBuiltIns.intType, v)
private val memberToTransformer = MemberToTransformer().apply {
val primitiveNumbers =
irBuiltIns.run { listOf(intType, shortType, byteType, floatType, doubleType) }
for (type in primitiveNumbers) {
add(type, OperatorNames.UNARY_PLUS, intrinsics.jsUnaryPlus)
add(type, OperatorNames.UNARY_MINUS, intrinsics.jsUnaryMinus)
add(type, OperatorNames.UNARY_MINUS, ::transformUnaryMinus)
}
add(irBuiltIns.stringType, OperatorNames.ADD, intrinsics.jsPlus)
@@ -52,87 +52,23 @@ class NumberOperatorCallsTransformer(context: JsIrBackendContext) : CallsTransfo
add(it, OperatorNames.XOR, intrinsics.jsBitXor)
}
// Conversion rules are ported from NumberAndCharConversionFIF
// TODO: Add Char and Number conversions
irBuiltIns.byteType.let {
add(it, ConversionNames.TO_BYTE, ::useDispatchReceiver)
add(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(it, ConversionNames.TO_INT, ::useDispatchReceiver)
add(it, ConversionNames.TO_SHORT, ::useDispatchReceiver)
add(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
for (type in listOf(irBuiltIns.floatType, irBuiltIns.doubleType)) {
add(type, ConversionNames.TO_BYTE, intrinsics.jsNumberToByte)
add(type, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(type, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(type, ConversionNames.TO_INT, intrinsics.jsNumberToInt)
add(type, ConversionNames.TO_SHORT, intrinsics.jsNumberToShort)
add(type, ConversionNames.TO_LONG, intrinsics.jsNumberToLong)
}
irBuiltIns.intType.let {
add(it, ConversionNames.TO_BYTE, intrinsics.jsToByte)
add(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(it, ConversionNames.TO_INT, ::useDispatchReceiver)
add(it, ConversionNames.TO_SHORT, intrinsics.jsToShort)
add(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
irBuiltIns.shortType.let {
add(it, ConversionNames.TO_BYTE, intrinsics.jsToByte)
add(it, ConversionNames.TO_DOUBLE, ::useDispatchReceiver)
add(it, ConversionNames.TO_FLOAT, ::useDispatchReceiver)
add(it, ConversionNames.TO_INT, ::useDispatchReceiver)
add(it, ConversionNames.TO_SHORT, ::useDispatchReceiver)
add(it, ConversionNames.TO_LONG, intrinsics.jsToLong)
}
for (type in primitiveNumbers) {
add(type, Name.identifier("rangeTo"), ::transformRangeTo)
}
for (type in primitiveNumbers) {
// TODO: use increment and decrement when it's possible
add(type, OperatorNames.INC) {
irCall(it, intrinsics.jsPlus.symbol, dispatchReceiverAsFirstArgument = true).apply {
putValueArgument(1, JsIrBuilder.buildInt(irBuiltIns.intType, 1))
}
}
add(type, OperatorNames.DEC) {
irCall(it, intrinsics.jsMinus.symbol, dispatchReceiverAsFirstArgument = true).apply {
putValueArgument(1, JsIrBuilder.buildInt(irBuiltIns.intType, 1))
}
}
add(type, OperatorNames.INC, ::transformIncrement)
add(type, OperatorNames.DEC, ::transformDecrement)
}
for (type in primitiveNumbers) {
add(type, OperatorNames.ADD, withLongCoercion(intrinsics.jsPlus))
add(type, OperatorNames.SUB, withLongCoercion(intrinsics.jsMinus))
add(type, OperatorNames.MUL, withLongCoercion(intrinsics.jsMult))
add(type, OperatorNames.DIV, withLongCoercion(intrinsics.jsDiv))
add(type, OperatorNames.MOD, withLongCoercion(intrinsics.jsMod))
add(type, OperatorNames.REM, withLongCoercion(intrinsics.jsMod))
add(type, OperatorNames.ADD, withLongCoercion(::transformAdd))
add(type, OperatorNames.SUB, withLongCoercion(::transformSub))
add(type, OperatorNames.MUL, withLongCoercion(::transformMul))
add(type, OperatorNames.DIV, withLongCoercion(::transformDiv))
add(type, OperatorNames.MOD, withLongCoercion(::transformRem))
add(type, OperatorNames.REM, withLongCoercion(::transformRem))
}
for (type in arrayOf(irBuiltIns.byteType, irBuiltIns.intType)) {
add(type, ConversionNames.TO_CHAR) {
irCall(it, intrinsics.charClassSymbol.constructors.single(), dispatchReceiverAsFirstArgument = true)
}
}
for (type in arrayOf(irBuiltIns.floatType, irBuiltIns.doubleType)) {
add(type, ConversionNames.TO_CHAR) {
JsIrBuilder.buildCall(intrinsics.charClassSymbol.constructors.single()).apply {
putValueArgument(0, irCall(it, intrinsics.jsNumberToInt, dispatchReceiverAsFirstArgument = true))
}
}
}
add(irBuiltIns.charType, ConversionNames.TO_CHAR) { it.dispatchReceiver!! }
}
override fun transformCall(call: IrCall): IrExpression {
@@ -146,10 +82,6 @@ class NumberOperatorCallsTransformer(context: JsIrBackendContext) : CallsTransfo
return call
}
private fun useDispatchReceiver(call: IrCall): IrExpression {
return call.dispatchReceiver!!
}
private fun transformRangeTo(call: IrCall): IrExpression {
if (call.valueArgumentsCount != 1) return call
return with(call.symbol.owner.valueParameters[0].type) {
@@ -163,7 +95,83 @@ class NumberOperatorCallsTransformer(context: JsIrBackendContext) : CallsTransfo
}
}
private fun withLongCoercion(intrinsic: IrSimpleFunction): (IrCall) -> IrExpression = { call ->
private fun irBinaryOp(
call: IrCall,
intrinsic: IrFunction,
toInt32: Boolean = false
): IrExpression {
val newCall = irCall(call, intrinsic, dispatchReceiverAsFirstArgument = true)
if (toInt32)
return toInt32(newCall)
return newCall
}
class BinaryOp(call: IrCall) {
val function = call.symbol.owner
val name = function.name
val lhs = function.dispatchReceiverParameter!!.type
val rhs = function.valueParameters[0].type
val result = function.returnType
fun canAddOrSubOverflow() =
result.isInt() && (lhs.isInt() || rhs.isInt())
}
private fun transformAdd(call: IrCall) =
irBinaryOp(call, intrinsics.jsPlus, toInt32 = BinaryOp(call).canAddOrSubOverflow())
private fun transformSub(call: IrCall) =
irBinaryOp(call, intrinsics.jsMinus, toInt32 = BinaryOp(call).canAddOrSubOverflow())
private fun transformMul(call: IrCall) = BinaryOp(call).run {
when {
result.isInt() -> when {
lhs.isInt() && rhs.isInt() ->
irBinaryOp(call, intrinsics.jsImul.owner)
else ->
irBinaryOp(call, intrinsics.jsMult, toInt32 = true)
}
else -> irBinaryOp(call, intrinsics.jsMult, toInt32 = false)
}
}
private fun transformDiv(call: IrCall) =
irBinaryOp(call, intrinsics.jsDiv, toInt32 = BinaryOp(call).result.isInt())
private fun transformRem(call: IrCall) =
irBinaryOp(call, intrinsics.jsMod)
private fun transformIncrement(call: IrCall) =
transformCrement(call, intrinsics.jsPlus)
private fun transformDecrement(call: IrCall) =
transformCrement(call, intrinsics.jsMinus)
private fun transformCrement(call: IrCall, correspondingBinaryOp: IrFunction): IrExpression {
val operation = irCall(call, correspondingBinaryOp.symbol, dispatchReceiverAsFirstArgument = true).apply {
putValueArgument(1, buildInt(1))
}
return convertResultToPrimitiveType(operation, call.type)
}
private fun transformUnaryMinus(call: IrCall) =
convertResultToPrimitiveType(
irCall(call, intrinsics.jsUnaryMinus, dispatchReceiverAsFirstArgument = true),
call.type
)
private fun convertResultToPrimitiveType(e: IrExpression, type: IrType) = when {
type.isInt() -> toInt32(e)
type.isByte() -> intrinsics.jsNumberToByte.call(e)
type.isShort() -> intrinsics.jsNumberToShort.call(e)
else -> e
}
private fun withLongCoercion(default: (IrCall) -> IrExpression): (IrCall) -> IrExpression = { call ->
assert(call.valueArgumentsCount == 1)
val arg = call.getValueArgument(0)!!
@@ -211,8 +219,20 @@ class NumberOperatorCallsTransformer(context: JsIrBackendContext) : CallsTransfo
// LHS is Long => use as is
call
} else {
irCall(call, intrinsic.symbol, dispatchReceiverAsFirstArgument = true)
default(call)
}
}
}
fun IrFunctionSymbol.call(vararg arguments: IrExpression) =
JsIrBuilder.buildCall(this, owner.returnType).apply {
for ((idx, arg) in arguments.withIndex()) {
putValueArgument(idx, arg)
}
}
private fun toInt32(e: IrExpression) =
JsIrBuilder.buildCall(intrinsics.jsBitOr.symbol, irBuiltIns.intType).apply {
putValueArgument(0, e)
putValueArgument(1, buildInt(0))
}
}
@@ -279,7 +279,7 @@ class StateMachineBuilder(
if (expression is IrReturnableBlock) processReturnableBlock(expression) else super.visitBlock(expression)
private fun implicitCast(value: IrExpression, toType: IrType) =
JsIrBuilder.buildTypeOperator(toType, IrTypeOperator.IMPLICIT_CAST, value, toType, toType.classifierOrFail)
JsIrBuilder.buildImplicitCast(value, toType)
override fun visitCall(expression: IrCall) {
super.visitCall(expression)
@@ -140,4 +140,11 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
// TODO check when w/o else branch and empty when
return expression.toJsNode(this, context, ::JsConditional)!!
}
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: JsGenerationContext): JsExpression {
return when (expression.operator) {
IrTypeOperator.IMPLICIT_CAST -> expression.argument.accept(this, data)
else -> throw IllegalStateException("All type operator calls except IMPLICIT_CAST should be lowered at this point")
}
}
}
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
fun box(): String {
val i1: Int = Int.MAX_VALUE
val i2 = i1 + 1
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
fun box(): String {
val a: Long = 2147483647 + 1
if (a != -2147483648L) return "fail: in this case we should add to ints and than cast the result to long - overflow expected"
@@ -6404,6 +6404,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
runTest("js/js.translator/testData/box/number/longUnaryOperations.kt");
}
@TestMetadata("mixedTypesOverflow.kt")
public void testMixedTypesOverflow() throws Exception {
runTest("js/js.translator/testData/box/number/mixedTypesOverflow.kt");
}
@TestMetadata("mulInt32.kt")
public void testMulInt32() throws Exception {
runTest("js/js.translator/testData/box/number/mulInt32.kt");
@@ -6404,6 +6404,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
runTest("js/js.translator/testData/box/number/longUnaryOperations.kt");
}
@TestMetadata("mixedTypesOverflow.kt")
public void testMixedTypesOverflow() throws Exception {
runTest("js/js.translator/testData/box/number/mixedTypesOverflow.kt");
}
@TestMetadata("mulInt32.kt")
public void testMulInt32() throws Exception {
runTest("js/js.translator/testData/box/number/mulInt32.kt");
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1282
package foo
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1266
// MODULE: lib1
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1293
package foo
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1280
package foo
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1282
package foo
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1284
package foo
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1281
package foo
@@ -0,0 +1,31 @@
// EXPECTED_REACHABLE_NODES: 1284
package foo
fun box(): String {
val byteOne = 1.toByte()
val shortOne = 1.toShort()
var v: Int
v = maxInt() + byteOne
if (v != minInt()) return "fail1: $v"
v = minInt() - byteOne
if (v != maxInt()) return "fail2: $v"
v = maxInt() + shortOne
if (v != minInt()) return "fail3: $v"
v = minInt() - shortOne
if (v != maxInt()) return "fail4: $v"
v = maxInt() * Byte.MAX_VALUE
if (v != 2147483521) return "fail5: $v"
v = maxInt() * Short.MAX_VALUE
if (v != 2147450881) return "fail6: $v"
return "OK"
}
fun minInt() = Int.MIN_VALUE
fun maxInt() = Int.MAX_VALUE
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1289
package foo
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1289
package foo
+10
View File
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.js
// TODO: Polyfill
fun imul(a: dynamic, b: dynamic) =
js("((a & 0xffff0000) * (b & 0xffff) + (a & 0xffff) * (b | 0)) | 0")