From 78b72efd328842703c753c97b8b19165d9fb0988 Mon Sep 17 00:00:00 2001 From: Igor Yakovlev Date: Thu, 18 May 2023 19:19:12 +0200 Subject: [PATCH] [Wasm] Make Arrays' constructors with size and lambda inline Fixed #KT-58746 --- .../kotlin/backend/wasm/WasmLoweringPhases.kt | 17 +++ .../kotlin/backend/wasm/WasmSymbols.kt | 12 ++ .../dce/WasmUsefulDeclarationProcessor.kt | 12 +- .../lower/WasmArrayConstructorLowering.kt | 92 +++++++++++++++ .../arrays/nonLocalReturnArrayConstructor.kt | 2 - .../stdlib/wasm/builtins/kotlin/Array.kt | 22 ++-- .../stdlib/wasm/builtins/kotlin/Arrays.kt | 111 +++++++++++------- 7 files changed, 218 insertions(+), 50 deletions(-) create mode 100644 compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmArrayConstructorLowering.kt diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt index 7ea30eed0c2..377242de14f 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt @@ -18,6 +18,8 @@ import org.jetbrains.kotlin.backend.common.lower.optimizations.PropertyAccessorI import org.jetbrains.kotlin.backend.common.phaser.* import org.jetbrains.kotlin.backend.common.toMultiModuleAction import org.jetbrains.kotlin.backend.wasm.lower.* +import org.jetbrains.kotlin.backend.wasm.lower.WasmArrayConstructorLowering +import org.jetbrains.kotlin.backend.wasm.lower.WasmArrayConstructorReferenceLowering import org.jetbrains.kotlin.ir.backend.js.lower.* import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.AddContinuationToFunctionCallsLowering import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendFunctionsLowering @@ -113,6 +115,19 @@ private val lateinitUsageLoweringPhase = makeWasmModulePhase( description = "Insert checks for lateinit field references" ) +private val arrayConstructorReferencePhase = makeWasmModulePhase( + ::WasmArrayConstructorReferenceLowering, + name = "ArrayConstructorReference", + description = "Transform `::Array` into a lambda" +) + +private val arrayConstructorPhase = makeWasmModulePhase( + ::WasmArrayConstructorLowering, + name = "ArrayConstructor", + description = "Transform `Array(size) { index -> value }` into a loop", + prerequisite = setOf(arrayConstructorReferencePhase) +) + private val sharedVariablesLoweringPhase = makeWasmModulePhase( ::SharedVariablesLowering, name = "SharedVariablesLowering", @@ -607,6 +622,8 @@ val wasmPhases = SameTypeNamedCompilerPhase( lateinitNullableFieldsPhase then lateinitDeclarationLoweringPhase then lateinitUsageLoweringPhase then + arrayConstructorReferencePhase then + arrayConstructorPhase then sharedVariablesLoweringPhase then localClassesInInlineLambdasPhase then localClassesInInlineFunctionsPhase then diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt index bcf0b6a4dd2..bb9797d1849 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt @@ -194,6 +194,18 @@ class WasmSymbols( val wasmArrayCopy = getInternalFunction("wasm_array_copy") val wasmArrayNewData0 = getInternalFunction("array_new_data0") + val primitiveTypeToCreateTypedArray = mapOf( + context.irBuiltIns.arrayClass to getFunction("createAnyArray", kotlinTopLevelPackage), + context.irBuiltIns.booleanArray to getFunction("createBooleanArray", kotlinTopLevelPackage), + context.irBuiltIns.byteArray to getFunction("createByteArray", kotlinTopLevelPackage), + context.irBuiltIns.shortArray to getFunction("createShortArray", kotlinTopLevelPackage), + context.irBuiltIns.charArray to getFunction("createCharArray", kotlinTopLevelPackage), + context.irBuiltIns.intArray to getFunction("createIntArray", kotlinTopLevelPackage), + context.irBuiltIns.longArray to getFunction("createLongArray", kotlinTopLevelPackage), + context.irBuiltIns.floatArray to getFunction("createFloatArray", kotlinTopLevelPackage), + context.irBuiltIns.doubleArray to getFunction("createDoubleArray", kotlinTopLevelPackage), + ) + val intToLong = getInternalFunction("wasm_i64_extend_i32_s") val rangeCheck = getInternalFunction("rangeCheck") diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt index d55da537852..f6e74ce3801 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt @@ -141,6 +141,12 @@ internal class WasmUsefulDeclarationProcessor( irClass.getWasmArrayAnnotation()?.type ?.enqueueType(irClass, "array type for wasm array annotated") + if (irClass.symbol in context.wasmSymbols.primitiveTypeToCreateTypedArray.keys) { + irClass.declarations.forEach { + (it as? IrField)?.enqueue(irClass, "preserve all fields for primitive arrays") + } + } + if (context.inlineClassesUtils.isClassInlineLike(irClass)) { irClass.declarations .firstIsInstanceOrNull() @@ -166,7 +172,11 @@ internal class WasmUsefulDeclarationProcessor( irFunction.getEffectiveValueParameters().forEach { it.enqueueValueParameterType(irFunction) } irFunction.returnType.enqueueType(irFunction, "function return type") - kotlinClosureToJsClosureConvertFunToKotlinClosureCallFun[irFunction]?.enqueue(irFunction, "kotlin closure to JS closure conversion", false) + kotlinClosureToJsClosureConvertFunToKotlinClosureCallFun[irFunction]?.enqueue( + irFunction, + "kotlin closure to JS closure conversion", + false + ) } override fun processSimpleFunction(irFunction: IrSimpleFunction) { diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmArrayConstructorLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmArrayConstructorLowering.kt new file mode 100644 index 00000000000..2df7f2cad0d --- /dev/null +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/WasmArrayConstructorLowering.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2019 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.backend.wasm.lower + +import org.jetbrains.kotlin.backend.common.BodyLoweringPass +import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.wasm.WasmBackendContext +import org.jetbrains.kotlin.ir.builders.irBlock +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.declarations.IrConstructor +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.declarations.IrSymbolOwner +import org.jetbrains.kotlin.ir.expressions.IrBody +import org.jetbrains.kotlin.ir.expressions.IrConstructorCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrFunctionReference +import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.util.constructedClass +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid + +class WasmArrayConstructorLowering(val context: WasmBackendContext) : BodyLoweringPass { + override fun lower(irBody: IrBody, container: IrDeclaration) { + irBody.transformChildrenVoid(ArrayConstructorTransformer(context, container as IrSymbolOwner)) + } +} + +private class ArrayConstructorTransformer( + val context: WasmBackendContext, + val container: IrSymbolOwner +) : IrElementTransformerVoidWithContext() { + // Array(size, init) -> create###Array(size, init) + companion object { + internal fun arrayInlineToSizeCreator(context: WasmBackendContext, irConstructor: IrConstructor): IrFunctionSymbol? = + when (irConstructor.valueParameters.size) { + 2 -> context.wasmSymbols.primitiveTypeToCreateTypedArray[irConstructor.constructedClass.symbol] + else -> null + } + } + + override fun visitConstructorCall(expression: IrConstructorCall): IrExpression { + val creator = arrayInlineToSizeCreator(context, expression.symbol.owner) + ?: return super.visitConstructorCall(expression) + + expression.transformChildrenVoid() + + val scope = (currentScope ?: createScope(container)).scope + return context.createIrBuilder(scope.scopeOwnerSymbol).irBlock(expression.startOffset, expression.endOffset) { + +irCall(creator, expression.type).also { call -> + repeat(expression.typeArgumentsCount) { call.putTypeArgument(it, expression.getTypeArgument(it)) } + repeat(expression.valueArgumentsCount) { call.putValueArgument(it, expression.getValueArgument(it)) } + } + } + } +} + +class WasmArrayConstructorReferenceLowering(val context: WasmBackendContext) : BodyLoweringPass { + override fun lower(irBody: IrBody, container: IrDeclaration) { + irBody.transformChildrenVoid(ArrayConstructorReferenceTransformer(context)) + } + + private class ArrayConstructorReferenceTransformer(val context: WasmBackendContext) : IrElementTransformerVoid() { + override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { + expression.transformChildrenVoid() + val target = expression.symbol.owner + + if (target !is IrConstructor) return expression + + val creator = ArrayConstructorTransformer.arrayInlineToSizeCreator(context, target) + ?: return super.visitFunctionReference(expression) + + return IrFunctionReferenceImpl( + startOffset = expression.startOffset, + endOffset = expression.endOffset, + type = expression.type, + symbol = creator, + typeArgumentsCount = expression.typeArgumentsCount, + valueArgumentsCount = expression.valueArgumentsCount, + reflectionTarget = creator, + origin = expression.origin + ).also { reference -> + repeat(expression.typeArgumentsCount) { reference.putTypeArgument(it, expression.getTypeArgument(it)) } + repeat(expression.valueArgumentsCount) { reference.putValueArgument(it, expression.getValueArgument(it)) } + } + } + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/arrays/nonLocalReturnArrayConstructor.kt b/compiler/testData/codegen/box/arrays/nonLocalReturnArrayConstructor.kt index 8c41a0bd6c2..dded8539cda 100644 --- a/compiler/testData/codegen/box/arrays/nonLocalReturnArrayConstructor.kt +++ b/compiler/testData/codegen/box/arrays/nonLocalReturnArrayConstructor.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: WASM -// WASM_MUTE_REASON: INLINE_ARRAY_CONSTRUCTOR typealias ArrayS = Array fun testArray() { diff --git a/libraries/stdlib/wasm/builtins/kotlin/Array.kt b/libraries/stdlib/wasm/builtins/kotlin/Array.kt index a3a9a29ff41..f2ce2a4f568 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/Array.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/Array.kt @@ -3,6 +3,14 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ +@file:Suppress( + "TYPE_PARAMETER_AS_REIFIED", + "WRONG_MODIFIER_TARGET", + "NON_ABSTRACT_FUNCTION_WITH_NO_BODY", + "PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED", + "UNUSED_PARAMETER" +) + package kotlin import kotlin.wasm.internal.* @@ -17,9 +25,8 @@ import kotlin.wasm.internal.* public class Array @PublishedApi internal constructor(size: Int) { internal val storage: WasmAnyArray = WasmAnyArray(size) - @Suppress("TYPE_PARAMETER_AS_REIFIED", "UNUSED_PARAMETER", "CAST_NEVER_SUCCEEDS") @WasmPrimitiveConstructor - internal constructor(storage: WasmAnyArray) : this(check(false) as Int) + internal constructor(storage: WasmAnyArray) /** * Creates a new array with the specified [size], where each element is calculated by calling the specified @@ -28,11 +35,7 @@ public class Array @PublishedApi internal constructor(size: Int) { * The function [init] is called for each array element sequentially starting from the first one. * It should return the value for an array element given its index. */ - // TODO: it have to be inline - @Suppress("TYPE_PARAMETER_AS_REIFIED") - public constructor(size: Int, init: (Int) -> T) : this(size) { - storage.fill(size, init) - } + public inline constructor(size: Int, init: (Int) -> T) /** * Returns the array element at the specified [index]. This method can be called using the @@ -83,3 +86,8 @@ internal fun arrayIterator(array: Array) = object : Iterator { override fun next() = if (index != array.size) array[index++] else throw NoSuchElementException("$index") } +internal inline fun createAnyArray(size: Int, invokable: (Int) -> T): Array { + val result = WasmAnyArray(size) + result.fill(size, invokable) + return Array(result) +} diff --git a/libraries/stdlib/wasm/builtins/kotlin/Arrays.kt b/libraries/stdlib/wasm/builtins/kotlin/Arrays.kt index c215943ca3d..bd85f4c0091 100644 --- a/libraries/stdlib/wasm/builtins/kotlin/Arrays.kt +++ b/libraries/stdlib/wasm/builtins/kotlin/Arrays.kt @@ -3,6 +3,14 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ +@file:Suppress( + "TYPE_PARAMETER_AS_REIFIED", + "WRONG_MODIFIER_TARGET", + "NON_ABSTRACT_FUNCTION_WITH_NO_BODY", + "PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED", + "UNUSED_PARAMETER" +) + package kotlin import kotlin.wasm.internal.* @@ -10,13 +18,10 @@ import kotlin.wasm.internal.* public class ByteArray(size: Int) { internal val storage = WasmByteArray(size) - @Suppress("TYPE_PARAMETER_AS_REIFIED", "UNUSED_PARAMETER", "CAST_NEVER_SUCCEEDS") @WasmPrimitiveConstructor - internal constructor(storage: WasmByteArray) : this(check(false) as Int) + internal constructor(storage: WasmByteArray) - public constructor(size: Int, init: (Int) -> Byte) : this(size) { - storage.fill(size, init) - } + public inline constructor(size: Int, init: (Int) -> Byte) public operator fun get(index: Int): Byte { rangeCheck(index, storage.len()) @@ -40,17 +45,19 @@ internal fun byteArrayIterator(array: ByteArray) = object : ByteIterator() { override fun nextByte() = if (index != array.size) array[index++] else throw NoSuchElementException("$index") } +internal inline fun createByteArray(size: Int, invokable: (Int) -> Byte): ByteArray { + val result = WasmByteArray(size) + result.fill(size, invokable) + return ByteArray(result) +} public class CharArray(size: Int) { internal val storage = WasmCharArray(size) - @Suppress("TYPE_PARAMETER_AS_REIFIED", "UNUSED_PARAMETER", "CAST_NEVER_SUCCEEDS") @WasmPrimitiveConstructor - internal constructor(storage: WasmCharArray) : this(check(false) as Int) + internal constructor(storage: WasmCharArray) - public constructor(size: Int, init: (Int) -> Char) : this(size) { - storage.fill(size) { init(it) } - } + public inline constructor(size: Int, init: (Int) -> Char) public operator fun get(index: Int): Char { rangeCheck(index, storage.len()) @@ -75,17 +82,19 @@ internal fun charArrayIterator(array: CharArray) = object : CharIterator() { override fun nextChar() = if (index != array.size) array[index++] else throw NoSuchElementException("$index") } +internal inline fun createCharArray(size: Int, invokable: (Int) -> Char): CharArray { + val result = WasmCharArray(size) + result.fill(size, invokable) + return CharArray(result) +} public class ShortArray(size: Int) { internal val storage = WasmShortArray(size) - @Suppress("TYPE_PARAMETER_AS_REIFIED", "UNUSED_PARAMETER", "CAST_NEVER_SUCCEEDS") @WasmPrimitiveConstructor - internal constructor(storage: WasmShortArray) : this(check(false) as Int) + internal constructor(storage: WasmShortArray) - public constructor(size: Int, init: (Int) -> Short) : this(size) { - storage.fill(size, init) - } + public inline constructor(size: Int, init: (Int) -> Short) public operator fun get(index: Int): Short { rangeCheck(index, storage.len()) @@ -110,17 +119,19 @@ internal fun shortArrayIterator(array: ShortArray) = object : ShortIterator() { override fun nextShort() = if (index != array.size) array[index++] else throw NoSuchElementException("$index") } +internal inline fun createShortArray(size: Int, invokable: (Int) -> Short): ShortArray { + val result = WasmShortArray(size) + result.fill(size, invokable) + return ShortArray(result) +} public class IntArray(size: Int) { internal val storage = WasmIntArray(size) - @Suppress("TYPE_PARAMETER_AS_REIFIED", "UNUSED_PARAMETER", "CAST_NEVER_SUCCEEDS") @WasmPrimitiveConstructor - internal constructor(storage: WasmIntArray) : this(check(false) as Int) + internal constructor(storage: WasmIntArray) - public constructor(size: Int, init: (Int) -> Int) : this(size) { - storage.fill(size, init) - } + public inline constructor(size: Int, init: (Int) -> Int) public operator fun get(index: Int): Int { rangeCheck(index, storage.len()) @@ -145,17 +156,19 @@ internal fun intArrayIterator(array: IntArray) = object : IntIterator() { override fun nextInt() = if (index != array.size) array[index++] else throw NoSuchElementException("$index") } +internal inline fun createIntArray(size: Int, invokable: (Int) -> Int): IntArray { + val result = WasmIntArray(size) + result.fill(size, invokable) + return IntArray(result) +} public class LongArray(size: Int) { internal val storage = WasmLongArray (size) - @Suppress("TYPE_PARAMETER_AS_REIFIED", "UNUSED_PARAMETER", "CAST_NEVER_SUCCEEDS") @WasmPrimitiveConstructor - internal constructor(storage: WasmLongArray) : this(check(false) as Int) + internal constructor(storage: WasmLongArray) - public constructor(size: Int, init: (Int) -> Long) : this(size) { - storage.fill(size, init) - } + public inline constructor(size: Int, init: (Int) -> Long) public operator fun get(index: Int): Long { rangeCheck(index, storage.len()) @@ -179,17 +192,19 @@ internal fun longArrayIterator(array: LongArray) = object : LongIterator() { override fun nextLong() = if (index != array.size) array[index++] else throw NoSuchElementException("$index") } +internal inline fun createLongArray(size: Int, invokable: (Int) -> Long): LongArray { + val result = WasmLongArray(size) + result.fill(size, invokable) + return LongArray(result) +} public class FloatArray(size: Int) { internal val storage = WasmFloatArray(size) - @Suppress("TYPE_PARAMETER_AS_REIFIED", "UNUSED_PARAMETER", "CAST_NEVER_SUCCEEDS") @WasmPrimitiveConstructor - internal constructor(storage: WasmFloatArray) : this(check(false) as Int) + internal constructor(storage: WasmFloatArray) - public constructor(size: Int, init: (Int) -> Float) : this(size) { - storage.fill(size, init) - } + public inline constructor(size: Int, init: (Int) -> Float) public operator fun get(index: Int): Float { rangeCheck(index, storage.len()) @@ -213,17 +228,19 @@ internal fun floatArrayIterator(array: FloatArray) = object : FloatIterator() { override fun nextFloat() = if (index != array.size) array[index++] else throw NoSuchElementException("$index") } +internal inline fun createFloatArray(size: Int, invokable: (Int) -> Float): FloatArray { + val result = WasmFloatArray(size) + result.fill(size, invokable) + return FloatArray(result) +} public class DoubleArray(size: Int) { internal val storage = WasmDoubleArray(size) - @Suppress("TYPE_PARAMETER_AS_REIFIED", "UNUSED_PARAMETER", "CAST_NEVER_SUCCEEDS") @WasmPrimitiveConstructor - internal constructor(storage: WasmDoubleArray) : this(check(false) as Int) + internal constructor(storage: WasmDoubleArray) - public constructor(size: Int, init: (Int) -> Double) : this(size) { - storage.fill(size, init) - } + public inline constructor(size: Int, init: (Int) -> Double) public operator fun get(index: Int): Double { rangeCheck(index, storage.len()) @@ -247,17 +264,19 @@ internal fun doubleArrayIterator(array: DoubleArray) = object : DoubleIterator() override fun nextDouble() = if (index != array.size) array[index++] else throw NoSuchElementException("$index") } +internal inline fun createDoubleArray(size: Int, invokable: (Int) -> Double): DoubleArray { + val result = WasmDoubleArray(size) + result.fill(size, invokable) + return DoubleArray(result) +} public class BooleanArray(size: Int) { internal val storage = WasmByteArray(size) - @Suppress("TYPE_PARAMETER_AS_REIFIED", "CAST_NEVER_SUCCEEDS", "UNUSED_PARAMETER") @WasmPrimitiveConstructor - internal constructor(storage: WasmByteArray) : this(check(false) as Int) + internal constructor(storage: WasmByteArray) - public constructor(size: Int, init: (Int) -> Boolean) : this(size) { - storage.fill(size) { init(it).toInt().reinterpretAsByte() } - } + public inline constructor(size: Int, init: (Int) -> Boolean) public operator fun get(index: Int): Boolean { rangeCheck(index, storage.len()) @@ -280,3 +299,15 @@ internal fun booleanArrayIterator(array: BooleanArray) = object : BooleanIterato override fun hasNext() = index != array.size override fun nextBoolean() = if (index != array.size) array[index++] else throw NoSuchElementException("$index") } + +@WasmNoOpCast +private fun Boolean.reinterpretAsByte(): Byte = + implementedAsIntrinsic + +internal inline fun createBooleanArray(size: Int, invokable: (Int) -> Boolean): BooleanArray { + val result = WasmByteArray(size) + result.fill(size) { + invokable(it).reinterpretAsByte() + } + return BooleanArray(result) +} \ No newline at end of file