Support lateinit local vars in JVM BE

This commit is contained in:
Dmitry Petrov
2017-05-31 16:03:04 +03:00
parent 53961e8df0
commit c5b9d500bc
13 changed files with 363 additions and 17 deletions
@@ -1828,10 +1828,10 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
Type sharedVarType = typeMapper.getSharedVarType(descriptor);
Type varType = getVariableTypeNoSharing(variableDescriptor);
if (sharedVarType != null) {
return StackValue.shared(index, varType);
return StackValue.shared(index, varType, variableDescriptor);
}
else {
return adjustVariableValue(StackValue.local(index, varType), variableDescriptor);
return adjustVariableValue(StackValue.local(index, varType, variableDescriptor), variableDescriptor);
}
}
else {
@@ -3496,6 +3496,9 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
else if (delegateExpression != null) {
initializeLocalVariable(property, gen(delegateExpression));
}
else if (property.hasModifier(KtTokens.LATEINIT_KEYWORD)) {
initializeLocalVariable(property, null);
}
return StackValue.none();
}
@@ -3592,7 +3595,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
private void initializeLocalVariable(
@NotNull KtVariableDeclaration variableDeclaration,
@NotNull StackValue initializer
@Nullable StackValue initializer
) {
LocalVariableDescriptor variableDescriptor = (LocalVariableDescriptor) getVariableDescriptorNotNull(variableDeclaration);
@@ -3612,6 +3615,14 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
StackValue storeTo = sharedVarType == null ? StackValue.local(index, varType) : StackValue.shared(index, varType);
storeTo.putReceiver(v, false);
if (variableDescriptor.isLateInit()) {
assert initializer == null : "Initializer should be null for lateinit var " + variableDescriptor + ": " + initializer;
v.aconst(null);
storeTo.storeSelector(storeTo.type, v);
return;
}
assert initializer != null : "Initializer should be not null for " + variableDescriptor;
initializer.put(initializer.type, v);
markLineNumber(variableDeclaration, false);
@@ -146,6 +146,11 @@ public abstract class StackValue {
return new Local(index, type);
}
@NotNull
public static Local local(int index, @NotNull Type type, @NotNull VariableDescriptor descriptor) {
return new Local(index, type, descriptor.isLateInit(), descriptor.getName().asString());
}
@NotNull
public static Delegate delegate(
@NotNull Type type,
@@ -162,6 +167,11 @@ public abstract class StackValue {
return new Shared(index, type);
}
@NotNull
public static StackValue shared(int index, @NotNull Type type, @NotNull VariableDescriptor descriptor) {
return new Shared(index, type, descriptor.isLateInit(), descriptor.getName().asString());
}
@NotNull
public static StackValue onStack(@NotNull Type type) {
return type == Type.VOID_TYPE ? none() : new OnStack(type);
@@ -434,16 +444,18 @@ public abstract class StackValue {
@NotNull Type localType,
@NotNull Type classType,
@NotNull String fieldName,
@NotNull Field refWrapper
@NotNull Field refWrapper,
@NotNull VariableDescriptor variableDescriptor
) {
return new FieldForSharedVar(localType, classType, fieldName, refWrapper);
return new FieldForSharedVar(localType, classType, fieldName, refWrapper,
variableDescriptor.isLateInit(), variableDescriptor.getName().asString());
}
@NotNull
public static FieldForSharedVar fieldForSharedVar(@NotNull FieldForSharedVar field, @NotNull StackValue newReceiver) {
Field oldReceiver = (Field) field.receiver;
Field newSharedVarReceiver = field(oldReceiver, newReceiver);
return new FieldForSharedVar(field.type, field.owner, field.name, newSharedVarReceiver);
return new FieldForSharedVar(field.type, field.owner, field.name, newSharedVarReceiver, field.isLateinit, field.variableName);
}
public static StackValue coercion(@NotNull StackValue value, @NotNull Type castType) {
@@ -623,19 +635,35 @@ public abstract class StackValue {
public static class Local extends StackValue {
public final int index;
private final boolean isLateinit;
private final String name;
private Local(int index, Type type) {
private Local(int index, Type type, boolean isLateinit, String name) {
super(type, false);
this.index = index;
if (index < 0) {
throw new IllegalStateException("local variable index must be non-negative");
}
if (isLateinit && name == null) {
throw new IllegalArgumentException("Lateinit local variable should have name: #" + index + " " + type.getDescriptor());
}
this.index = index;
this.isLateinit = isLateinit;
this.name = name;
}
private Local(int index, Type type) {
this(index, type, false, null);
}
@Override
public void putSelector(@NotNull Type type, @NotNull InstructionAdapter v) {
v.load(index, this.type);
if (isLateinit) {
StackValue.genNonNullAssertForLateinit(v, name);
}
coerceTo(type, v);
// TODO unbox
}
@@ -1258,12 +1286,7 @@ public abstract class StackValue {
private void genNotNullAssertionForLateInitIfNeeded(@NotNull InstructionAdapter v) {
if (!descriptor.isLateInit()) return;
v.dup();
Label ok = new Label();
v.ifnonnull(ok);
v.visitLdcInsn(descriptor.getName().asString());
v.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V", false);
v.mark(ok);
StackValue.genNonNullAssertForLateinit(v, descriptor.getName().asString());
}
@Override
@@ -1327,6 +1350,15 @@ public abstract class StackValue {
}
}
private static void genNonNullAssertForLateinit(@NotNull InstructionAdapter v, @NotNull String name) {
v.dup();
Label ok = new Label();
v.ifnonnull(ok);
v.visitLdcInsn(name);
v.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "throwUninitializedPropertyAccessException", "(Ljava/lang/String;)V", false);
v.mark(ok);
}
private static class Expression extends StackValue {
private final KtExpression expression;
private final ExpressionCodegen generator;
@@ -1345,10 +1377,23 @@ public abstract class StackValue {
public static class Shared extends StackValueWithSimpleReceiver {
private final int index;
private final boolean isLateinit;
private final String name;
public Shared(int index, Type type) {
public Shared(int index, Type type, boolean isLateinit, String name) {
super(type, false, false, local(index, OBJECT_TYPE), false);
this.index = index;
if (isLateinit && name == null) {
throw new IllegalArgumentException("Lateinit shared local variable should have name: #" + index + " " + type.getDescriptor());
}
this.isLateinit = isLateinit;
this.name = name;
}
public Shared(int index, Type type) {
this(index, type, false, null);
}
public int getIndex() {
@@ -1360,6 +1405,9 @@ public abstract class StackValue {
Type refType = refType(this.type);
Type sharedType = sharedTypeForType(this.type);
v.visitFieldInsn(GETFIELD, sharedType.getInternalName(), "element", refType.getDescriptor());
if (isLateinit) {
StackValue.genNonNullAssertForLateinit(v, name);
}
coerceFrom(refType, v);
coerceTo(type, v);
}
@@ -1397,11 +1445,23 @@ public abstract class StackValue {
public static class FieldForSharedVar extends StackValueWithSimpleReceiver {
final Type owner;
final String name;
final boolean isLateinit;
final String variableName;
public FieldForSharedVar(Type type, Type owner, String name, StackValue.Field receiver) {
public FieldForSharedVar(
Type type, Type owner, String name, StackValue.Field receiver,
boolean isLateinit, String variableName
) {
super(type, false, false, receiver, receiver.canHaveSideEffects());
if (isLateinit && variableName == null) {
throw new IllegalArgumentException("variableName should be non-null for captured lateinit variable " + name);
}
this.owner = owner;
this.name = name;
this.isLateinit = isLateinit;
this.variableName = variableName;
}
@Override
@@ -1409,6 +1469,9 @@ public abstract class StackValue {
Type sharedType = sharedTypeForType(this.type);
Type refType = refType(this.type);
v.visitFieldInsn(GETFIELD, sharedType.getInternalName(), "element", refType.getDescriptor());
if (isLateinit) {
StackValue.genNonNullAssertForLateinit(v, variableName);
}
coerceFrom(refType, v);
coerceTo(type, v);
}
@@ -70,7 +70,7 @@ public interface LocalLookup {
EnclosedValueDescriptor enclosedValueDescriptor;
if (sharedVarType != null) {
StackValue.Field wrapperValue = StackValue.receiverWithRefWrapper(localType, classType, fieldName, thiz, vd);
innerValue = StackValue.fieldForSharedVar(localType, classType, fieldName, wrapperValue);
innerValue = StackValue.fieldForSharedVar(localType, classType, fieldName, wrapperValue, vd);
enclosedValueDescriptor = new EnclosedValueDescriptor(fieldName, d, innerValue, wrapperValue, type);
}
else {
@@ -0,0 +1,9 @@
fun runNoInline(f: () -> Unit) = f()
fun box(): String {
lateinit var ok: String
runNoInline {
ok = "OK"
}
return ok
}
@@ -0,0 +1,7 @@
fun box(): String {
lateinit var ok: String
run {
ok = "OK"
}
return ok
}
@@ -0,0 +1,21 @@
// WITH_REFLECT
import kotlin.UninitializedPropertyAccessException
fun runNoInline(f: () -> Unit) = f()
fun box(): String {
lateinit var str: String
var i: Int = 0
try {
runNoInline {
i = str.length
}
return "Should throw an exception"
}
catch (e: UninitializedPropertyAccessException) {
return "OK"
}
catch (e: Throwable) {
return "Unexpected exception: ${e::class.qualifiedName}"
}
}
@@ -0,0 +1,21 @@
// WITH_REFLECT
import kotlin.UninitializedPropertyAccessException
fun runNoInline(f: () -> Unit) = f()
fun box(): String {
lateinit var str: String
var str2: String = ""
try {
runNoInline {
str2 = str
}
return "Should throw an exception"
}
catch (e: UninitializedPropertyAccessException) {
return "OK"
}
catch (e: Throwable) {
return "Unexpected exception: ${e::class.qualifiedName}"
}
}
@@ -0,0 +1,17 @@
// WITH_REFLECT
import kotlin.UninitializedPropertyAccessException
fun box(): String {
lateinit var str: String
var i: Int = 0
try {
i = str.length
return "Should throw an exception"
}
catch (e: UninitializedPropertyAccessException) {
return "OK"
}
catch (e: Throwable) {
return "Unexpected exception: ${e::class.qualifiedName}"
}
}
@@ -0,0 +1,17 @@
// WITH_REFLECT
import kotlin.UninitializedPropertyAccessException
fun box(): String {
lateinit var str: String
var str2: String = ""
try {
str2 = str
return "Should throw an exception"
}
catch (e: UninitializedPropertyAccessException) {
return "OK"
}
catch (e: Throwable) {
return "Unexpected exception: ${e::class.qualifiedName}"
}
}
@@ -13374,6 +13374,51 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/visibility.kt");
doTest(fileName);
}
@TestMetadata("compiler/testData/codegen/box/properties/lateinit/local")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Local extends AbstractIrBlackBoxCodegenTest {
public void testAllFilesPresentInLocal() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/properties/lateinit/local"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("capturedLocalLateinit.kt")
public void testCapturedLocalLateinit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/capturedLocalLateinit.kt");
doTest(fileName);
}
@TestMetadata("localLateinit.kt")
public void testLocalLateinit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/localLateinit.kt");
doTest(fileName);
}
@TestMetadata("uninitializedCapturedMemberAccess.kt")
public void testUninitializedCapturedMemberAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedCapturedMemberAccess.kt");
doTest(fileName);
}
@TestMetadata("uninitializedCapturedRead.kt")
public void testUninitializedCapturedRead() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedCapturedRead.kt");
doTest(fileName);
}
@TestMetadata("uninitializedMemberAccess.kt")
public void testUninitializedMemberAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedMemberAccess.kt");
doTest(fileName);
}
@TestMetadata("uninitializedRead.kt")
public void testUninitializedRead() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedRead.kt");
doTest(fileName);
}
}
}
}
@@ -13374,6 +13374,51 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/visibility.kt");
doTest(fileName);
}
@TestMetadata("compiler/testData/codegen/box/properties/lateinit/local")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Local extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInLocal() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/properties/lateinit/local"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("capturedLocalLateinit.kt")
public void testCapturedLocalLateinit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/capturedLocalLateinit.kt");
doTest(fileName);
}
@TestMetadata("localLateinit.kt")
public void testLocalLateinit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/localLateinit.kt");
doTest(fileName);
}
@TestMetadata("uninitializedCapturedMemberAccess.kt")
public void testUninitializedCapturedMemberAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedCapturedMemberAccess.kt");
doTest(fileName);
}
@TestMetadata("uninitializedCapturedRead.kt")
public void testUninitializedCapturedRead() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedCapturedRead.kt");
doTest(fileName);
}
@TestMetadata("uninitializedMemberAccess.kt")
public void testUninitializedMemberAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedMemberAccess.kt");
doTest(fileName);
}
@TestMetadata("uninitializedRead.kt")
public void testUninitializedRead() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedRead.kt");
doTest(fileName);
}
}
}
}
@@ -13374,6 +13374,51 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/visibility.kt");
doTest(fileName);
}
@TestMetadata("compiler/testData/codegen/box/properties/lateinit/local")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Local extends AbstractLightAnalysisModeTest {
public void testAllFilesPresentInLocal() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/properties/lateinit/local"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("capturedLocalLateinit.kt")
public void testCapturedLocalLateinit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/capturedLocalLateinit.kt");
doTest(fileName);
}
@TestMetadata("localLateinit.kt")
public void testLocalLateinit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/localLateinit.kt");
doTest(fileName);
}
@TestMetadata("uninitializedCapturedMemberAccess.kt")
public void testUninitializedCapturedMemberAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedCapturedMemberAccess.kt");
doTest(fileName);
}
@TestMetadata("uninitializedCapturedRead.kt")
public void testUninitializedCapturedRead() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedCapturedRead.kt");
doTest(fileName);
}
@TestMetadata("uninitializedMemberAccess.kt")
public void testUninitializedMemberAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedMemberAccess.kt");
doTest(fileName);
}
@TestMetadata("uninitializedRead.kt")
public void testUninitializedRead() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedRead.kt");
doTest(fileName);
}
}
}
}
@@ -15000,6 +15000,51 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
}
@TestMetadata("compiler/testData/codegen/box/properties/lateinit/local")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Local extends AbstractJsCodegenBoxTest {
public void testAllFilesPresentInLocal() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/properties/lateinit/local"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("capturedLocalLateinit.kt")
public void testCapturedLocalLateinit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/capturedLocalLateinit.kt");
doTest(fileName);
}
@TestMetadata("localLateinit.kt")
public void testLocalLateinit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/localLateinit.kt");
doTest(fileName);
}
@TestMetadata("uninitializedCapturedMemberAccess.kt")
public void testUninitializedCapturedMemberAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedCapturedMemberAccess.kt");
doTest(fileName);
}
@TestMetadata("uninitializedCapturedRead.kt")
public void testUninitializedCapturedRead() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedCapturedRead.kt");
doTest(fileName);
}
@TestMetadata("uninitializedMemberAccess.kt")
public void testUninitializedMemberAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedMemberAccess.kt");
doTest(fileName);
}
@TestMetadata("uninitializedRead.kt")
public void testUninitializedRead() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/local/uninitializedRead.kt");
doTest(fileName);
}
}
}
}