From 65846d783d1a5522f5dfa45216790452bda9ea00 Mon Sep 17 00:00:00 2001 From: Anton Bannykh Date: Wed, 4 Jul 2018 15:15:06 +0300 Subject: [PATCH] JS IR: Long --- .../kotlin/ir/backend/js/JsIntrinsics.kt | 16 + .../js/lower/IntrinsicifyCallsLowering.kt | 137 ++++++- .../IrElementToJsExpressionTransformer.kt | 4 +- .../ir/backend/js/utils/OperatorNames.kt | 2 +- .../kotlin/js/test/BasicIrBoxTest.kt | 6 +- libraries/stdlib/js/irRuntime/core.kt | 15 +- libraries/stdlib/js/irRuntime/long.kt | 253 ++++++++++++ libraries/stdlib/js/irRuntime/longjs.kt | 380 ++++++++++++++++++ .../stdlib/js/irRuntime/numberConversion.kt | 4 + .../stdlib/js/irRuntime/typeCheckUtils.kt | 2 +- 10 files changed, 787 insertions(+), 32 deletions(-) create mode 100644 libraries/stdlib/js/irRuntime/long.kt create mode 100644 libraries/stdlib/js/irRuntime/longjs.kt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index 592fc5b91d0..91b6e4c599b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -14,7 +14,9 @@ import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.ir.util.DeclarationStubGenerator import org.jetbrains.kotlin.js.resolve.JsPlatform.builtIns +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi2ir.findSingleFunction import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinTypeFactory import org.jetbrains.kotlin.types.Variance @@ -96,8 +98,10 @@ class JsIntrinsics( val jsNumberToDouble = getInternalFunction("numberToDouble") val jsNumberToInt = getInternalFunction("numberToInt") val jsNumberToShort = getInternalFunction("numberToShort") + val jsNumberToLong = getInternalFunction("numberToLong") val jsToByte = getInternalFunction("toByte") val jsToShort = getInternalFunction("toShort") + val jsToLong = getInternalFunction("toLong") // Other: @@ -112,6 +116,18 @@ class JsIntrinsics( val jsEquals = getInternalFunction("equals") + val longConstructor = + context.symbolTable.referenceConstructor(context.getClass(FqName("kotlin.Long")).constructors.single()) + val longToDouble = context.symbolTable.referenceSimpleFunction( + context.getClass(FqName("kotlin.Long")).unsubstitutedMemberScope.findSingleFunction( + Name.identifier("toDouble") + ) + ) + val longToFloat = context.symbolTable.referenceSimpleFunction( + context.getClass(FqName("kotlin.Long")).unsubstitutedMemberScope.findSingleFunction( + Name.identifier("toFloat") + ) + ) // Helpers: diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt index e206d697057..9a3bb854d70 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt @@ -17,9 +17,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.isFakeOverriddenFromAny import org.jetbrains.kotlin.ir.declarations.IrFile 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.copyTypeArgumentsFrom +import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol @@ -52,13 +50,6 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL for (type in primitiveNumbers) { op(type, OperatorNames.UNARY_PLUS, intrinsics.jsUnaryPlus) op(type, OperatorNames.UNARY_MINUS, intrinsics.jsUnaryMinus) - - op(type, OperatorNames.ADD, intrinsics.jsPlus) - op(type, OperatorNames.SUB, intrinsics.jsMinus) - op(type, OperatorNames.MUL, intrinsics.jsMult) - op(type, OperatorNames.DIV, intrinsics.jsDiv) - op(type, OperatorNames.MOD, intrinsics.jsMod) - op(type, OperatorNames.REM, intrinsics.jsMod) } irBuiltIns.stringType.let { @@ -83,7 +74,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL } // Conversion rules are ported from NumberAndCharConversionFIF - // TODO: Add Char, Long and Number conversions + // TODO: Add Char and Number conversions irBuiltIns.byteType.let { op(it, ConversionNames.TO_BYTE, intrinsics.jsAsIs) @@ -91,6 +82,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL op(it, ConversionNames.TO_FLOAT, intrinsics.jsAsIs) op(it, ConversionNames.TO_INT, intrinsics.jsAsIs) op(it, ConversionNames.TO_SHORT, intrinsics.jsAsIs) + op(it, ConversionNames.TO_LONG, intrinsics.jsToLong) } for (type in listOf(irBuiltIns.floatType, irBuiltIns.doubleType)) { @@ -99,6 +91,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL op(type, ConversionNames.TO_FLOAT, intrinsics.jsAsIs) op(type, ConversionNames.TO_INT, intrinsics.jsNumberToInt) op(type, ConversionNames.TO_SHORT, intrinsics.jsNumberToShort) + op(type, ConversionNames.TO_LONG, intrinsics.jsNumberToLong) } irBuiltIns.intType.let { @@ -107,6 +100,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL op(it, ConversionNames.TO_FLOAT, intrinsics.jsAsIs) op(it, ConversionNames.TO_INT, intrinsics.jsAsIs) op(it, ConversionNames.TO_SHORT, intrinsics.jsToShort) + op(it, ConversionNames.TO_LONG, intrinsics.jsToLong) } irBuiltIns.shortType.let { @@ -115,13 +109,14 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL op(it, ConversionNames.TO_FLOAT, intrinsics.jsAsIs) op(it, ConversionNames.TO_INT, intrinsics.jsAsIs) op(it, ConversionNames.TO_SHORT, intrinsics.jsAsIs) + op(it, ConversionNames.TO_LONG, intrinsics.jsToLong) } } symbolToIrFunction.run { add(irBuiltIns.eqeqeqSymbol, intrinsics.jsEqeqeq) // TODO: implement it a right way - add(irBuiltIns.eqeqSymbol, intrinsics.jsEqeq) + add(irBuiltIns.eqeqSymbol, intrinsics.jsEquals.owner) // TODO: implement it a right way add(irBuiltIns.ieee754equalsFunByOperandType, intrinsics.jsEqeqeq) @@ -147,24 +142,39 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL } } } + + for (type in primitiveNumbers) { + op(type, OperatorNames.ADD, withLongCoercion(intrinsics.jsPlus)) + op(type, OperatorNames.SUB, withLongCoercion(intrinsics.jsMinus)) + op(type, OperatorNames.MUL, withLongCoercion(intrinsics.jsMult)) + op(type, OperatorNames.DIV, withLongCoercion(intrinsics.jsDiv)) + op(type, OperatorNames.MOD, withLongCoercion(intrinsics.jsMod)) + op(type, OperatorNames.REM, withLongCoercion(intrinsics.jsMod)) + } } nameToIrTransformer.run { addWithPredicate( Name.special(Namer.KCALLABLE_GET_NAME), - { call -> call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kCallableClass) } ?: false }, + { call -> + call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kCallableClass) } ?: false + }, { call -> irCall(call, context.intrinsics.jsName.symbol, dispatchReceiverAsFirstArgument = true) }) addWithPredicate( Name.identifier(Namer.KPROPERTY_GET), - { call -> call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kPropertyClass) } ?: false }, - { call -> irCall(call, context.intrinsics.jsPropertyGet.symbol, dispatchReceiverAsFirstArgument = true)} + { call -> + call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kPropertyClass) } ?: false + }, + { call -> irCall(call, context.intrinsics.jsPropertyGet.symbol, dispatchReceiverAsFirstArgument = true) } ) addWithPredicate( Name.identifier(Namer.KPROPERTY_SET), - { call -> call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kPropertyClass) } ?: false}, - { call -> irCall(call, context.intrinsics.jsPropertySet.symbol, dispatchReceiverAsFirstArgument = true)} + { call -> + call.symbol.owner.dispatchReceiverParameter?.run { type.isSubtypeOfClass(context.irBuiltIns.kPropertyClass) } ?: false + }, + { call -> irCall(call, context.intrinsics.jsPropertySet.symbol, dispatchReceiverAsFirstArgument = true) } ) addWithPredicate( @@ -189,12 +199,44 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL override fun lower(irFile: IrFile) { irFile.transform(object : IrElementTransformerVoid() { + + // TODO should this be a separate lowering? + override fun visitConst(expression: IrConst): IrExpression { + if (expression.kind is IrConstKind.Long) { + val value = IrConstKind.Long.valueOf(expression) + val high = (value shr 32).toInt() + val low = value.toInt() + return IrCallImpl( + expression.startOffset, + expression.endOffset, + context.intrinsics.longConstructor.owner.returnType, + context.intrinsics.longConstructor + ).apply { + putValueArgument(0, JsIrBuilder.buildInt(context.irBuiltIns.intType, low)) + putValueArgument(1, JsIrBuilder.buildInt(context.irBuiltIns.intType, high)) + } + } + return super.visitConst(expression) + } + override fun visitCall(expression: IrCall): IrExpression { val call = super.visitCall(expression) if (call is IrCall) { val symbol = call.symbol + // TODO create another map for this? Lower == to equals beforehand? + if (symbol == irBuiltIns.eqeqSymbol) { + val lhs = call.getValueArgument(0)!! + val rhs = call.getValueArgument(1)!! + + return when (translateEquals(lhs.type, rhs.type)) { + is IdentityOperator -> irCall(call, intrinsics.jsEqeqeq.symbol) + is EqualityOperator -> irCall(call, intrinsics.jsEqeq.symbol) + else -> irCall(call, intrinsics.jsEquals) + } + } + symbolToIrFunction[symbol]?.let { return irCall(call, it.symbol) } @@ -229,6 +271,58 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL }, null) } + private fun withLongCoercion(intrinsic: IrSimpleFunction): (IrCall) -> IrExpression = { call -> + assert(call.valueArgumentsCount == 1) + val arg = call.getValueArgument(0)!! + val receiverType = call.dispatchReceiver!!.type + + if (arg.type.isLong()) { + when { + // Double OP Long => Double OP Long.toDouble() + receiverType.isDouble() -> { + call.putValueArgument(0, IrCallImpl( + call.startOffset, + call.endOffset, + context.intrinsics.longToDouble.owner.returnType, + context.intrinsics.longToDouble + ).apply { + dispatchReceiver = arg + }) + } + // Float OP Long => Float OP Long.toFloat() + receiverType.isFloat() -> { + call.putValueArgument(0, IrCallImpl( + + call.startOffset, + call.endOffset, + context.intrinsics.longToFloat.owner.returnType, + context.intrinsics.longToFloat + ).apply { + dispatchReceiver = arg + }) + } + // {Byte, Short, Int} OP Long => {Byte, Sort, Int}.toLong() OP Long + !receiverType.isLong() -> { + call.dispatchReceiver = IrCallImpl( + call.startOffset, + call.endOffset, + intrinsics.jsNumberToLong.owner.returnType, + intrinsics.jsNumberToLong + ).apply { + putValueArgument(0, call.dispatchReceiver) + } + } + } + } + + if (receiverType.isLong()) { + // LHS is Long => use as is + call + } else { + irCall(call, intrinsic.symbol, dispatchReceiverAsFirstArgument = true) + } + } + private fun transformEquals(call: IrCall): IrExpression { if (call.superQualifier != null) return call val symbol = call.symbol @@ -434,11 +528,16 @@ private fun MutableMap.add(from: IrFunctionSymbol, to: put(from, to) } -private fun MutableMap IrExpression>.addWithPredicate(from: K, predicate: (IrCall) -> Boolean, action: (IrCall) -> IrExpression) { +private fun MutableMap IrExpression>.addWithPredicate( + from: K, + predicate: (IrCall) -> Boolean, + action: (IrCall) -> IrExpression +) { put(from) { call: IrCall -> select({ predicate(call) }, { action(call) }, { call }) } } -private inline fun select(crossinline predicate: () -> Boolean, crossinline ifTrue: () -> T, crossinline ifFalse: () -> T): T = if (predicate()) ifTrue() else ifFalse() +private inline fun select(crossinline predicate: () -> Boolean, crossinline ifTrue: () -> T, crossinline ifFalse: () -> T): T = + if (predicate()) ifTrue() else ifFalse() private class SimpleMemberKey(val klass: IrType, val name: Name) { // TODO drop custom equals and hashCode when IrTypes will have right equals diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index a1e27e9c25f..8d5f57f483d 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -82,8 +82,8 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer JsIntLiteral(kind.valueOf(expression).toInt()) is IrConstKind.Short -> JsIntLiteral(kind.valueOf(expression).toInt()) is IrConstKind.Int -> JsIntLiteral(kind.valueOf(expression)) - is IrConstKind.Long, - is IrConstKind.Char -> super.visitConst(expression, context) + is IrConstKind.Long -> throw IllegalStateException("Long const should have been lowered at this point") + is IrConstKind.Char -> TODO("Char const") is IrConstKind.Float -> JsDoubleLiteral(kind.valueOf(expression).toDouble()) is IrConstKind.Double -> JsDoubleLiteral(kind.valueOf(expression)) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/OperatorNames.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/OperatorNames.kt index 7248a0c4a90..a17e1038ed5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/OperatorNames.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/OperatorNames.kt @@ -26,7 +26,7 @@ object OperatorNames { val SHL = Name.identifier("shl") val SHR = Name.identifier("shr") - val SHRU = Name.identifier("shru") + val SHRU = Name.identifier("ushr") val NOT = OperatorNameConventions.NOT diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt index 950a9555256..b9c504211e8 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt @@ -46,13 +46,17 @@ abstract class BasicIrBoxTest( val runtime = listOf( "libraries/stdlib/js/src/kotlin/core.kt", "libraries/stdlib/js/irRuntime/core.kt", + "libraries/stdlib/js/irRuntime/long.kt", + "libraries/stdlib/js/irRuntime/longjs.kt", "libraries/stdlib/js/irRuntime/numberConversion.kt", "libraries/stdlib/js/irRuntime/compareTo.kt", "libraries/stdlib/js/irRuntime/annotations.kt", "libraries/stdlib/js/irRuntime/DefaultConstructorMarker.kt", "libraries/stdlib/js/irRuntime/exceptions.kt", "libraries/stdlib/js/irRuntime/internalAnnotations.kt", - "libraries/stdlib/js/irRuntime/typeCheckUtils.kt" + "libraries/stdlib/js/irRuntime/typeCheckUtils.kt", + "core/builtins/native/kotlin/Number.kt", + "core/builtins/native/kotlin/Comparable.kt" ).map { createPsiFile(it) } val filesToIgnore = listOf( diff --git a/libraries/stdlib/js/irRuntime/core.kt b/libraries/stdlib/js/irRuntime/core.kt index a899b7964af..4996f1b88c5 100644 --- a/libraries/stdlib/js/irRuntime/core.kt +++ b/libraries/stdlib/js/irRuntime/core.kt @@ -6,17 +6,16 @@ package kotlin.js fun equals(obj1: dynamic, obj2: dynamic): Boolean { - if (obj1 == null) { - return obj2 == null + if (js("obj1 == null").unsafeCast()) { + return js("obj2 == null").unsafeCast(); } - - if (obj2 == null) { - return false + if (js("obj2 == null").unsafeCast()) { + return false; } return js(""" - if (typeof obj1 === "object" && typeof obj1.equals === "function") { - return obj1.equals(obj2); + if (typeof obj1 === "object" && typeof obj1.equals_Any_ === "function") { + return obj1.equals_Any_(obj2); } if (obj1 !== obj1) { @@ -31,7 +30,7 @@ fun equals(obj1: dynamic, obj2: dynamic): Boolean { } fun toString(o: dynamic): String = when { - o == null -> "null" + js("o == null").unsafeCast() -> "null" isArrayish(o) -> "[...]" else -> js("o.toString()").unsafeCast() } diff --git a/libraries/stdlib/js/irRuntime/long.kt b/libraries/stdlib/js/irRuntime/long.kt new file mode 100644 index 00000000000..f8766a470dc --- /dev/null +++ b/libraries/stdlib/js/irRuntime/long.kt @@ -0,0 +1,253 @@ +/* + * 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 + +/** + * Represents a 64-bit signed integer. + */ +public class Long internal constructor( + internal val low: Int, + internal val high: Int +) : Number(), Comparable { + + companion object { + /** + * A constant holding the minimum value an instance of Long can have. + */ + public const val MIN_VALUE: Long = -9223372036854775807L - 1L + + /** + * A constant holding the maximum value an instance of Long can have. + */ + public const val MAX_VALUE: Long = 9223372036854775807L + } + + /** + * Compares this value with the specified value for order. + * Returns zero if this value is equal to the specified other value, a negative number if it's less than other, + * or a positive number if it's greater than other. + */ + public inline operator fun compareTo(other: Byte): Int = compareTo(other.toLong()) + + /** + * Compares this value with the specified value for order. + * Returns zero if this value is equal to the specified other value, a negative number if it's less than other, + * or a positive number if it's greater than other. + */ + public inline operator fun compareTo(other: Short): Int = compareTo(other.toLong()) + + /** + * Compares this value with the specified value for order. + * Returns zero if this value is equal to the specified other value, a negative number if it's less than other, + * or a positive number if it's greater than other. + */ + public inline operator fun compareTo(other: Int): Int = compareTo(other.toLong()) + + /** + * Compares this value with the specified value for order. + * Returns zero if this value is equal to the specified other value, a negative number if it's less than other, + * or a positive number if it's greater than other. + */ + public override operator fun compareTo(other: Long): Int = compare(other) + + /** + * Compares this value with the specified value for order. + * Returns zero if this value is equal to the specified other value, a negative number if it's less than other, + * or a positive number if it's greater than other. + */ + public inline operator fun compareTo(other: Float): Int = toFloat().compareTo(other) + + /** + * Compares this value with the specified value for order. + * Returns zero if this value is equal to the specified other value, a negative number if it's less than other, + * or a positive number if it's greater than other. + */ + public inline operator fun compareTo(other: Double): Int = toDouble().compareTo(other) + + /** Adds the other value to this value. */ + public inline operator fun plus(other: Byte): Long = plus(other.toLong()) + + /** Adds the other value to this value. */ + public inline operator fun plus(other: Short): Long = plus(other.toLong()) + + /** Adds the other value to this value. */ + public inline operator fun plus(other: Int): Long = plus(other.toLong()) + + /** Adds the other value to this value. */ + public operator fun plus(other: Long): Long = add(other) + + /** Adds the other value to this value. */ + public inline operator fun plus(other: Float): Float = toFloat() + other + + /** Adds the other value to this value. */ + public inline operator fun plus(other: Double): Double = toDouble() + other + + /** Subtracts the other value from this value. */ + public inline operator fun minus(other: Byte): Long = minus(other.toLong()) + + /** Subtracts the other value from this value. */ + public inline operator fun minus(other: Short): Long = minus(other.toLong()) + + /** Subtracts the other value from this value. */ + public inline operator fun minus(other: Int): Long = minus(other.toLong()) + + /** Subtracts the other value from this value. */ + public operator fun minus(other: Long): Long = subtract(other) + + /** Subtracts the other value from this value. */ + public inline operator fun minus(other: Float): Float = toFloat() - other + + /** Subtracts the other value from this value. */ + public inline operator fun minus(other: Double): Double = toDouble() - other + + /** Multiplies this value by the other value. */ + public inline operator fun times(other: Byte): Long = times(other.toLong()) + + /** Multiplies this value by the other value. */ + public inline operator fun times(other: Short): Long = times(other.toLong()) + + /** Multiplies this value by the other value. */ + public inline operator fun times(other: Int): Long = times(other.toLong()) + + /** Multiplies this value by the other value. */ + public operator fun times(other: Long): Long = multiply(other) + + /** Multiplies this value by the other value. */ + public inline operator fun times(other: Float): Float = toFloat() * other + + /** Multiplies this value by the other value. */ + public inline operator fun times(other: Double): Double = toDouble() * other + + /** Divides this value by the other value. */ + public inline operator fun div(other: Byte): Long = div(other.toLong()) + + /** Divides this value by the other value. */ + public inline operator fun div(other: Short): Long = div(other.toLong()) + + /** Divides this value by the other value. */ + public inline operator fun div(other: Int): Long = div(other.toLong()) + + /** Divides this value by the other value. */ + public operator fun div(other: Long): Long = divide(other) + + /** Divides this value by the other value. */ + public inline operator fun div(other: Float): Float = toFloat() / other + + /** Divides this value by the other value. */ + public inline operator fun div(other: Double): Double = toDouble() / other + + /** Calculates the remainder of dividing this value by the other value. */ + @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING) + public inline operator fun mod(other: Byte): Long = rem(other) + + /** Calculates the remainder of dividing this value by the other value. */ + @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING) + public inline operator fun mod(other: Short): Long = rem(other) + + /** Calculates the remainder of dividing this value by the other value. */ + @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING) + public inline operator fun mod(other: Int): Long = rem(other) + + /** Calculates the remainder of dividing this value by the other value. */ + @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING) + public inline operator fun mod(other: Long): Long = rem(other) + + /** Calculates the remainder of dividing this value by the other value. */ + @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING) + public inline operator fun mod(other: Float): Float = rem(other) + + /** Calculates the remainder of dividing this value by the other value. */ + @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING) + public inline operator fun mod(other: Double): Double = rem(other) + + /** Calculates the remainder of dividing this value by the other value. */ + @SinceKotlin("1.1") + public inline operator fun rem(other: Byte): Long = rem(other.toLong()) + + /** Calculates the remainder of dividing this value by the other value. */ + @SinceKotlin("1.1") + public inline operator fun rem(other: Short): Long = rem(other.toLong()) + + /** Calculates the remainder of dividing this value by the other value. */ + @SinceKotlin("1.1") + public inline operator fun rem(other: Int): Long = rem(other.toLong()) + + /** Calculates the remainder of dividing this value by the other value. */ + @SinceKotlin("1.1") + public operator fun rem(other: Long): Long = modulo(other) + + /** Calculates the remainder of dividing this value by the other value. */ + @SinceKotlin("1.1") + public inline operator fun rem(other: Float): Float = toFloat() % other + + /** Calculates the remainder of dividing this value by the other value. */ + @SinceKotlin("1.1") + public inline operator fun rem(other: Double): Double = toDouble() % other + + /** Increments this value. */ + public operator fun inc(): Long = this + 1L + + /** Decrements this value. */ + public operator fun dec(): Long = this - 1L + + /** Returns this value. */ + public inline operator fun unaryPlus(): Long = this + + /** Returns the negative of this value. */ + public operator fun unaryMinus(): Long = inv() + 1L + +/* TODO + /** Creates a range from this value to the specified [other] value. */ + public operator fun rangeTo(other: Byte): LongRange + + /** Creates a range from this value to the specified [other] value. */ + public operator fun rangeTo(other: Short): LongRange + + /** Creates a range from this value to the specified [other] value. */ + public operator fun rangeTo(other: Int): LongRange + + /** Creates a range from this value to the specified [other] value. */ + public operator fun rangeTo(other: Long): LongRange +*/ + + /** Shifts this value left by the [bitCount] number of bits. */ + public infix fun shl(bitCount: Int): Long = shiftLeft(bitCount) + + /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. */ + public infix fun shr(bitCount: Int): Long = shiftRight(bitCount) + + /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. */ + public infix fun ushr(bitCount: Int): Long = shiftRightUnsigned(bitCount) + + /** Performs a bitwise AND operation between the two values. */ + public infix fun and(other: Long): Long = Long(low and other.low, high and other.high) + + /** Performs a bitwise OR operation between the two values. */ + public infix fun or(other: Long): Long = Long(low or other.low, high or other.high) + + /** Performs a bitwise XOR operation between the two values. */ + public infix fun xor(other: Long): Long = Long(low xor other.low, high xor other.high) + + /** Inverts the bits in this value. */ + public fun inv(): Long = Long(low.inv(), high.inv()) + + public override fun toByte(): Byte = low.toByte() + public override fun toChar(): Char = low.toChar() + public override fun toShort(): Short = low.toShort() + public override fun toInt(): Int = low + public override fun toLong(): Long = this + public override fun toFloat(): Float = toDouble().toFloat() + public override fun toDouble(): Double = toNumber() + + // TODO: is it still needed? + private fun valueOf() = toDouble() + + override fun equals(other: Any?): Boolean = other is Long && equalsLong(other) + + override fun hashCode(): Int = hashCode(this) + + override fun toString(): String = toString(10) +} \ No newline at end of file diff --git a/libraries/stdlib/js/irRuntime/longjs.kt b/libraries/stdlib/js/irRuntime/longjs.kt new file mode 100644 index 00000000000..ac2212927d8 --- /dev/null +++ b/libraries/stdlib/js/irRuntime/longjs.kt @@ -0,0 +1,380 @@ +/* + * 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. + */ + + +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// 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. + +package kotlin + +internal fun Long.toNumber() = high * TWO_PWR_32_DBL_ + getLowBitsUnsigned() + +internal fun Long.getLowBitsUnsigned() = if (low >= 0) low.toDouble() else TWO_PWR_32_DBL_ + low + +internal fun hashCode(l: Long) = l.low xor l.high + +internal fun Long.toString(radix: Int): String { + if (radix < 2 || 36 < radix) { + throw Exception("radix out of range: $radix") + } + + if (isZero()) { + return "0" + } + + if (isNegative()) { + if (equalsLong(MIN_VALUE)) { + // We need to change the Long value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + val radixLong = fromInt(radix) + val div = div(radixLong) + val rem = div.multiply(radixLong).subtract(this).toInt(); + return js("div.toString(radix) + rem.toString(radix)") + } else { + return "-${negate().toString()}" + } + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + val radixToPower = fromNumber(js("Math.pow(radix, 6)").unsafeCast()) + + var rem = this + var result = "" + while (true) { + val remDiv = rem.div(radixToPower) + val intval = rem.subtract(remDiv.multiply(radixToPower)).toInt() + var digits = js("intval.toString(radix)").unsafeCast() + + rem = remDiv + if (rem.isZero()) { + return digits + result + } else { + while (digits.length < 6) { + digits = "0" + digits + } + result = digits + result + } + } +} + +internal fun Long.negate() = unaryMinus() + +internal fun Long.isZero() = high == 0 && low == 0 + +internal fun Long.isNegative() = high < 0 + +internal fun Long.isOdd() = low and 1 == 1 + +internal fun Long.equalsLong(other: Long) = high == other.high && low == other.low + +internal fun Long.lessThan(other: Long) = compare(other) < 0 + +internal fun Long.lessThanOrEqual(other: Long) = compare(other) <= 0 + +internal fun Long.greaterThan(other: Long) = compare(other) > 0 + +internal fun Long.greaterThanOrEqual(other: Long) = compare(other) >= 0 + +internal fun Long.compare(other: Long): Int { + if (equalsLong(other)) { + return 0; + } + + val thisNeg = isNegative(); + val otherNeg = other.isNegative(); + + return when { + thisNeg && !otherNeg -> -1 + !thisNeg && otherNeg -> 1 + // at this point, the signs are the same, so subtraction will not overflow + subtract(other).isNegative() -> -1 + else -> 1 + } +} + +internal fun Long.add(other: Long): Long { + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + val a48 = high ushr 16 + val a32 = high and 0xFFFF + val a16 = high ushr 16 + val a00 = low and 0xFFFF + + val b48 = other.high ushr 16 + val b32 = other.high and 0xFFFF + val b16 = other.low ushr 16 + val b00 = other.low and 0xFFFF + + var c48 = 0 + var c32 = 0 + var c16 = 0 + var c00 = 0 + c00 += a00 + b00 + c16 += c00 ushr 16 + c00 = c00 and 0xFFFF + c16 += a16 + b16 + c32 += c16 ushr 16 + c16 = c16 and 0xFFFF + c32 += a32 + b32 + c48 += c32 ushr 16 + c32 = c32 and 0xFFFF + c48 += a48 + b48 + c48 = c48 and 0xFFFF + return Long((c16 shl 16) or c00, (c48 shl 16) or c32) +} + +internal fun Long.subtract(other: Long) = add(other.unaryMinus()) + +internal fun Long.multiply(other: Long): Long { + if (isZero()) { + return ZERO + } else if (other.isZero()) { + return ZERO + } + + if (equalsLong(MIN_VALUE)) { + return if (other.isOdd()) MIN_VALUE else ZERO + } else if (other.equalsLong(MIN_VALUE)) { + return if (isOdd()) MIN_VALUE else ZERO + } + + if (isNegative()) { + return if (other.isNegative()) { + negate().multiply(other.negate()) + } else { + negate().multiply(other).negate() + } + } else if (other.isNegative()) { + return multiply(other.negate()).negate() + } + + // If both longs are small, use float multiplication + if (lessThan(TWO_PWR_24_) && other.lessThan(TWO_PWR_24_)) { + return fromNumber(toNumber() * other.toNumber()) + } + + // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + val a48 = high ushr 16 + val a32 = high and 0xFFFF + val a16 = low ushr 16 + val a00 = low and 0xFFFF + + val b48 = other.high ushr 16 + val b32 = other.high and 0xFFFF + val b16 = other.low ushr 16 + val b00 = other.low and 0xFFFF + + var c48 = 0 + var c32 = 0 + var c16 = 0 + var c00 = 0 + c00 += a00 * b00 + c16 += c00 ushr 16 + c00 = c00 and 0xFFFF + c16 += a16 * b00 + c32 += c16 ushr 16 + c16 = c16 and 0xFFFF + c16 += a00 * b16 + c32 += c16 ushr 16 + c16 = c16 and 0xFFFF + c32 += a32 * b00 + c48 += c32 ushr 16 + c32 = c32 and 0xFFFF + c32 += a16 * b16 + c48 += c32 ushr 16 + c32 = c32 and 0xFFFF + c32 += a00 * b32 + c48 += c32 ushr 16 + c32 = c32 and 0xFFFF + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48 + c48 = c48 and 0xFFFF + return Long(c16 shl 16 or c00, c48 shl 16 or c32) +} + +internal fun Long.divide(other: Long): Long { + if (other.isZero()) { + throw Exception("division by zero") + } else if (isZero()) { + return ZERO + } + + if (equalsLong(MIN_VALUE)) { + if (other.equalsLong(ONE) || other.equalsLong(NEG_ONE)) { + return MIN_VALUE // recall that -MIN_VALUE == MIN_VALUE + } else if (other.equalsLong(MIN_VALUE)) { + return ONE + } else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + val halfThis = shiftRight(1) + val approx = halfThis.div(other).shiftLeft(1) + if (approx.equalsLong(ZERO)) { + return if (other.isNegative()) ONE else NEG_ONE + } else { + val rem = subtract(other.multiply(approx)) + return approx.add(rem.div(other)) + } + } + } else if (other.equalsLong(MIN_VALUE)) { + return ZERO + } + + if (isNegative()) { + return if (other.isNegative()) { + negate().div(other.negate()) + } else { + negate().div(other).negate() + } + } else if (other.isNegative()) { + return div(other.negate()).negate() + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + var res = ZERO + var rem = this + while (rem.greaterThanOrEqual(other)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + val approxDouble = rem.toNumber() / other.toNumber() + var approx = js("Math.max(1, Math.floor(approxDouble))").unsafeCast() + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + val log2 = js("Math.ceil(Math.log(approx) / Math.LN2)").unsafeCast() + val delta = if (log2 <= 48) 1.0 else js("Math.pow(2, log2 - 48)").unsafeCast() + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + var approxRes = fromNumber(approx) + var approxRem = approxRes.multiply(other) + while (approxRem.isNegative() || approxRem.greaterThan(rem)) { + approx -= delta + approxRes = fromNumber(approx) + approxRem = approxRes.multiply(other) + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) { + approxRes = ONE + } + + res = res.add(approxRes) + rem = rem.subtract(approxRem) + } + return res +} + +internal fun Long.modulo(other: Long) = subtract(div(other).multiply(other)) + +internal fun Long.shiftLeft(numBits: Int): Long { + val numBits = numBits and 63 + if (numBits == 0) { + return this + } else { + if (numBits < 32) { + return Long(low shl numBits, (high shl numBits) or (low ushr (32 - numBits))) + } else { + return Long(0, low shl (numBits - 32)) + } + } +} + +internal fun Long.shiftRight(numBits: Int): Long { + val numBits = numBits and 63 + if (numBits == 0) { + return this + } else { + if (numBits < 32) { + return Long((low ushr numBits) or (high shl (32 - numBits)), high shr numBits) + } else { + return Long(high shr (numBits - 32), if (high >= 0) 0 else -1) + } + } +} + +internal fun Long.shiftRightUnsigned(numBits: Int): Long { + val numBits = numBits and 63 + if (numBits == 0) { + return this + } else { + if (numBits < 32) { + return Long((low ushr numBits) or (high shl (32 - numBits)), high ushr numBits) + } else return if (numBits == 32) { + Long(high, 0) + } else { + Long(high ushr (numBits - 32), 0) + } + } +} + +/** + * Returns a Long representing the given (32-bit) integer value. + * @param {number} value The 32-bit integer in question. + * @return {!Kotlin.Long} The corresponding Long value. + */ +// TODO: cache +internal fun fromInt(value: Int) = Long(value, if (value < 0) -1 else 0) + +/** + * Returns a Long representing the given value, provided that it is a finite + * number. Otherwise, zero is returned. + * @param {number} value The number in question. + * @return {!Kotlin.Long} The corresponding Long value. + */ +internal fun fromNumber(value: Double): Long { + if (js("isNaN(value)").unsafeCast() || !js("isFinite(value)").unsafeCast()) { + return ZERO; + } else if (value <= -TWO_PWR_63_DBL_) { + return MIN_VALUE; + } else if (value + 1 >= TWO_PWR_63_DBL_) { + return MAX_VALUE; + } else if (value < 0) { + return fromNumber(-value).negate(); + } else { + val twoPwr32 = TWO_PWR_32_DBL_ + return Long( + js("(value % twoPwr32) | 0").unsafeCast(), + js("(value / twoPwr32) | 0").unsafeCast() + ) + } +} + +private val TWO_PWR_16_DBL_ = (1 shl 16).toDouble() + +private val TWO_PWR_24_DBL_ = (1 shl 24).toDouble() + +private val TWO_PWR_32_DBL_ = TWO_PWR_16_DBL_ * TWO_PWR_16_DBL_ + +private val TWO_PWR_64_DBL_ = TWO_PWR_32_DBL_ * TWO_PWR_32_DBL_ + +private val TWO_PWR_63_DBL_ = TWO_PWR_64_DBL_ / 2 + +private val ZERO = fromInt(0) + +private val ONE = fromInt(1) + +private val NEG_ONE = fromInt(-1) + +private val MAX_VALUE = Long(-1, -1 ushr 1) + +private val MIN_VALUE = Long(0, 1 shl 31) + +private val TWO_PWR_24_ = fromInt(1 shl 24) diff --git a/libraries/stdlib/js/irRuntime/numberConversion.kt b/libraries/stdlib/js/irRuntime/numberConversion.kt index 41c3d99b113..4c2fe415ea0 100644 --- a/libraries/stdlib/js/irRuntime/numberConversion.kt +++ b/libraries/stdlib/js/irRuntime/numberConversion.kt @@ -19,6 +19,10 @@ fun numberToShort(a: dynamic): Short = toShort(numberToInt(a)) fun toByte(a: dynamic): Byte = js("a << 24 >> 24").unsafeCast() fun toShort(a: dynamic): Short = js("a << 16 >> 16").unsafeCast() +fun numberToLong(a: dynamic): Long = fromNumber(a) + +fun toLong(a: dynamic): Long = fromInt(a) + fun doubleToInt(a: dynamic) = js(""" if (a > 2147483647) return 2147483647; if (a < -2147483648) return -2147483648; diff --git a/libraries/stdlib/js/irRuntime/typeCheckUtils.kt b/libraries/stdlib/js/irRuntime/typeCheckUtils.kt index dba0d06c8b2..f943bc0eee5 100644 --- a/libraries/stdlib/js/irRuntime/typeCheckUtils.kt +++ b/libraries/stdlib/js/irRuntime/typeCheckUtils.kt @@ -6,7 +6,7 @@ package kotlin.js @kotlin.internal.DynamicExtension -public fun dynamic.unsafeCast(): T = this +public inline fun dynamic.unsafeCast(): T = this private fun isInterfaceImpl(ctor: dynamic, iface: dynamic): Boolean { if (ctor === iface) return true;