[JS IR BE] Fix VarargLowering
- Enable empty vararg transformation for all IrFunctionAccessExpression - Fix empty vararg boxing - Refactor inline class boxing logic - Fix types of generated expressions
This commit is contained in:
+67
-48
@@ -8,8 +8,8 @@ package org.jetbrains.kotlin.ir.backend.js.lower
|
|||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.name
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
|
||||||
@@ -55,68 +55,84 @@ private class VarargTransformer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun IrExpression.unboxInlineClassIfNeeded(): IrExpression {
|
inner class InlineClassArrayInfo(
|
||||||
val inlinedClass = type.getInlinedClass() ?: return this
|
val elementType: IrType,
|
||||||
val field = getInlineClassBackingField(inlinedClass)
|
val arrayType: IrType
|
||||||
return IrGetFieldImpl(startOffset, endOffset, field.symbol, field.type, this)
|
) {
|
||||||
}
|
val arrayInlineClass = arrayType.getInlinedClass()
|
||||||
|
val inlined = arrayInlineClass != null
|
||||||
|
|
||||||
fun IrExpression.boxInlineClassIfNeeded(inlineClass: IrClass?) =
|
val primitiveElementType = when {
|
||||||
if (inlineClass == null)
|
inlined -> getInlineClassUnderlyingType(elementType.getInlinedClass()!!)
|
||||||
this
|
else -> elementType
|
||||||
else
|
}
|
||||||
IrConstructorCallImpl.fromSymbolOwner(startOffset, endOffset, inlineClass.defaultType, inlineClass.constructors.single { it.isPrimary }.symbol).also {
|
|
||||||
it.putValueArgument(0, this)
|
val primitiveArrayType = when {
|
||||||
|
inlined -> getInlineClassUnderlyingType(arrayInlineClass!!)
|
||||||
|
else -> arrayType
|
||||||
|
}
|
||||||
|
|
||||||
|
fun boxArrayIfNeeded(array: IrExpression) =
|
||||||
|
if (arrayInlineClass == null)
|
||||||
|
array
|
||||||
|
else with(array) {
|
||||||
|
IrConstructorCallImpl.fromSymbolOwner(
|
||||||
|
startOffset,
|
||||||
|
endOffset,
|
||||||
|
arrayInlineClass.defaultType,
|
||||||
|
arrayInlineClass.constructors.single { it.isPrimary }.symbol
|
||||||
|
).also {
|
||||||
|
it.putValueArgument(0, array)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun unboxElementIfNeeded(element: IrExpression): IrExpression {
|
||||||
|
if (arrayInlineClass == null)
|
||||||
|
return element
|
||||||
|
else with(element) {
|
||||||
|
val inlinedClass = type.getInlinedClass() ?: return element
|
||||||
|
val field = getInlineClassBackingField(inlinedClass)
|
||||||
|
return IrGetFieldImpl(startOffset, endOffset, field.symbol, field.type, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toPrimitiveArrayLiteral(elements: List<IrExpression>) =
|
||||||
|
elements.toArrayLiteral(primitiveArrayType, primitiveElementType)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitVararg(expression: IrVararg): IrExpression {
|
override fun visitVararg(expression: IrVararg): IrExpression {
|
||||||
expression.transformChildrenVoid(this)
|
expression.transformChildrenVoid(this)
|
||||||
|
|
||||||
val currentList = mutableListOf<IrExpression>()
|
val currentList = mutableListOf<IrExpression>()
|
||||||
val segments = mutableListOf<IrExpression>()
|
val segments = mutableListOf<IrExpression>()
|
||||||
|
|
||||||
val elementType = expression.varargElementType
|
val arrayInfo = InlineClassArrayInfo(expression.varargElementType, expression.type)
|
||||||
val primitiveElementType: IrType
|
|
||||||
val primitiveExpressionType: IrType
|
|
||||||
val needUnboxing: Boolean
|
|
||||||
val arrayInlineClass = expression.type.getInlinedClass()
|
|
||||||
if (arrayInlineClass != null) {
|
|
||||||
primitiveElementType = getInlineClassUnderlyingType(elementType.getInlinedClass()!!)
|
|
||||||
primitiveExpressionType = getInlineClassUnderlyingType(arrayInlineClass)
|
|
||||||
needUnboxing = true
|
|
||||||
} else {
|
|
||||||
primitiveElementType = elementType
|
|
||||||
primitiveExpressionType = expression.type
|
|
||||||
needUnboxing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
for (e in expression.elements) {
|
for (e in expression.elements) {
|
||||||
when (e) {
|
when (e) {
|
||||||
is IrSpreadElement -> {
|
is IrSpreadElement -> {
|
||||||
if (!currentList.isEmpty()) {
|
if (!currentList.isEmpty()) {
|
||||||
segments.add(currentList.toArrayLiteral(primitiveExpressionType, primitiveElementType))
|
segments.add(arrayInfo.toPrimitiveArrayLiteral(currentList))
|
||||||
currentList.clear()
|
currentList.clear()
|
||||||
}
|
}
|
||||||
segments.add(if (needUnboxing) e.expression.unboxInlineClassIfNeeded() else e.expression)
|
segments.add(arrayInfo.unboxElementIfNeeded(e.expression))
|
||||||
}
|
}
|
||||||
|
|
||||||
is IrExpression -> {
|
is IrExpression -> {
|
||||||
currentList.add(if (needUnboxing) e.unboxInlineClassIfNeeded() else e)
|
currentList.add(arrayInfo.unboxElementIfNeeded(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!currentList.isEmpty()) {
|
if (!currentList.isEmpty()) {
|
||||||
segments.add(currentList.toArrayLiteral(primitiveExpressionType, primitiveElementType))
|
segments.add(arrayInfo.toPrimitiveArrayLiteral(currentList))
|
||||||
currentList.clear()
|
currentList.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty vararg => empty array literal
|
// empty vararg => empty array literal
|
||||||
if (segments.isEmpty()) {
|
if (segments.isEmpty()) {
|
||||||
val res = emptyList<IrExpression>().toArrayLiteral(primitiveExpressionType, primitiveElementType)
|
with (arrayInfo) {
|
||||||
return if (needUnboxing)
|
return boxArrayIfNeeded(toPrimitiveArrayLiteral(emptyList<IrExpression>()))
|
||||||
res.boxInlineClassIfNeeded(arrayInlineClass!!)
|
}
|
||||||
else
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// vararg with a single segment => no need to concatenate
|
// vararg with a single segment => no need to concatenate
|
||||||
@@ -126,15 +142,15 @@ private class VarargTransformer(
|
|||||||
IrCallImpl(
|
IrCallImpl(
|
||||||
expression.startOffset,
|
expression.startOffset,
|
||||||
expression.endOffset,
|
expression.endOffset,
|
||||||
expression.type,
|
arrayInfo.primitiveArrayType,
|
||||||
context.intrinsics.jsArraySlice
|
context.intrinsics.jsArraySlice
|
||||||
).apply {
|
).apply {
|
||||||
putTypeArgument(0, expression.type)
|
putTypeArgument(0, arrayInfo.primitiveArrayType)
|
||||||
putValueArgument(0, segment)
|
putValueArgument(0, segment)
|
||||||
}
|
}
|
||||||
} else segment
|
} else segment
|
||||||
|
|
||||||
return if (needUnboxing) argument.boxInlineClassIfNeeded(arrayInlineClass!!) else argument
|
return arrayInfo.boxArrayIfNeeded(argument)
|
||||||
}
|
}
|
||||||
|
|
||||||
val arrayLiteral =
|
val arrayLiteral =
|
||||||
@@ -143,7 +159,7 @@ private class VarargTransformer(
|
|||||||
context.irBuiltIns.anyType
|
context.irBuiltIns.anyType
|
||||||
)
|
)
|
||||||
|
|
||||||
val concatFun = if (expression.type.classifierOrNull in context.intrinsics.primitiveArrays.keys) {
|
val concatFun = if (arrayInfo.primitiveArrayType.classifierOrNull in context.intrinsics.primitiveArrays.keys) {
|
||||||
context.intrinsics.primitiveArrayConcat
|
context.intrinsics.primitiveArrayConcat
|
||||||
} else {
|
} else {
|
||||||
context.intrinsics.arrayConcat
|
context.intrinsics.arrayConcat
|
||||||
@@ -152,16 +168,13 @@ private class VarargTransformer(
|
|||||||
val res = IrCallImpl(
|
val res = IrCallImpl(
|
||||||
expression.startOffset,
|
expression.startOffset,
|
||||||
expression.endOffset,
|
expression.endOffset,
|
||||||
expression.type,
|
arrayInfo.primitiveArrayType,
|
||||||
concatFun
|
concatFun
|
||||||
).apply {
|
).apply {
|
||||||
putValueArgument(0, arrayLiteral)
|
putValueArgument(0, arrayLiteral)
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (needUnboxing)
|
return arrayInfo.boxArrayIfNeeded(res)
|
||||||
res.boxInlineClassIfNeeded(arrayInlineClass!!)
|
|
||||||
else
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transformFunctionAccessExpression(expression: IrFunctionAccessExpression): IrExpression {
|
private fun transformFunctionAccessExpression(expression: IrFunctionAccessExpression): IrExpression {
|
||||||
@@ -171,14 +184,20 @@ private class VarargTransformer(
|
|||||||
for (i in 0 until size) {
|
for (i in 0 until size) {
|
||||||
val argument = expression.getValueArgument(i)
|
val argument = expression.getValueArgument(i)
|
||||||
val parameter = expression.symbol.owner.valueParameters[i]
|
val parameter = expression.symbol.owner.valueParameters[i]
|
||||||
if (argument == null && parameter.varargElementType != null) {
|
val varargElementType = parameter.varargElementType
|
||||||
expression.putValueArgument(i, emptyList<IrExpression>().toArrayLiteral(parameter.type, parameter.varargElementType!!))
|
if (argument == null && varargElementType != null) {
|
||||||
|
val arrayInfo = InlineClassArrayInfo(varargElementType, parameter.type)
|
||||||
|
val emptyArray = with (arrayInfo) {
|
||||||
|
boxArrayIfNeeded(toPrimitiveArrayLiteral(emptyList<IrExpression>()))
|
||||||
|
}
|
||||||
|
|
||||||
|
expression.putValueArgument(i, emptyArray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return expression
|
return expression
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitCall(expression: IrCall) = transformFunctionAccessExpression(expression)
|
override fun visitFunctionAccess(expression: IrFunctionAccessExpression) =
|
||||||
override fun visitConstructorCall(expression: IrConstructorCall) = transformFunctionAccessExpression(expression)
|
transformFunctionAccessExpression(expression)
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// IGNORE_BACKEND: JS
|
// IGNORE_BACKEND: JS
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+18
@@ -6778,6 +6778,24 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
|||||||
runTest("js/js.translator/testData/box/regression/stdlibTestSnippets/throwable.kt");
|
runTest("js/js.translator/testData/box/regression/stdlibTestSnippets/throwable.kt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("js/js.translator/testData/box/regression/typeChecks")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class TypeChecks extends AbstractIrBoxJsTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInTypeChecks() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/regression/typeChecks"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("emptyVarargInConstructorCall.kt")
|
||||||
|
public void testEmptyVarargInConstructorCall() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/regression/typeChecks/emptyVarargInConstructorCall.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("js/js.translator/testData/box/reified")
|
@TestMetadata("js/js.translator/testData/box/reified")
|
||||||
|
|||||||
+18
@@ -6813,6 +6813,24 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
|||||||
runTest("js/js.translator/testData/box/regression/stdlibTestSnippets/throwable.kt");
|
runTest("js/js.translator/testData/box/regression/stdlibTestSnippets/throwable.kt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("js/js.translator/testData/box/regression/typeChecks")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class TypeChecks extends AbstractBoxJsTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInTypeChecks() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/regression/typeChecks"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("emptyVarargInConstructorCall.kt")
|
||||||
|
public void testEmptyVarargInConstructorCall() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/regression/typeChecks/emptyVarargInConstructorCall.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("js/js.translator/testData/box/reified")
|
@TestMetadata("js/js.translator/testData/box/reified")
|
||||||
|
|||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
// KJS_WITH_FULL_RUNTIME
|
||||||
|
// EXPECTED_REACHABLE_NODES: 1857
|
||||||
|
|
||||||
|
package foo
|
||||||
|
|
||||||
|
open class AByte(vararg val x: Byte)
|
||||||
|
class BByte : AByte()
|
||||||
|
|
||||||
|
open class AShort(vararg val x: Short)
|
||||||
|
class BShort : AShort()
|
||||||
|
|
||||||
|
open class AInt(vararg val x: Int)
|
||||||
|
class BInt : AInt()
|
||||||
|
|
||||||
|
open class AChar(vararg val x: Char)
|
||||||
|
class BChar : AChar()
|
||||||
|
|
||||||
|
open class ALong(vararg val x: Long)
|
||||||
|
class BLong : ALong()
|
||||||
|
|
||||||
|
open class AFloat(vararg val x: Float)
|
||||||
|
class BFloat : AFloat()
|
||||||
|
|
||||||
|
open class ADouble(vararg val x: Double)
|
||||||
|
class BDouble : ADouble()
|
||||||
|
|
||||||
|
open class AUByte(vararg val x: UByte)
|
||||||
|
class BUByte : AUByte()
|
||||||
|
|
||||||
|
open class AUShort(vararg val x: UShort)
|
||||||
|
class BUShort : AUShort()
|
||||||
|
|
||||||
|
open class AUInt(vararg val x: UInt)
|
||||||
|
class BUInt : AUInt()
|
||||||
|
|
||||||
|
open class AULong(vararg val x: ULong)
|
||||||
|
class BULong : AULong()
|
||||||
|
|
||||||
|
open class ABoolean(vararg val x: Boolean)
|
||||||
|
class BBoolean : ABoolean()
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val a_x_Byte = AByte().x as Any as ByteArray
|
||||||
|
if (a_x_Byte.size != 0) return "Fail a ByteArray"
|
||||||
|
|
||||||
|
val b_x_Byte = BByte().x as Any as ByteArray
|
||||||
|
if (b_x_Byte.size != 0) return "Fail b ByteArray"
|
||||||
|
|
||||||
|
val a_x_Short = AShort().x as Any as ShortArray
|
||||||
|
if (a_x_Short.size != 0) return "Fail a ShortArray"
|
||||||
|
|
||||||
|
val b_x_Short = BShort().x as Any as ShortArray
|
||||||
|
if (b_x_Short.size != 0) return "Fail b ShortArray"
|
||||||
|
|
||||||
|
val a_x_Int = AInt().x as Any as IntArray
|
||||||
|
if (a_x_Int.size != 0) return "Fail a IntArray"
|
||||||
|
|
||||||
|
val b_x_Int = BInt().x as Any as IntArray
|
||||||
|
if (b_x_Int.size != 0) return "Fail b IntArray"
|
||||||
|
|
||||||
|
val a_x_Char = AChar().x as Any as CharArray
|
||||||
|
if (a_x_Char.size != 0) return "Fail a CharArray"
|
||||||
|
|
||||||
|
val b_x_Char = BChar().x as Any as CharArray
|
||||||
|
if (b_x_Char.size != 0) return "Fail b CharArray"
|
||||||
|
|
||||||
|
val a_x_Long = ALong().x as Any as LongArray
|
||||||
|
if (a_x_Long.size != 0) return "Fail a LongArray"
|
||||||
|
|
||||||
|
val b_x_Long = BLong().x as Any as LongArray
|
||||||
|
if (b_x_Long.size != 0) return "Fail b LongArray"
|
||||||
|
|
||||||
|
val a_x_Float = AFloat().x as Any as FloatArray
|
||||||
|
if (a_x_Float.size != 0) return "Fail a FloatArray"
|
||||||
|
|
||||||
|
val b_x_Float = BFloat().x as Any as FloatArray
|
||||||
|
if (b_x_Float.size != 0) return "Fail b FloatArray"
|
||||||
|
|
||||||
|
val a_x_Double = ADouble().x as Any as DoubleArray
|
||||||
|
if (a_x_Double.size != 0) return "Fail a DoubleArray"
|
||||||
|
|
||||||
|
val b_x_Double = BDouble().x as Any as DoubleArray
|
||||||
|
if (b_x_Double.size != 0) return "Fail b DoubleArray"
|
||||||
|
|
||||||
|
val a_x_UByte = AUByte().x as Any as UByteArray
|
||||||
|
if (a_x_UByte.size != 0) return "Fail a UByteArray"
|
||||||
|
|
||||||
|
val b_x_UByte = BUByte().x as Any as UByteArray
|
||||||
|
if (b_x_UByte.size != 0) return "Fail b UByteArray"
|
||||||
|
|
||||||
|
val a_x_UShort = AUShort().x as Any as UShortArray
|
||||||
|
if (a_x_UShort.size != 0) return "Fail a UShortArray"
|
||||||
|
|
||||||
|
val b_x_UShort = BUShort().x as Any as UShortArray
|
||||||
|
if (b_x_UShort.size != 0) return "Fail b UShortArray"
|
||||||
|
|
||||||
|
val a_x_UInt = AUInt().x as Any as UIntArray
|
||||||
|
if (a_x_UInt.size != 0) return "Fail a UIntArray"
|
||||||
|
|
||||||
|
val b_x_UInt = BUInt().x as Any as UIntArray
|
||||||
|
if (b_x_UInt.size != 0) return "Fail b UIntArray"
|
||||||
|
|
||||||
|
val a_x_ULong = AULong().x as Any as ULongArray
|
||||||
|
if (a_x_ULong.size != 0) return "Fail a ULongArray"
|
||||||
|
|
||||||
|
val b_x_ULong = BULong().x as Any as ULongArray
|
||||||
|
if (b_x_ULong.size != 0) return "Fail b ULongArray"
|
||||||
|
|
||||||
|
val a_x_Boolean = ABoolean().x as Any as BooleanArray
|
||||||
|
if (a_x_Boolean.size != 0) return "Fail a BooleanArray"
|
||||||
|
|
||||||
|
val b_x_Boolean = BBoolean().x as Any as BooleanArray
|
||||||
|
if (b_x_Boolean.size != 0) return "Fail b BooleanArray"
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
// return Local().obj.result()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user