diff options
Diffstat (limited to 'compiler/optimizing/instruction_builder.cc')
-rw-r--r-- | compiler/optimizing/instruction_builder.cc | 204 |
1 files changed, 187 insertions, 17 deletions
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc index 5c3fcc59fc..e555d0d890 100644 --- a/compiler/optimizing/instruction_builder.cc +++ b/compiler/optimizing/instruction_builder.cc @@ -1147,6 +1147,151 @@ void HInstructionBuilder::BuildConstructorFenceForAllocation(HInstruction* alloc MethodCompilationStat::kConstructorFenceGeneratedNew); } +static bool IsInBootImage(ObjPtr<mirror::Class> cls, const CompilerOptions& compiler_options) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (compiler_options.IsBootImage()) { + std::string temp; + const char* descriptor = cls->GetDescriptor(&temp); + return compiler_options.IsImageClass(descriptor); + } else { + return Runtime::Current()->GetHeap()->FindSpaceFromObject(cls, false)->IsImageSpace(); + } +} + +static bool IsSubClass(ObjPtr<mirror::Class> to_test, ObjPtr<mirror::Class> super_class) + REQUIRES_SHARED(Locks::mutator_lock_) { + return to_test != nullptr && !to_test->IsInterface() && to_test->IsSubClass(super_class); +} + +static bool HasTrivialClinit(ObjPtr<mirror::Class> klass, PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_) { + // Check if the class has encoded fields that trigger bytecode execution. + // (Encoded fields are just a different representation of <clinit>.) + if (klass->NumStaticFields() != 0u) { + DCHECK(klass->GetClassDef() != nullptr); + EncodedStaticFieldValueIterator it(klass->GetDexFile(), *klass->GetClassDef()); + for (; it.HasNext(); it.Next()) { + switch (it.GetValueType()) { + case EncodedArrayValueIterator::ValueType::kBoolean: + case EncodedArrayValueIterator::ValueType::kByte: + case EncodedArrayValueIterator::ValueType::kShort: + case EncodedArrayValueIterator::ValueType::kChar: + case EncodedArrayValueIterator::ValueType::kInt: + case EncodedArrayValueIterator::ValueType::kLong: + case EncodedArrayValueIterator::ValueType::kFloat: + case EncodedArrayValueIterator::ValueType::kDouble: + case EncodedArrayValueIterator::ValueType::kNull: + case EncodedArrayValueIterator::ValueType::kString: + // Primitive, null or j.l.String initialization is permitted. + break; + case EncodedArrayValueIterator::ValueType::kType: + // Type initialization can load classes and execute bytecode through a class loader + // which can execute arbitrary bytecode. We do not optimize for known class loaders; + // kType is rarely used (if ever). + return false; + default: + // Other types in the encoded static field list are rejected by the DexFileVerifier. + LOG(FATAL) << "Unexpected type " << it.GetValueType(); + UNREACHABLE(); + } + } + } + // Check if the class has <clinit> that executes arbitrary code. + // Initialization of static fields of the class itself with constants is allowed. + ArtMethod* clinit = klass->FindClassInitializer(pointer_size); + if (clinit != nullptr) { + const DexFile& dex_file = *clinit->GetDexFile(); + CodeItemInstructionAccessor accessor(dex_file, clinit->GetCodeItem()); + for (DexInstructionPcPair it : accessor) { + switch (it->Opcode()) { + case Instruction::CONST_4: + case Instruction::CONST_16: + case Instruction::CONST: + case Instruction::CONST_HIGH16: + case Instruction::CONST_WIDE_16: + case Instruction::CONST_WIDE_32: + case Instruction::CONST_WIDE: + case Instruction::CONST_WIDE_HIGH16: + case Instruction::CONST_STRING: + case Instruction::CONST_STRING_JUMBO: + // Primitive, null or j.l.String initialization is permitted. + break; + case Instruction::RETURN_VOID: + case Instruction::RETURN_VOID_NO_BARRIER: + break; + case Instruction::SPUT: + case Instruction::SPUT_WIDE: + case Instruction::SPUT_OBJECT: + case Instruction::SPUT_BOOLEAN: + case Instruction::SPUT_BYTE: + case Instruction::SPUT_CHAR: + case Instruction::SPUT_SHORT: + // Only initialization of a static field of the same class is permitted. + if (dex_file.GetFieldId(it->VRegB_21c()).class_idx_ != klass->GetDexTypeIndex()) { + return false; + } + break; + case Instruction::NEW_ARRAY: + // Only primitive arrays are permitted. + if (Primitive::GetType(dex_file.GetTypeDescriptor(dex_file.GetTypeId( + dex::TypeIndex(it->VRegC_22c())))[1]) == Primitive::kPrimNot) { + return false; + } + break; + case Instruction::APUT: + case Instruction::APUT_WIDE: + case Instruction::APUT_BOOLEAN: + case Instruction::APUT_BYTE: + case Instruction::APUT_CHAR: + case Instruction::APUT_SHORT: + case Instruction::FILL_ARRAY_DATA: + case Instruction::NOP: + // Allow initialization of primitive arrays (only constants can be stored). + // Note: We expect NOPs used for fill-array-data-payload but accept all NOPs + // (even unreferenced switch payloads if they make it through the verifier). + break; + default: + return false; + } + } + } + return true; +} + +static bool HasTrivialInitialization(ObjPtr<mirror::Class> cls, + const CompilerOptions& compiler_options) + REQUIRES_SHARED(Locks::mutator_lock_) { + Runtime* runtime = Runtime::Current(); + PointerSize pointer_size = runtime->GetClassLinker()->GetImagePointerSize(); + + // Check the superclass chain. + for (ObjPtr<mirror::Class> klass = cls; klass != nullptr; klass = klass->GetSuperClass()) { + if (klass->IsInitialized() && IsInBootImage(klass, compiler_options)) { + break; // `klass` and its superclasses are already initialized in the boot image. + } + if (!HasTrivialClinit(klass, pointer_size)) { + return false; + } + } + + // Also check interfaces with default methods as they need to be initialized as well. + ObjPtr<mirror::IfTable> iftable = cls->GetIfTable(); + DCHECK(iftable != nullptr); + for (int32_t i = 0, count = iftable->Count(); i != count; ++i) { + ObjPtr<mirror::Class> iface = iftable->GetInterface(i); + if (!iface->HasDefaultMethods()) { + continue; // Initializing `cls` does not initialize this interface. + } + if (iface->IsInitialized() && IsInBootImage(iface, compiler_options)) { + continue; // This interface is already initialized in the boot image. + } + if (!HasTrivialClinit(iface, pointer_size)) { + return false; + } + } + return true; +} + bool HInstructionBuilder::IsInitialized(ScopedObjectAccess& soa, Handle<mirror::Class> cls) const { if (cls == nullptr) { return false; @@ -1162,35 +1307,60 @@ bool HInstructionBuilder::IsInitialized(ScopedObjectAccess& soa, Handle<mirror:: } // Assume loaded only if klass is in the boot image. App classes cannot be assumed // loaded because we don't even know what class loader will be used to load them. - const CompilerOptions& compiler_options = compiler_driver_->GetCompilerOptions(); - if (compiler_options.IsBootImage()) { - std::string temp; - const char* descriptor = cls->GetDescriptor(&temp); - if (compiler_options.IsImageClass(descriptor)) { - return true; - } - } else { - if (runtime->GetHeap()->FindSpaceFromObject(cls.Get(), false)->IsImageSpace()) { - return true; - } + if (IsInBootImage(cls.Get(), compiler_driver_->GetCompilerOptions())) { + return true; } } - // We can avoid the class initialization check for `cls` only in static methods in the + // We can avoid the class initialization check for `cls` in static methods in the // very same class. Instance methods of the same class can run on an escaped instance // of an erroneous class. Even a superclass may need to be checked as the subclass // can be completely initialized while the superclass is initializing and the subclass // remains initialized when the superclass initializer throws afterwards. b/62478025 // Note: The HClinitCheck+HInvokeStaticOrDirect merging can still apply. - if ((dex_compilation_unit_->GetAccessFlags() & kAccStatic) != 0u) { - ObjPtr<mirror::Class> outermost_cls = ResolveOutermostCompilingClass(soa); - if (outermost_cls == cls.Get()) { + ObjPtr<mirror::Class> outermost_cls = ResolveOutermostCompilingClass(soa); + bool is_static = (dex_compilation_unit_->GetAccessFlags() & kAccStatic) != 0u; + if (is_static && outermost_cls == cls.Get()) { + return true; + } + // Remember if the compiled class is a subclass of `cls`. By the time this is used + // below the `outermost_cls` may be invalidated by calling ResolveCompilingClass(). + bool is_subclass = IsSubClass(outermost_cls, cls.Get()); + if (dex_compilation_unit_ != outer_compilation_unit_) { + // Check also the innermost method. Though excessive copies of ClinitCheck can be + // eliminated by GVN, that happens only after the decision whether to inline the + // graph or not and that may depend on the presence of the ClinitCheck. + // TODO: We should walk over the entire inlined method chain, but we don't pass that + // information to the builder. + ObjPtr<mirror::Class> innermost_cls = ResolveCompilingClass(soa); + if (is_static && innermost_cls == cls.Get()) { return true; } + is_subclass = is_subclass || IsSubClass(innermost_cls, cls.Get()); } - // Note: We could walk over the inlined methods to avoid allocating excessive - // `HClinitCheck`s in inlined static methods but they shall be eliminated by GVN. + // Otherwise, we may be able to avoid the check if `cls` is a superclass of a method being + // compiled here (anywhere in the inlining chain) as the `cls` must have started initializing + // before calling any `cls` or subclass methods. Static methods require a clinit check and + // instance methods require an instance which cannot be created before doing a clinit check. + // When a subclass of `cls` starts initializing, it starts initializing its superclass + // chain up to `cls` without running any bytecode, i.e. without any opportunity for circular + // initialization weirdness. + // + // If the initialization of `cls` is trivial (`cls` and its superclasses and superinterfaces + // with default methods initialize only their own static fields using constant values), it must + // complete, either successfully or by throwing and marking `cls` erroneous, without allocating + // any instances of `cls` or subclasses (or any other class) and without calling any methods. + // If it completes by throwing, no instances of `cls` shall be created and no subclass method + // bytecode shall execute (see above), therefore the instruction we're building shall be + // unreachable. By reaching the instruction, we know that `cls` was initialized successfully. + // + // TODO: We should walk over the entire inlined methods chain, but we don't pass that + // information to the builder. (We could also check if we're guaranteed a non-null instance + // of `cls` at this location but that's outside the scope of the instruction builder.) + if (is_subclass && HasTrivialInitialization(cls.Get(), compiler_driver_->GetCompilerOptions())) { + return true; + } return false; } |