[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:
Svyatoslav Kuzmich
2019-05-19 01:27:34 +03:00
parent f80b6ff947
commit 59617c97b4
5 changed files with 220 additions and 49 deletions
@@ -8,8 +8,8 @@ package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
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.name
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
@@ -55,68 +55,84 @@ private class VarargTransformer(
}
}
fun IrExpression.unboxInlineClassIfNeeded(): IrExpression {
val inlinedClass = type.getInlinedClass() ?: return this
val field = getInlineClassBackingField(inlinedClass)
return IrGetFieldImpl(startOffset, endOffset, field.symbol, field.type, this)
}
inner class InlineClassArrayInfo(
val elementType: IrType,
val arrayType: IrType
) {
val arrayInlineClass = arrayType.getInlinedClass()
val inlined = arrayInlineClass != null
fun IrExpression.boxInlineClassIfNeeded(inlineClass: IrClass?) =
if (inlineClass == null)
this
else
IrConstructorCallImpl.fromSymbolOwner(startOffset, endOffset, inlineClass.defaultType, inlineClass.constructors.single { it.isPrimary }.symbol).also {
it.putValueArgument(0, this)
val primitiveElementType = when {
inlined -> getInlineClassUnderlyingType(elementType.getInlinedClass()!!)
else -> elementType
}
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 {
expression.transformChildrenVoid(this)
val currentList = mutableListOf<IrExpression>()
val segments = mutableListOf<IrExpression>()
val elementType = expression.varargElementType
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
}
val arrayInfo = InlineClassArrayInfo(expression.varargElementType, expression.type)
for (e in expression.elements) {
when (e) {
is IrSpreadElement -> {
if (!currentList.isEmpty()) {
segments.add(currentList.toArrayLiteral(primitiveExpressionType, primitiveElementType))
segments.add(arrayInfo.toPrimitiveArrayLiteral(currentList))
currentList.clear()
}
segments.add(if (needUnboxing) e.expression.unboxInlineClassIfNeeded() else e.expression)
segments.add(arrayInfo.unboxElementIfNeeded(e.expression))
}
is IrExpression -> {
currentList.add(if (needUnboxing) e.unboxInlineClassIfNeeded() else e)
currentList.add(arrayInfo.unboxElementIfNeeded(e))
}
}
}
if (!currentList.isEmpty()) {
segments.add(currentList.toArrayLiteral(primitiveExpressionType, primitiveElementType))
segments.add(arrayInfo.toPrimitiveArrayLiteral(currentList))
currentList.clear()
}
// empty vararg => empty array literal
if (segments.isEmpty()) {
val res = emptyList<IrExpression>().toArrayLiteral(primitiveExpressionType, primitiveElementType)
return if (needUnboxing)
res.boxInlineClassIfNeeded(arrayInlineClass!!)
else
res
with (arrayInfo) {
return boxArrayIfNeeded(toPrimitiveArrayLiteral(emptyList<IrExpression>()))
}
}
// vararg with a single segment => no need to concatenate
@@ -126,15 +142,15 @@ private class VarargTransformer(
IrCallImpl(
expression.startOffset,
expression.endOffset,
expression.type,
arrayInfo.primitiveArrayType,
context.intrinsics.jsArraySlice
).apply {
putTypeArgument(0, expression.type)
putTypeArgument(0, arrayInfo.primitiveArrayType)
putValueArgument(0, segment)
}
} else segment
return if (needUnboxing) argument.boxInlineClassIfNeeded(arrayInlineClass!!) else argument
return arrayInfo.boxArrayIfNeeded(argument)
}
val arrayLiteral =
@@ -143,7 +159,7 @@ private class VarargTransformer(
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
} else {
context.intrinsics.arrayConcat
@@ -152,16 +168,13 @@ private class VarargTransformer(
val res = IrCallImpl(
expression.startOffset,
expression.endOffset,
expression.type,
arrayInfo.primitiveArrayType,
concatFun
).apply {
putValueArgument(0, arrayLiteral)
}
return if (needUnboxing)
res.boxInlineClassIfNeeded(arrayInlineClass!!)
else
res
return arrayInfo.boxArrayIfNeeded(res)
}
private fun transformFunctionAccessExpression(expression: IrFunctionAccessExpression): IrExpression {
@@ -171,14 +184,20 @@ private class VarargTransformer(
for (i in 0 until size) {
val argument = expression.getValueArgument(i)
val parameter = expression.symbol.owner.valueParameters[i]
if (argument == null && parameter.varargElementType != null) {
expression.putValueArgument(i, emptyList<IrExpression>().toArrayLiteral(parameter.type, parameter.varargElementType!!))
val varargElementType = 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
}
override fun visitCall(expression: IrCall) = transformFunctionAccessExpression(expression)
override fun visitConstructorCall(expression: IrConstructorCall) = transformFunctionAccessExpression(expression)
override fun visitFunctionAccess(expression: IrFunctionAccessExpression) =
transformFunctionAccessExpression(expression)
}
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS
@@ -6778,6 +6778,24 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
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")
@@ -6813,6 +6813,24 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
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")
@@ -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()
}