From 36e62471256a4dbb483f0da4b4aa81d1a487ddce Mon Sep 17 00:00:00 2001 From: Abduqodiri Qurbonzoda Date: Mon, 14 Sep 2020 00:06:57 +0300 Subject: [PATCH] Add more details about bit shift operations #KT-41112 --- core/builtins/native/kotlin/Primitives.kt | 48 ++++++++++++++++--- generators/builtins/primitives.kt | 26 ++++++++-- generators/builtins/unsignedTypes.kt | 8 +++- libraries/stdlib/js-ir/builtins/Primitives.kt | 24 ++++++++-- libraries/stdlib/js-ir/runtime/long.kt | 21 ++++++-- .../test/numbers/BitwiseOperationsTest.kt | 44 +++++++++++++++++ libraries/stdlib/unsigned/src/kotlin/UInt.kt | 16 ++++++- libraries/stdlib/unsigned/src/kotlin/ULong.kt | 16 ++++++- .../wasm/builtins/native/kotlin/Primitives.kt | 42 +++++++++++++--- 9 files changed, 219 insertions(+), 26 deletions(-) diff --git a/core/builtins/native/kotlin/Primitives.kt b/core/builtins/native/kotlin/Primitives.kt index 9bf85e13b27..3833a6d9293 100644 --- a/core/builtins/native/kotlin/Primitives.kt +++ b/core/builtins/native/kotlin/Primitives.kt @@ -589,12 +589,30 @@ public class Int private constructor() : Number(), Comparable { /** 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. */ + /** + * Shifts this value left by the [bitCount] number of bits. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ public infix fun shl(bitCount: Int): Int - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. */ + + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ public infix fun shr(bitCount: Int): Int - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. */ + + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ public infix fun ushr(bitCount: Int): Int + /** Performs a bitwise AND operation between the two values. */ public infix fun and(other: Int): Int /** Performs a bitwise OR operation between the two values. */ @@ -818,12 +836,30 @@ public class Long private constructor() : Number(), Comparable { /** 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. */ + /** + * Shifts this value left by the [bitCount] number of bits. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ public infix fun shl(bitCount: Int): Long - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. */ + + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ public infix fun shr(bitCount: Int): Long - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. */ + + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ public infix fun ushr(bitCount: Int): Long + /** Performs a bitwise AND operation between the two values. */ public infix fun and(other: Long): Long /** Performs a bitwise OR operation between the two values. */ diff --git a/generators/builtins/primitives.kt b/generators/builtins/primitives.kt index 67d0fe29bd8..a51bbd5777c 100644 --- a/generators/builtins/primitives.kt +++ b/generators/builtins/primitives.kt @@ -31,6 +31,19 @@ class GeneratePrimitives(out: PrintWriter) : BuiltInsSourceGenerator(out) { "and" to "Performs a bitwise AND operation between the two values.", "or" to "Performs a bitwise OR operation between the two values.", "xor" to "Performs a bitwise XOR operation between the two values.") + + + internal fun shiftOperatorsDocDetail(kind: PrimitiveType): String { + val bitsUsed = when (kind) { + PrimitiveType.INT -> "five" + PrimitiveType.LONG -> "six" + else -> throw IllegalArgumentException("Bit shift operation is not implemented for $kind") + } + return """ + * Note that only the $bitsUsed lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..${kind.bitSize - 1}`. + """ + } } private val typeDescriptions: Map = mapOf( PrimitiveType.DOUBLE to "double-precision 64-bit IEEE 754 floating point number", @@ -124,7 +137,7 @@ class GeneratePrimitives(out: PrintWriter) : BuiltInsSourceGenerator(out) { generateRangeTo(kind) if (kind == PrimitiveType.INT || kind == PrimitiveType.LONG) { - generateBitShiftOperators(className) + generateBitShiftOperators(kind) } if (kind == PrimitiveType.INT || kind == PrimitiveType.LONG /* || kind == PrimitiveType.BYTE || kind == PrimitiveType.SHORT */) { generateBitwiseOperators(className, since = if (kind == PrimitiveType.BYTE || kind == PrimitiveType.SHORT) "1.1" else null) @@ -202,10 +215,17 @@ class GeneratePrimitives(out: PrintWriter) : BuiltInsSourceGenerator(out) { out.println() } - private fun generateBitShiftOperators(className: String) { + private fun generateBitShiftOperators(kind: PrimitiveType) { + val className = kind.capitalized + val detail = shiftOperatorsDocDetail(kind) for ((name, doc) in shiftOperators) { - out.println(" /** $doc */") + out.println(" /**") + out.println(" * $doc") + out.println(" *") + out.println(detail.replaceIndent(" ")) + out.println(" */") out.println(" public infix fun $name(bitCount: Int): $className") + out.println() } } private fun generateBitwiseOperators(className: String, since: String?) { diff --git a/generators/builtins/unsignedTypes.kt b/generators/builtins/unsignedTypes.kt index fc630447c94..b7eb8bdc6f1 100644 --- a/generators/builtins/unsignedTypes.kt +++ b/generators/builtins/unsignedTypes.kt @@ -176,9 +176,15 @@ class UnsignedTypeGenerator(val type: UnsignedType, out: PrintWriter) : BuiltIns fun generateShiftOperator(name: String, implementation: String = name) { val doc = GeneratePrimitives.shiftOperators[implementation]!! - out.println(" /** $doc */") + val detail = GeneratePrimitives.shiftOperatorsDocDetail(type.asSigned) + out.println(" /**") + out.println(" * $doc") + out.println(" *") + out.println(detail.replaceIndent(" ")) + out.println(" */") out.println(" @kotlin.internal.InlineOnly") out.println(" public inline infix fun $name(bitCount: Int): $className = $className(data $implementation bitCount)") + out.println() } generateShiftOperator("shl") diff --git a/libraries/stdlib/js-ir/builtins/Primitives.kt b/libraries/stdlib/js-ir/builtins/Primitives.kt index bae513e8277..9269673392a 100644 --- a/libraries/stdlib/js-ir/builtins/Primitives.kt +++ b/libraries/stdlib/js-ir/builtins/Primitives.kt @@ -598,12 +598,30 @@ public class Int private constructor() : Number(), Comparable { /** 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. */ + /** + * Shifts this value left by the [bitCount] number of bits. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ public infix fun shl(bitCount: Int): Int - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. */ + + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ public infix fun shr(bitCount: Int): Int - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. */ + + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ public infix fun ushr(bitCount: Int): Int + /** Performs a bitwise AND operation between the two values. */ public infix fun and(other: Int): Int /** Performs a bitwise OR operation between the two values. */ diff --git a/libraries/stdlib/js-ir/runtime/long.kt b/libraries/stdlib/js-ir/runtime/long.kt index 72105220b8e..5a3f49ceee5 100644 --- a/libraries/stdlib/js-ir/runtime/long.kt +++ b/libraries/stdlib/js-ir/runtime/long.kt @@ -201,13 +201,28 @@ public class Long internal constructor( /** Creates a range from this value to the specified [other] value. */ public operator fun rangeTo(other: Long): LongRange = LongRange(this, other) - /** Shifts this value left by the [bitCount] number of bits. */ + /** + * Shifts this value left by the [bitCount] number of bits. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ 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. */ + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ 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. */ + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ public infix fun ushr(bitCount: Int): Long = shiftRightUnsigned(bitCount) /** Performs a bitwise AND operation between the two values. */ diff --git a/libraries/stdlib/test/numbers/BitwiseOperationsTest.kt b/libraries/stdlib/test/numbers/BitwiseOperationsTest.kt index 552ac6c5e28..daf929371e6 100644 --- a/libraries/stdlib/test/numbers/BitwiseOperationsTest.kt +++ b/libraries/stdlib/test/numbers/BitwiseOperationsTest.kt @@ -23,14 +23,58 @@ class BitwiseOperationsTest { @Test fun shlForInt() { assertEquals(4, 1 shl 2) + assertEquals(4, 1 shl 34) } @Test fun shrForInt() { assertEquals(1, 2 shr 1) + assertEquals(1, 2 shr 33) } @Test fun ushrForInt() { assertEquals(2147483647, -1 ushr 1) + assertEquals(2147483647, -1 ushr 33) + } + + @Test fun shlForUInt() { + assertEquals(4u, 1u shl 2) + assertEquals(4u, 1u shl 34) + } + + @Test fun shrForUInt() { + assertEquals(1u, 2u shr 1) + assertEquals(1u, 2u shr 33) + + assertEquals(Int.MAX_VALUE.toUInt(), UInt.MAX_VALUE shr 1) + assertEquals(Int.MAX_VALUE.toUInt(), UInt.MAX_VALUE shr 33) + } + + @Test fun shlForLong() { + assertEquals(4L, 1L shl 2) + assertEquals(4L, 1L shl 66) + } + + @Test fun shrForLong() { + assertEquals(1L, 2L shr 1) + assertEquals(1L, 2L shr 65) + } + + @Test fun ushrForLong() { + assertEquals(Long.MAX_VALUE, -1L ushr 1) + assertEquals(Long.MAX_VALUE, -1L ushr 65) + } + + @Test fun shlForULong() { + assertEquals(4UL, 1UL shl 2) + assertEquals(4UL, 1UL shl 66) + } + + @Test fun shrForULong() { + assertEquals(1UL, 2UL shr 1) + assertEquals(1UL, 2UL shr 65) + + assertEquals(Long.MAX_VALUE.toULong(), ULong.MAX_VALUE shr 1) + assertEquals(Long.MAX_VALUE.toULong(), ULong.MAX_VALUE shr 65) } @Test fun invForInt() { diff --git a/libraries/stdlib/unsigned/src/kotlin/UInt.kt b/libraries/stdlib/unsigned/src/kotlin/UInt.kt index a718e9d84ff..c5273c3698d 100644 --- a/libraries/stdlib/unsigned/src/kotlin/UInt.kt +++ b/libraries/stdlib/unsigned/src/kotlin/UInt.kt @@ -145,12 +145,24 @@ public inline class UInt @PublishedApi internal constructor(@PublishedApi intern @kotlin.internal.InlineOnly public inline operator fun rangeTo(other: UInt): UIntRange = UIntRange(this, other) - /** Shifts this value left by the [bitCount] number of bits. */ + /** + * Shifts this value left by the [bitCount] number of bits. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ @kotlin.internal.InlineOnly public inline infix fun shl(bitCount: Int): UInt = UInt(data shl bitCount) - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. */ + + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ @kotlin.internal.InlineOnly public inline infix fun shr(bitCount: Int): UInt = UInt(data ushr bitCount) + /** Performs a bitwise AND operation between the two values. */ @kotlin.internal.InlineOnly public inline infix fun and(other: UInt): UInt = UInt(this.data and other.data) diff --git a/libraries/stdlib/unsigned/src/kotlin/ULong.kt b/libraries/stdlib/unsigned/src/kotlin/ULong.kt index d6b14540a28..db03542cfef 100644 --- a/libraries/stdlib/unsigned/src/kotlin/ULong.kt +++ b/libraries/stdlib/unsigned/src/kotlin/ULong.kt @@ -145,12 +145,24 @@ public inline class ULong @PublishedApi internal constructor(@PublishedApi inter @kotlin.internal.InlineOnly public inline operator fun rangeTo(other: ULong): ULongRange = ULongRange(this, other) - /** Shifts this value left by the [bitCount] number of bits. */ + /** + * Shifts this value left by the [bitCount] number of bits. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ @kotlin.internal.InlineOnly public inline infix fun shl(bitCount: Int): ULong = ULong(data shl bitCount) - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. */ + + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ @kotlin.internal.InlineOnly public inline infix fun shr(bitCount: Int): ULong = ULong(data ushr bitCount) + /** Performs a bitwise AND operation between the two values. */ @kotlin.internal.InlineOnly public inline infix fun and(other: ULong): ULong = ULong(this.data and other.data) diff --git a/libraries/stdlib/wasm/builtins/native/kotlin/Primitives.kt b/libraries/stdlib/wasm/builtins/native/kotlin/Primitives.kt index 2d7b6883a15..b645e6a3e90 100644 --- a/libraries/stdlib/wasm/builtins/native/kotlin/Primitives.kt +++ b/libraries/stdlib/wasm/builtins/native/kotlin/Primitives.kt @@ -844,17 +844,32 @@ public class Int private constructor() : Number(), Comparable { /** Returns the negative of this value. */ public inline operator fun unaryMinus(): Int = 0 - this - /** Shifts this value left by the [bitCount] number of bits. */ + /** + * Shifts this value left by the [bitCount] number of bits. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ @WasmInstruction(WasmInstruction.I32_SHL) public infix fun shl(bitCount: Int): Int = implementedAsIntrinsic - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. */ + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ @WasmInstruction(WasmInstruction.I32_SHR_S) public infix fun shr(bitCount: Int): Int = implementedAsIntrinsic - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. */ + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. + * + * Note that only the five lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..31`. + */ @WasmInstruction(WasmInstruction.I32_SHR_U) public infix fun ushr(bitCount: Int): Int = implementedAsIntrinsic @@ -1233,15 +1248,30 @@ public class Long private constructor() : Number(), Comparable { // return LongRange(this, other.toLong()) // } - /** Shifts this value left by the [bitCount] number of bits. */ + /** + * Shifts this value left by the [bitCount] number of bits. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ public inline infix fun shl(bitCount: Int): Long = wasm_i64_shl(this, bitCount.toLong()) - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. */ + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ public inline infix fun shr(bitCount: Int): Long = wasm_i64_shr_s(this, bitCount.toLong()) - /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. */ + /** + * Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. + * + * Note that only the six lowest-order bits of the [bitCount] are used as the shift distance. + * The shift distance actually used is therefore always in the range `0..63`. + */ public inline infix fun ushr(bitCount: Int): Long = wasm_i64_shr_u(this, bitCount.toLong())