opt/singleimpl/SingleImplDefs.h (115 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include <memory> #include <unordered_map> #include <unordered_set> #include <utility> #include <vector> #include "CheckCastTransform.h" #include "ClassHierarchy.h" #include "DexClass.h" #include "IRInstruction.h" #include "IRList.h" #include "SingleImpl.h" struct ProguardMap; /** * Analyze and optimize data structures. * The data set is filled during analysis and it's used by the optimizer. */ using Scope = std::vector<DexClass*>; using TypeList = std::vector<DexType*>; using TypeMap = std::unordered_map<DexType*, DexType*>; using TypeToTypes = std::unordered_map<DexType*, TypeList>; using FieldList = std::vector<DexField*>; using OrderedMethodSet = std::set<DexMethod*, dexmethods_comparator>; using OpcodeList = std::vector<IRInstruction*>; using OpcodeSet = std::unordered_set<IRInstruction*>; using FieldRefToOpcodes = std::unordered_map<DexFieldRef*, OpcodeList>; using MethodToOpcodes = std::unordered_map<DexMethodRef*, OpcodeSet>; using NewMethods = std::unordered_map<DexMethodRef*, DexMethodRef*>; using NewVTable = std::vector<std::pair<DexMethod*, DexMethod*>>; /** * Possible escape reason of interfaces. * Those are all problematic cases that require us to drop the optimization * or require deeper analysis. */ enum EscapeReason : uint32_t { NO_ESCAPE = 0x0, // analysis escape reason // inteface has a <clinit> CLINIT = 0x1, // interface has static fields HAS_SFIELDS = 0x2, // interface appears in an array type HAS_ARRAY_TYPE = 0x4, // interface is in the signature of a native method NATIVE_METHOD = 0x8, // a method ref to the interface is for a method not defined on the interface UNKNOWN_MREF = 0x20, // a field ref whose class is the interface HAS_FIELD_REF = 0X40, // filtered by config FILTERED = 0x80, // parent is unknown to redex IMPL_PARENT_ESCAPED = 0x100, // interface marked DoNotStrip DO_NOT_STRIP = 0X400, // create a reference across stores that is illegal CROSS_STORES = 0x800, // optimization escape reason // interface substitution causes a collision with an existing method SIG_COLLISION = 0x10000, // interface substitution causes a collision with an existing field FIELD_COLLISION = 0x20000, // move the interface to the next pass. Something dropped the interface // for the current pass NEXT_PASS = 0x40000, }; inline EscapeReason operator|=(EscapeReason& a, const EscapeReason b) { return (a = static_cast<EscapeReason>(static_cast<uint32_t>(a) | static_cast<uint32_t>(b))); } inline EscapeReason operator|(const EscapeReason a, const EscapeReason b) { return static_cast<EscapeReason>(static_cast<uint32_t>(a) | static_cast<uint32_t>(b)); } inline EscapeReason operator&(const EscapeReason a, const EscapeReason b) { return static_cast<EscapeReason>(static_cast<uint32_t>(a) & static_cast<uint32_t>(b)); } /** * For every single implemented interface the set of data related to that * interface only. * Every map here points to the original (as found in analysis) def/ref. * Fielddef/ref and typeref are easy to manage in that an optimization step * (if allowed) can simply go through and flip the type. * Methods handling is more complex as each method may have multiple * interfaces in the signature. The optimizer keeps track of current * methods as they get rewritten. */ struct SingleImplData { // single concrete class for the single impl interface (entry in the // SingleImplInterfaces map) DexType* cls; // Single impl interface escape EscapeReason escape; // Direct children of the interface TypeSet children; // single impl interface typed fields FieldList fielddefs; // methods with the single impl interface in the signature OrderedMethodSet methoddefs; // single impl interface typerefs OpcodeList typerefs; // single impl interface typed fieldref opcode FieldRefToOpcodes fieldrefs; // invoke-interface to the single impl interface methods MethodToOpcodes intf_methodrefs; // opcodes to a methodref with the single impl interface in the signature MethodToOpcodes methodrefs; std::unordered_map<DexMethod*, std::unordered_map<IRInstruction*, IRList::iterator>> referencing_methods; std::mutex mutex; bool is_escaped() const { return escape != NO_ESCAPE; } }; // map from single implemented interfaces to the data related to that interface using SingleImpls = std::unordered_map<DexType*, SingleImplData>; struct SingleImplAnalysis { virtual ~SingleImplAnalysis() = default; /** * Create a SingleImplAnalysis from a given Scope. */ static std::unique_ptr<SingleImplAnalysis> analyze( const Scope& scope, const DexStoresVector& stores, const TypeMap& single_impl, const TypeSet& intfs, const ProguardMap& pg_map, const SingleImplConfig& config); /** * Escape an interface and all parent interfaces. */ void escape_interface(DexType* intf, EscapeReason reason); /** * Return whether a type is escaped. Works with any type. */ bool is_escaped(DexType* intf) const { auto single_impl = single_impls.find(intf); return single_impl != single_impls.end() && single_impl->second.is_escaped(); } /** * Return whether a type is single impl. */ bool is_single_impl(DexType* intf) const { auto single_impl = single_impls.find(intf); return single_impl != single_impls.end(); } /** * Load the list of interface to optimize. */ void get_interfaces(TypeList& to_optimize) const; SingleImplData& get_single_impl_data(DexType* intf) { return single_impls.at(intf); } protected: SingleImpls single_impls; }; struct OptimizeStats { size_t removed_interfaces{0}; size_t inserted_check_casts{0}; size_t retained_check_casts{0}; check_casts::impl::Stats post_process; size_t deleted_removed_instructions{0}; OptimizeStats& operator+=(const OptimizeStats& rhs) { removed_interfaces += rhs.removed_interfaces; inserted_check_casts += rhs.inserted_check_casts; retained_check_casts += rhs.retained_check_casts; post_process += rhs.post_process; deleted_removed_instructions += rhs.deleted_removed_instructions; return *this; } }; /** * Run an optimization pass over a SingleImplAnalysis. */ OptimizeStats optimize(std::unique_ptr<SingleImplAnalysis> analysis, const ClassHierarchy& ch, Scope& scope, const SingleImplConfig& config);