From fd653fd3d844838ee6ed3f05cb6c69cf0c2992c8 Mon Sep 17 00:00:00 2001 From: Nikolai Kholiavin Date: Tue, 2 Sep 2025 16:57:10 +0000 Subject: [PATCH 1/2] [type-propagation] Improve analysis correctness Previously, writes to UNKNOWN array indexes were treated like writes to any other constant index, but this is incorrect - writing to two UNKNOWN indexes shouldn't overwrite the written values. Also, writes to UNKNOWN indexes inside a loop were treated as writes to constant index 0. So handle these cases correctly. Issue: https://gitee.com/openharmony/arkcompiler_runtime_core/issues/ICVV0X Signed-off-by: Nikolai Kholiavin --- .../docs/object_type_propagation_doc.md | 2 +- .../optimizer/analysis/alias_analysis.cpp | 9 +- .../optimizer/analysis/alias_visitor.h | 82 +++++---- .../analysis/object_type_propagation.cpp | 161 +++++++++++------- .../optimizer/analysis/typed_ref_set.h | 12 +- .../tests/ani/tests/bridges/CMakeLists.txt | 5 - .../plugins/ets/tests/checked/CMakeLists.txt | 1 + .../type_propagation_unknown_offset.ets | 148 ++++++++++++++++ .../assignment_of_arrays_with_union_types.ets | 2 - ...ssignment_of_arrays_with_union_types_n.ets | 2 - .../20.union_types/union_type_assignment.ets | 2 - .../ets_func_tests/escompat/ArrayFlatTest.ets | 5 - 12 files changed, 308 insertions(+), 123 deletions(-) create mode 100644 static_core/plugins/ets/tests/checked/type_propagation_unknown_offset.ets diff --git a/static_core/compiler/docs/object_type_propagation_doc.md b/static_core/compiler/docs/object_type_propagation_doc.md index 1148a05413..3cb8d0694b 100644 --- a/static_core/compiler/docs/object_type_propagation_doc.md +++ b/static_core/compiler/docs/object_type_propagation_doc.md @@ -20,7 +20,7 @@ Get as much type information as possible from control- and data-flow graphs. Thi Instruction visit consists of two parts: 1. **AliasVisitor** part - here we track object creations, object moves (i.e. `Phi` instructions) and Loads/Stores. - * There is special processing of Load/Store instructions with non-immediate indices (like `a[x]` and unlike `a[3]` or `a.field`). We have unique `Pointer::CreateUnknownOffset` offset, which corresponds to all such offsets. Unknown offset may alias with any other (constant or unknown) offset, so real refs corresponding to fixed offset `f`in `a` are union `a[f] | a[UNKNOWN]` (makes sence only for arrays) + * There is special processing of Load/Store instructions with non-immediate indices (like `a[x]` and unlike `a[3]` or `a.field`). We have unique `Pointer::CreateUnknownOffset` offset, which corresponds to all such offsets. Unknown offset may alias with any other (constant or unknown) offset, so real refs corresponding to fixed offset `f`in `a` are union `a[f] | a[UNKNOWN]` (makes sence only for arrays). Reading from an unknown offset for this reason can't be analysed and is not modeled. 2. **TypePropagationVisitor** part - here we update `TypeInfo`s we have (stored in `TypedRefSet`s) ### Visiting Loops diff --git a/static_core/compiler/optimizer/analysis/alias_analysis.cpp b/static_core/compiler/optimizer/analysis/alias_analysis.cpp index 81bcda145a..36fea9882d 100644 --- a/static_core/compiler/optimizer/analysis/alias_analysis.cpp +++ b/static_core/compiler/optimizer/analysis/alias_analysis.cpp @@ -330,7 +330,7 @@ AliasType AliasAnalysis::CheckMemAddressEmptyIntersectionCase(const PointerWithI return NO_ALIAS; } // Different fields cannot alias each other even if they are not created locally - if (p1.GetType() == OBJECT_FIELD && !p1.HasSameOffset(p2)) { + if (p1.GetType() == OBJECT_FIELD && !p1.HasSameOffsetDroppedIdx(p2)) { return NO_ALIAS; } if (p1.GetType() == ARRAY_ELEMENT) { @@ -378,9 +378,8 @@ AliasType AliasAnalysis::CheckMemAddress(const Pointer &p1, const Pointer &p2, b return NO_ALIAS; } - if (p1.GetType() == STATIC_FIELD || p2.GetType() == STATIC_FIELD || p1.GetType() == POOL_CONSTANT || - p2.GetType() == POOL_CONSTANT) { - if (p1.HasSameOffset(p2)) { + if (p1.GetType() == STATIC_FIELD || p1.GetType() == POOL_CONSTANT) { + if (p1.HasSameOffsetDroppedIdx(p2)) { return MUST_ALIAS; } return NO_ALIAS; @@ -484,7 +483,7 @@ AliasType AliasAnalysis::SingleIntersectionAliasing(const Pointer &p1, const Poi } break; case OBJECT_FIELD: - if (!p1.HasSameOffset(p2)) { + if (!p1.HasSameOffsetDroppedIdx(p2)) { return NO_ALIAS; } break; diff --git a/static_core/compiler/optimizer/analysis/alias_visitor.h b/static_core/compiler/optimizer/analysis/alias_visitor.h index f817a11475..f83dcff1c5 100644 --- a/static_core/compiler/optimizer/analysis/alias_visitor.h +++ b/static_core/compiler/optimizer/analysis/alias_visitor.h @@ -52,11 +52,10 @@ enum PointerType { }; class PointerOffset { -protected: +public: PointerOffset(PointerType type, uint64_t imm, const void *typePtr) : type_(type), imm_(imm), typePtr_(typePtr) {}; PointerOffset() = default; -public: // Used for fields with offset not known in compile-time static PointerOffset CreateUnknownOffset() { @@ -84,6 +83,11 @@ public: return typePtr_; } + bool IsObject() const + { + return type_ == OBJECT; + } + struct Hash { uint32_t operator()(PointerOffset const &p) const { @@ -120,19 +124,18 @@ private: std::ostream &operator<<(std::ostream &out, const PointerOffset &p); -class Pointer : private PointerOffset { +class Pointer { private: Pointer(PointerType type, const Inst *base, const Inst *idx, uint64_t imm, const void *typePtr) - : PointerOffset(type, imm, typePtr), base_(base), idx_(idx) {}; + : base_(base), idx_(idx), off_(type, imm, typePtr) {}; public: - Pointer(const Inst *base, const PointerOffset &offset) : PointerOffset(offset), base_(base) {} + Pointer(const Inst *base, const PointerOffset &offset) + : Pointer(offset.GetType(), base, nullptr, offset.GetImm(), offset.GetTypePtr()) + { + } Pointer() = default; - using PointerOffset::GetImm; - using PointerOffset::GetType; - using PointerOffset::GetTypePtr; - static Pointer CreateObject(const Inst *base) { ASSERT(base != nullptr); @@ -177,7 +180,7 @@ public: static Pointer CreateUnknownOffset(const Inst *obj) { - return Pointer(UNKNOWN_OFFSET, obj, nullptr, 0, nullptr); + return Pointer(obj, PointerOffset::CreateUnknownOffset()); } const Inst *GetBase() const @@ -190,13 +193,31 @@ public: return idx_; } - void Dump(std::ostream *out) const; + PointerType GetType() const + { + return off_.GetType(); + } + + uint64_t GetImm() const + { + return off_.GetImm(); + } + + const void *GetTypePtr() const + { + return off_.GetTypePtr(); + } bool IsNotEscapingAlias() const; /// Returns true if reference is created in the current function bool IsLocalCreatedAlias() const; + bool IsObject() const + { + return GetType() == OBJECT; + } + struct Hash { uint32_t operator()(Pointer const &p) const { @@ -214,54 +235,55 @@ public: bool operator==(const Pointer &p) const { - auto ret = PointerOffset::operator==(p) && GetBase() == p.GetBase() && GetIdx() == p.GetIdx(); + auto ret = GetOffset() == p.GetOffset() && GetBase() == p.GetBase() && GetIdx() == p.GetIdx(); return ret; } - bool HasSameOffset(const Pointer &p) const - { - return PointerOffset::HasSameOffset(p); - } - - bool IsObject() const - { - return GetType() == OBJECT; - } - - PointerOffset GetOffset() const + bool HasSameOffsetDroppedIdx(const Pointer &p) const { - // copy the base class PointerOffset - return {static_cast(*this)}; + return GetOffsetDroppedIdx().HasSameOffset(p.GetOffsetDroppedIdx()); } - Pointer DropIdx() const + PointerOffset GetOffsetDroppedIdx() const { if (idx_ == nullptr) { - return *this; + return GetOffset(); } ASSERT(GetTypePtr() == nullptr); if (!idx_->IsConst() || GetType() == DICTIONARY_ELEMENT) { - return CreateUnknownOffset(base_); + return PointerOffset::CreateUnknownOffset(); } ASSERT(GetType() == ARRAY_ELEMENT || GetType() == RAW_OFFSET); auto newImm = GetImm() + idx_->CastToConstant()->GetIntValue(); - return {GetType(), base_, nullptr, newImm, GetTypePtr()}; + return PointerOffset(GetType(), newImm, GetTypePtr()); + } + + std::pair DropIdx() const + { + return {GetBase(), GetOffsetDroppedIdx()}; } + void Dump(std::ostream *out) const; + using Set = ArenaUnorderedSet; template using Map = ArenaUnorderedMap; private: + const PointerOffset &GetOffset() const + { + return off_; + } + /** * Returns true if a reference escapes the scope of current function: * Various function calls, constructors and stores to another objects' fields, arrays */ static bool IsEscapingAlias(const Inst *inst); -private: const Inst *base_ {nullptr}; const Inst *idx_ {nullptr}; + PointerOffset off_; }; std::ostream &operator<<(std::ostream &out, const Pointer &p); diff --git a/static_core/compiler/optimizer/analysis/object_type_propagation.cpp b/static_core/compiler/optimizer/analysis/object_type_propagation.cpp index aa0a4ad5de..0343b5c452 100644 --- a/static_core/compiler/optimizer/analysis/object_type_propagation.cpp +++ b/static_core/compiler/optimizer/analysis/object_type_propagation.cpp @@ -257,7 +257,11 @@ public: void WalkEdges(); Ref NewRef(ObjectTypeInfo info = ObjectTypeInfo::INVALID); ObjectTypeInfo GetRefTypeInfo(Ref ref) const; - std::string ToString(ObjectTypeInfo typeInfo); + auto Printer(ObjectTypeInfo typeInfo); + template + auto Printer(const TypedRefSet &typedRefSet); + template + auto Printer(TypedRefSet &&typedRefSet); void DumpRefInfos(std::ostream &os); ArenaTypedRefSet &GetInstRefSet(const Inst *inst); ArenaTypedRefSet &TryGetInstRefSet(const Inst *inst); @@ -292,9 +296,10 @@ private: void VisitLoop(Loop *loop); void RollbackChains(); void AddTempEdge(const Pointer &from, const Pointer &to); - bool AddEdge(Pointer from, Pointer to); - bool AddLoadEdge(const Pointer &from, const Inst *toObj, const ArenaTypedRefSet &srcRefSet); - bool AddStoreEdge(const Inst *fromObj, const Pointer &to, const ArenaTypedRefSet &srcRefSet); + bool AddEdge(const Pointer &from, const Pointer &to); + bool AddLoadEdge(const PointerOffset &fromOffset, const Inst *toObj, const ArenaTypedRefSet &srcRefSet); + bool AddStoreEdge(const Inst *fromObj, const Inst *toObj, const PointerOffset &toOffset, + const ArenaTypedRefSet &srcRefSet); friend class LoopPropagationVisitor; @@ -320,6 +325,48 @@ private: bool inLoop_ {false}; }; +template +struct LambdaPrinter : LambdaT { + explicit LambdaPrinter(LambdaT &&self) : LambdaT(std::move(self)) {} + + friend std::ostream &operator<<(std::ostream &o, const LambdaPrinter &p) + { + return p(o); + } +}; + +auto ObjectTypePropagationVisitor::Printer(ObjectTypeInfo typeInfo) +{ + // CC-OFFNXT(G.FMT.14-CPP) project code style + return LambdaPrinter([this, typeInfo](std::ostream &o) -> std::ostream & { + if (typeInfo == ObjectTypeInfo::UNKNOWN) { + return o << "UNKNOWN"; + } + if (typeInfo == ObjectTypeInfo::INVALID) { + return o << "INVALID"; + } + return o << GetGraph()->GetRuntime()->GetClassName(typeInfo.GetClass()); + }); +} + +template +auto ObjectTypePropagationVisitor::Printer(const TypedRefSet &typedRefSet) +{ + // CC-OFFNXT(G.FMT.14-CPP) project code style + return LambdaPrinter([this, &typedRefSet](std::ostream &o) -> std::ostream & { + return o << typedRefSet.GetRefSet() << " :: " << Printer(typedRefSet.GetTypeInfo()); + }); +} + +template +auto ObjectTypePropagationVisitor::Printer(TypedRefSet &&typedRefSet) +{ + // CC-OFFNXT(G.FMT.14-CPP) project code style + return LambdaPrinter([this, localTypedRefSet = std::move(typedRefSet)](std::ostream &o) -> std::ostream & { + return o << localTypedRefSet.GetRefSet() << " :: " << Printer(localTypedRefSet.GetTypeInfo()); + }); +} + bool ObjectTypePropagationVisitor::RunImpl() { if (!liveIns_.Run(false)) { @@ -357,7 +404,7 @@ void ObjectTypePropagationVisitor::SetTypeInfosInGraph() }); ASSERT_DO(info != ObjectTypeInfo::UNKNOWN || isNull, (std::cerr << "Inst shoulnd't have UNKNOWN TypeInfo: " << *inst << "\n" - << "refs: " << refs << "\n", + << "refs: " << Printer(refs) << "\n", GetGraph()->Dump(&std::cerr))); auto commonInfo = refs.GetTypeInfo(); TryImproveTypeInfo(info, commonInfo); @@ -439,22 +486,11 @@ ObjectTypeInfo ObjectTypePropagationVisitor::GetRefTypeInfo(Ref ref) const return refInfos_.at(ref); } -std::string ObjectTypePropagationVisitor::ToString(ObjectTypeInfo typeInfo) -{ - if (typeInfo == ObjectTypeInfo::UNKNOWN) { - return "UNKNOWN"; - } - if (typeInfo == ObjectTypeInfo::INVALID) { - return "INVALID"; - } - return GetGraph()->GetRuntime()->GetClassName(typeInfo.GetClass()); -} - void ObjectTypePropagationVisitor::DumpRefInfos(std::ostream &os) { for (Ref ref = BasicBlockState::REAL_REF_START; ref < refInfos_.size(); ref++) { auto typeInfo = GetRefTypeInfo(ref); - os << ref << ": " << ToString(typeInfo) << "\n"; + os << ref << ": " << Printer(typeInfo) << "\n"; } } @@ -514,7 +550,7 @@ void ObjectTypePropagationVisitor::AddDirectEdge(const Pointer &p) ASSERT(inst != nullptr); auto &refs = IsZeroConstantOrNullPtr(inst) ? nullSet_ : currentBlockState_->NewUnknownRef(); instRefSets_.try_emplace(inst, refs); - COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << "Add Direct " << *inst << ": " << instRefSets_.at(inst); + COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << "Add Direct " << *inst << ": " << Printer(instRefSets_.at(inst)); } void ObjectTypePropagationVisitor::AddConstantDirectEdge(Inst *inst, [[maybe_unused]] uint32_t id) @@ -527,7 +563,8 @@ void ObjectTypePropagationVisitor::AddCopyEdge(const Pointer &from, const Pointe { COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << "Add Copy " << from << " -> " << to; [[maybe_unused]] auto changed = AddEdge(from, to); - COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " ToSet: " << TryGetInstRefSet(to.GetBase()) << " changed: " << changed; + COMPILER_LOG(DEBUG, TYPE_PROPAGATION) + << " ToSet: " << Printer(TryGetInstRefSet(to.GetBase())) << " changed: " << changed; } void ObjectTypePropagationVisitor::VisitHeapInv([[maybe_unused]] Inst *inst) @@ -554,10 +591,11 @@ void ObjectTypePropagationVisitor::SetTypeInfo(Inst *inst, ObjectTypeInfo info) { // should be data-flow input instead of check inst ASSERT(!inst->IsCheck() || inst->GetOpcode() == Opcode::RefTypeCheck); - COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << "SetTypeInfo inst " << inst->GetId() << " " << ToString(info); + COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << "SetTypeInfo inst " << inst->GetId() << " " << Printer(info); auto &refSet = TryGetInstRefSet(inst); if (&refSet == &nullSet_) { ASSERT(inLoop_); + COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " -> now has type " << Printer(refSet.GetTypeInfo()); return; } if (refSet.PopCount() == 1) { @@ -565,6 +603,7 @@ void ObjectTypePropagationVisitor::SetTypeInfo(Inst *inst, ObjectTypeInfo info) TryImproveTypeInfo(refInfos_[ref], info); } refSet.TryImproveTypeInfo(info); + COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " -> now has type " << Printer(refSet.GetTypeInfo()); } BasicBlockState *ObjectTypePropagationVisitor::GetState(const BasicBlock *block) @@ -661,32 +700,28 @@ void ObjectTypePropagationVisitor::AddTempEdge(const Pointer &from, const Pointe { ASSERT(from.GetBase() != nullptr); { - auto &instEdges = - loopEdges_.try_emplace(from.GetBase(), GetGraph()->GetLocalAllocator()->Adapter()).first->second; - auto &ptrEdges = - instEdges.try_emplace(from.GetOffset(), GetGraph()->GetLocalAllocator()->Adapter()).first->second; + auto [fromObj, fromOffset] = from.DropIdx(); + auto &instEdges = loopEdges_.try_emplace(fromObj, GetGraph()->GetLocalAllocator()->Adapter()).first->second; + auto &ptrEdges = instEdges.try_emplace(fromOffset, GetGraph()->GetLocalAllocator()->Adapter()).first->second; ptrEdges.push_back(to); } if (from.IsObject() && !to.IsObject() && to.GetType() != STATIC_FIELD && to.GetType() != POOL_CONSTANT) { // store - auto &instEdges = - loopStoreEdges_.try_emplace(to.GetBase(), GetGraph()->GetLocalAllocator()->Adapter()).first->second; - auto &ptrEdges = - instEdges.try_emplace(to.GetOffset(), GetGraph()->GetLocalAllocator()->Adapter()).first->second; + auto [toObj, toOffset] = to.DropIdx(); + auto &instEdges = loopStoreEdges_.try_emplace(toObj, GetGraph()->GetLocalAllocator()->Adapter()).first->second; + auto &ptrEdges = instEdges.try_emplace(toOffset, GetGraph()->GetLocalAllocator()->Adapter()).first->second; ptrEdges.push_back(from.GetBase()); } } -bool ObjectTypePropagationVisitor::AddEdge(Pointer from, Pointer to) +bool ObjectTypePropagationVisitor::AddEdge(const Pointer &from, const Pointer &to) { - from = from.DropIdx(); - to = to.DropIdx(); - auto fromObj = from.GetBase(); + auto [fromObj, fromOffset] = from.DropIdx(); + auto [toObj, toOffset] = to.DropIdx(); auto it = instRefSets_.find(fromObj); const ArenaTypedRefSet *srcRefSet = nullptr; - auto toObj = to.GetBase(); if (fromObj == nullptr) { - ASSERT(from.GetType() == STATIC_FIELD || from.GetType() == POOL_CONSTANT); + ASSERT(fromOffset.GetType() == STATIC_FIELD || fromOffset.GetType() == POOL_CONSTANT); srcRefSet = ¤tBlockState_->NewUnknownRef(); } else if (it != instRefSets_.end()) { srcRefSet = &it->second; @@ -698,7 +733,7 @@ bool ObjectTypePropagationVisitor::AddEdge(Pointer from, Pointer to) return false; } ASSERT(currentBlockState_ != nullptr); - if (from.IsObject() && to.IsObject()) { + if (fromOffset.IsObject() && toOffset.IsObject()) { // copy bool changed = false; auto &destRefSet = GetOrCreateInstRefSet(toObj, changed); @@ -708,33 +743,33 @@ bool ObjectTypePropagationVisitor::AddEdge(Pointer from, Pointer to) } return changed; } - if (to.IsObject()) { - return AddLoadEdge(from, toObj, *srcRefSet); + if (toOffset.IsObject()) { + return AddLoadEdge(fromOffset, toObj, *srcRefSet); } - if (from.IsObject()) { - return AddStoreEdge(fromObj, to, *srcRefSet); + if (fromOffset.IsObject()) { + return AddStoreEdge(fromObj, toObj, toOffset, *srcRefSet); } // both are not objects UNREACHABLE(); } -bool ObjectTypePropagationVisitor::AddLoadEdge(const Pointer &from, const Inst *toObj, +bool ObjectTypePropagationVisitor::AddLoadEdge(const PointerOffset &fromOffset, const Inst *toObj, const ArenaTypedRefSet &srcRefSet) { bool changed = false; COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " LOAD"; auto &destRefSet = GetOrCreateInstRefSet(toObj, changed); - if (from.GetType() == STATIC_FIELD || from.GetType() == POOL_CONSTANT) { + if (fromOffset.GetType() == STATIC_FIELD || fromOffset.GetType() == POOL_CONSTANT) { COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " STATIC FIELD -> " << toObj->GetId(); changed |= destRefSet != srcRefSet; destRefSet = srcRefSet; return changed; } - COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " initial destRefSet: " << destRefSet; - srcRefSet.Visit([offset = from.GetOffset(), &destRefSet, &changed, this](Ref ref) { + COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " initial destRefSet: " << Printer(destRefSet); + srcRefSet.Visit([&fromOffset, &destRefSet, &changed, this](Ref ref) { COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " Visit " << ref; - auto &srcFieldSet = currentBlockState_->GetFieldRefSet(ref, offset); - COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " srcFieldSet: " << srcFieldSet; + auto &srcFieldSet = currentBlockState_->GetFieldRefSet(ref, fromOffset); + COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " srcFieldSet: " << Printer(srcFieldSet); if (!destRefSet.Includes(srcFieldSet)) { destRefSet |= srcFieldSet; changed = true; @@ -743,7 +778,7 @@ bool ObjectTypePropagationVisitor::AddLoadEdge(const Pointer &from, const Inst * return changed; } -bool ObjectTypePropagationVisitor::AddStoreEdge(const Inst *fromObj, const Pointer &to, +bool ObjectTypePropagationVisitor::AddStoreEdge(const Inst *fromObj, const Inst *toObj, const PointerOffset &toOffset, const ArenaTypedRefSet &srcRefSet) { COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " STORE"; @@ -752,35 +787,34 @@ bool ObjectTypePropagationVisitor::AddStoreEdge(const Inst *fromObj, const Point // primitive ANY type or null as integer is stored return false; } - if (to.GetType() == STATIC_FIELD || to.GetType() == POOL_CONSTANT) { + if (toOffset.GetType() == STATIC_FIELD || toOffset.GetType() == POOL_CONSTANT) { return false; } - if (instRefSets_.find(to.GetBase()) == instRefSets_.end()) { + if (instRefSets_.find(toObj) == instRefSets_.end()) { ASSERT(inLoop_); return true; } - auto &destRefSet = instRefSets_.at(to.GetBase()); + auto &destRefSet = instRefSets_.at(toObj); bool changed = false; bool escape = false; - destRefSet.Visit([&to, &srcRefSet, &changed, &escape, this](Ref ref) { + destRefSet.Visit([&toOffset, &srcRefSet, &changed, &escape, this](Ref ref) { if (currentBlockState_->IsEscaped(ref)) { escape = true; } if (ref == ALL_REF) { return; } - auto offset = to.GetOffset(); - auto &dstFieldSet = currentBlockState_->CreateFieldRefSet(ref, offset, changed); + auto &dstFieldSet = currentBlockState_->CreateFieldRefSet(ref, toOffset, changed); COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " ref: " << ref; - COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " src: " << srcRefSet; - if (!inLoop_ && to.GetType() != UNKNOWN_OFFSET) { + COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " src: " << Printer(srcRefSet); + if (!inLoop_ && toOffset.GetType() != UNKNOWN_OFFSET) { changed |= dstFieldSet != srcRefSet; dstFieldSet = srcRefSet; } else if (!dstFieldSet.Includes(srcRefSet)) { dstFieldSet |= srcRefSet; changed = true; } - COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " dst: " << dstFieldSet; + COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " dst: " << Printer(dstFieldSet); }); if (escape) { srcRefSet.Visit([this](Ref ref) { currentBlockState_->TryEscape(ref); }); @@ -904,6 +938,9 @@ const ArenaTypedRefSet &BasicBlockState::GetFieldRefSet(Ref base, const PointerO return visitor_->GetAllSet(); } COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " GetFieldRefSet " << base << ' ' << offset; + if (offset.GetType() == UNKNOWN_OFFSET) { + return escaped_; + } auto it = fieldRefs_.find(base); if (it == fieldRefs_.end()) { return escaped_; @@ -961,12 +998,10 @@ const ArenaTypedRefSet &BasicBlockState::NewUnknownRef() } template -class IsMap : public std::false_type { -}; +class IsMap : public std::false_type {}; template -class IsMap> : public std::true_type { -}; +class IsMap> : public std::true_type {}; template constexpr bool IS_ORDERED_MAP_V = IsMap>::value; @@ -1177,18 +1212,18 @@ ArenaAllocator *BasicBlockState::GetLocalAllocator() auto *visitor = state.visitor_; os << "BB " << state.GetBlockId() << " state:\n"; for (auto [ref, refInfo] : state.fieldRefs_) { - os << " ref " << ref << " " << visitor->ToString(visitor->GetRefTypeInfo(ref)) << ":\n"; + os << " ref " << ref << " " << visitor->Printer(visitor->GetRefTypeInfo(ref)) << ":\n"; for (auto [offset, refs] : *refInfo) { - os << " " << offset << " -> " << refs << " " << visitor->ToString(refs.GetTypeInfo()) << "\n"; + os << " " << offset << " -> " << visitor->Printer(refs) << "\n"; } } - os << "Escaped: " << state.escaped_ << "\n"; + os << "Escaped: " << visitor->Printer(state.escaped_) << "\n"; os << "Instructions RefSets (global):\n"; for (auto &[inst, refs] : visitor->GetInstRefSets()) { // short inst dump without inputs/outputs os << " " << inst->GetId() << "." << inst->GetType() << " "; inst->DumpOpcode(&os); - os << "-> " << refs << " " << visitor->ToString(refs.GetTypeInfo()) << "\n"; + os << "-> " << visitor->Printer(refs) << "\n"; } visitor->DumpRefInfos(os); return os; diff --git a/static_core/compiler/optimizer/analysis/typed_ref_set.h b/static_core/compiler/optimizer/analysis/typed_ref_set.h index 0e184a2e52..b70ff9acfd 100644 --- a/static_core/compiler/optimizer/analysis/typed_ref_set.h +++ b/static_core/compiler/optimizer/analysis/typed_ref_set.h @@ -410,8 +410,10 @@ public: return typeInfo_; } - template - friend std::ostream &operator<<(std::ostream &os, const TypedRefSet &typedRefSet); + const SmallSet &GetRefSet() const + { + return *this; + } private: ObjectTypeInfo typeInfo_ {ObjectTypeInfo::INVALID}; @@ -432,12 +434,6 @@ std::ostream &operator<<(std::ostream &os, const SmallSet &smallSet) return os << "}"; } -template -std::ostream &operator<<(std::ostream &os, const TypedRefSet &typedRefSet) -{ - return os << static_cast &>(typedRefSet); -} - using ArenaSmallSet = SmallSet; using ArenaTypedRefSet = TypedRefSet; diff --git a/static_core/plugins/ets/tests/ani/tests/bridges/CMakeLists.txt b/static_core/plugins/ets/tests/ani/tests/bridges/CMakeLists.txt index 5cbf17cc62..b40f1539c6 100644 --- a/static_core/plugins/ets/tests/ani/tests/bridges/CMakeLists.txt +++ b/static_core/plugins/ets/tests/ani/tests/bridges/CMakeLists.txt @@ -72,11 +72,6 @@ set(PANDA_RUN_PREFIX add_custom_target(ani_tests_bridges_tests COMMENT "Common target to run ANI bridges tests") function(add_tests pref suff) - # NOTE(mgroshev): #29029 - if(${pref} STREQUAL "types_double") - set(RT_OPTION "--compiler-inlining=false") - endif() - set(test_name ${pref}_${suff}) panda_add_library(${test_name} SHARED ${CMAKE_CURRENT_BINARY_DIR}/tests/${test_name}/${test_name}.cpp) add_dependencies(${test_name} ani_tests_bridges_generate_test_files) diff --git a/static_core/plugins/ets/tests/checked/CMakeLists.txt b/static_core/plugins/ets/tests/checked/CMakeLists.txt index e1a28e55ca..e881a5f92e 100644 --- a/static_core/plugins/ets/tests/checked/CMakeLists.txt +++ b/static_core/plugins/ets/tests/checked/CMakeLists.txt @@ -381,6 +381,7 @@ panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ani/unsafe_memory.et panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/ets_string_cache_options.ets) panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/interface_cache_intrinsic.ets) panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/lambda_with_capture_optimized.ets) +panda_add_checked_test_ets(FILE ${CMAKE_CURRENT_SOURCE_DIR}/type_propagation_unknown_offset.ets) if (NOT ENABLE_UNIT_TESTS_FULL_COVERAGE) # NOTE #28944 diff --git a/static_core/plugins/ets/tests/checked/type_propagation_unknown_offset.ets b/static_core/plugins/ets/tests/checked/type_propagation_unknown_offset.ets new file mode 100644 index 0000000000..253d6aba0f --- /dev/null +++ b/static_core/plugins/ets/tests/checked/type_propagation_unknown_offset.ets @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! CHECKER Check type of read at unknown index +//! RUN force_jit: true, options: "--compiler-regex='.*__noinline__read_after_write.*'", entry: "type_propagation_unknown_offset.ETSGLOBAL::read_after_write" +//! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__read_after_write" +//! PASS_BEFORE "Peepholes" +//! INST "IsInstance" +//! PASS_AFTER "Peepholes" +//! PASS_AFTER_NEXT "Cleanup" +//! INST "IsInstance" + +//! CHECKER Check type of read at unknown index (AOT) +//! SKIP_IF @architecture == "arm32" +//! RUN_PAOC options: "--compiler-regex='.*__noinline__read_after_write.*'" +//! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__read_after_write" +//! PASS_BEFORE "Peepholes" +//! INST "IsInstance" +//! PASS_AFTER "Peepholes" +//! PASS_AFTER_NEXT "Cleanup" +//! INST "IsInstance" +//! RUN options: "", entry: "type_propagation_unknown_offset.ETSGLOBAL::read_after_write" + +function __noinline__read_after_write(idx1: int, idx2: int): boolean { + const arr: FixedArray = [1, 2]; + arr[idx1] = 1.0; + // cannot optimize away instanceof (obviously) + return arr[idx2] instanceof Int; +} + +function read_after_write() { + arktest.assertEQ(__noinline__read_after_write(0, 1), true); + arktest.assertEQ(__noinline__read_after_write(0, 0), false); +} + +//! CHECKER Check type of read at unknown index in a loop +//! RUN force_jit: true, options: "--compiler-regex='.*__noinline__loop_read_unknown.*'", entry: "type_propagation_unknown_offset.ETSGLOBAL::loop_read_unknown" +//! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__loop_read_unknown" +//! PASS_BEFORE "Peepholes" +//! INST "IsInstance" +//! PASS_AFTER "Peepholes" +//! PASS_AFTER_NEXT "Cleanup" +//! INST "IsInstance" + +//! CHECKER Check type of read at unknown index in a loop (AOT) +//! SKIP_IF @architecture == "arm32" +//! RUN_PAOC options: "--compiler-regex='.*__noinline__loop_read_unknown.*'" +//! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__loop_read_unknown" +//! PASS_BEFORE "Peepholes" +//! INST "IsInstance" +//! PASS_AFTER "Peepholes" +//! PASS_AFTER_NEXT "Cleanup" +//! INST "IsInstance" +//! RUN options: "", entry: "type_propagation_unknown_offset.ETSGLOBAL::loop_read_unknown" + +function __noinline__loop_read_unknown(val: Int|Double): boolean { + const arr: FixedArray = [1, 4.0, val]; + let favorite_elem: Int|Double = arr[0]; + for (let i = 0; i < arr.length; i++) { + if (arr[i] === 4) { + favorite_elem = arr[i]; + } + } + // cannot optimize away instanceof (obviously) + return favorite_elem instanceof Double; +} + +function loop_read_unknown() { + arktest.assertEQ(__noinline__loop_read_unknown(3), true); + arktest.assertEQ(__noinline__loop_read_unknown(4), false); +} + +//! DISABLED_CHECKER Check write to aliasing array in function parameter +//! RUN force_jit: true, options: "--compiler-regex='.*__noinline__aliasing_write.*'", entry: "type_propagation_unknown_offset.ETSGLOBAL::aliasing_write" +//! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__aliasing_write" +//! PASS_BEFORE "Peepholes" +//! INST "IsInstance" +//! PASS_AFTER "Peepholes" +//! PASS_AFTER_NEXT "Cleanup" +//! INST "IsInstance" + +//! DISABLED_CHECKER Check write to aliasing array in function parameter (AOT) +//! SKIP_IF @architecture == "arm32" +//! RUN_PAOC options: "--compiler-regex='.*__noinline__aliasing_write.*'" +//! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__aliasing_write" +//! PASS_BEFORE "Peepholes" +//! INST "IsInstance" +//! PASS_AFTER "Peepholes" +//! PASS_AFTER_NEXT "Cleanup" +//! INST "IsInstance" +//! RUN options: "", entry: "type_propagation_unknown_offset.ETSGLOBAL::aliasing_write" + +function __noinline__aliasing_write(arr1: FixedArray, arr2: FixedArray): boolean { + arr1[0] = 1.0; + // cannot optimize away instanceof (obviously) + return arr2[0] instanceof Int; +} + +function aliasing_write() { + arktest.assertEQ(__noinline__aliasing_write([1, 2], [3, 4]), true); + arktest.assertEQ(__noinline__aliasing_write([1, 2], [1.0, 2]), false); + const arr: FixedArray = [1, 2]; + arktest.assertEQ(__noinline__aliasing_write(arr, arr), false); +} + +//! CHECKER Check write to unknown index with possible optimization +//! RUN force_jit: true, options: "--compiler-regex='.*__noinline__positive_store.*'", entry: "type_propagation_unknown_offset.ETSGLOBAL::positive_store" +//! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__positive_store" +//! PASS_BEFORE "Peepholes" +//! INST "IsInstance" +//! PASS_AFTER "Peepholes" +//! PASS_AFTER_NEXT "Cleanup" +//! INST_NOT "IsInstance" + +//! CHECKER Check write to unknown index with possible optimization (AOT) +//! SKIP_IF @architecture == "arm32" +//! RUN_PAOC options: "--compiler-regex='.*__noinline__positive_store.*'" +//! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__positive_store" +//! PASS_BEFORE "Peepholes" +//! INST "IsInstance" +//! PASS_AFTER "Peepholes" +//! PASS_AFTER_NEXT "Cleanup" +//! INST_NOT "IsInstance" +//! RUN options: "", entry: "type_propagation_unknown_offset.ETSGLOBAL::positive_store" + +function __noinline__positive_store(idx: int, val: Int): boolean { + const arr: FixedArray = [1, 2, 3]; + arr[idx] = val; + // optimize it out! + return arr[0] instanceof Double; +} + +function positive_store() { + arktest.assertEQ(__noinline__positive_store(0, 1), false); + arktest.assertEQ(__noinline__positive_store(1, 2), false); +} diff --git a/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/assignment_of_arrays_with_union_types.ets b/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/assignment_of_arrays_with_union_types.ets index 3476c2b667..b53a624bff 100644 --- a/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/assignment_of_arrays_with_union_types.ets +++ b/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/assignment_of_arrays_with_union_types.ets @@ -17,9 +17,7 @@ /*--- desc: {{c.desc}} -ark_options: ['--compiler-inlining=false'] ---*/ -// NOTE(mgroshev): remove ark_options after #29029 interface GenericInterface { process(data: T): T; diff --git a/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/assignment_of_arrays_with_union_types_n.ets b/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/assignment_of_arrays_with_union_types_n.ets index 551c504e84..be1f572737 100644 --- a/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/assignment_of_arrays_with_union_types_n.ets +++ b/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/assignment_of_arrays_with_union_types_n.ets @@ -18,9 +18,7 @@ /*--- desc: {{c.desc}} tags: [compile-only, negative] -ark_options: ['--compiler-inlining=false'] ---*/ -// NOTE(mgroshev): remove ark_options after #29029 {{c.class}} {{c.function}} diff --git a/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/union_type_assignment.ets b/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/union_type_assignment.ets index 5970b6147a..63635e946b 100644 --- a/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/union_type_assignment.ets +++ b/static_core/plugins/ets/tests/ets-templates/03.types/20.union_types/union_type_assignment.ets @@ -17,9 +17,7 @@ /*--- desc: {{case.desc}} -ark_options: ['--compiler-inlining=false'] ---*/ -// NOTE(mgroshev): remove ark_options after #29029 {{case.dec1|indent}} diff --git a/static_core/plugins/ets/tests/ets_func_tests/escompat/ArrayFlatTest.ets b/static_core/plugins/ets/tests/ets_func_tests/escompat/ArrayFlatTest.ets index 1f89bebcbb..d40a2e6375 100644 --- a/static_core/plugins/ets/tests/ets_func_tests/escompat/ArrayFlatTest.ets +++ b/static_core/plugins/ets/tests/ets_func_tests/escompat/ArrayFlatTest.ets @@ -13,11 +13,6 @@ * limitations under the License. */ -/*--- -ark_options: ['--compiler-inlining=false'] ----*/ -// NOTE(mgroshev): remove ark_options after #29029 - function compareString(first: string, second: string) { arktest.assertEQ(first, second) } -- Gitee From cd3d9dfa9a5c4c2d0ffa4e2552ae4bf6a7199e76 Mon Sep 17 00:00:00 2001 From: Nikolai Kholiavin Date: Mon, 1 Sep 2025 19:21:41 +0000 Subject: [PATCH 2/2] [type-propagation] Improve analysis correctness 2 Previously, writes to fields of potentially aliased values (for example, function parameters) could overwrite the field value in all of the aliases. So handle these cases correctly. Issue: https://gitee.com/openharmony/arkcompiler_runtime_core/issues/ICVV0X Signed-off-by: Nikolai Kholiavin --- .../analysis/object_type_propagation.cpp | 49 ++++++++++++++++--- .../type_propagation_unknown_offset.ets | 4 +- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/static_core/compiler/optimizer/analysis/object_type_propagation.cpp b/static_core/compiler/optimizer/analysis/object_type_propagation.cpp index 0343b5c452..55c5f23b74 100644 --- a/static_core/compiler/optimizer/analysis/object_type_propagation.cpp +++ b/static_core/compiler/optimizer/analysis/object_type_propagation.cpp @@ -100,7 +100,10 @@ public: const ArenaTypedRefSet &GetFieldRefSet(Ref base, const PointerOffset &offset); void CreateFieldRefSetForNewObject(Ref base, bool defaultConstructed); - ArenaTypedRefSet &CreateFieldRefSet(Ref base, const PointerOffset &offset, bool &changed); + ArenaTypedRefSet &CreateFieldRefSetOrDefault(Ref base, const PointerOffset &offset, + const ArenaTypedRefSet &defaultValue, bool &changed); + ArenaTypedRefSet &CreateFieldRefSet(const ArenaTypedRefSet &destRefSet, Ref base, const PointerOffset &offset, + bool &changed); const ArenaTypedRefSet &NewUnknownRef(); const ArenaTypedRefSet &GetNullRef(); void Merge(BasicBlockState *other); @@ -272,6 +275,7 @@ public: { return instRefSets_; } + static bool MayReadOldFieldValueAfterStore(const ArenaTypedRefSet &destRefSet); protected: void AddDirectEdge(const Pointer &p) override; @@ -298,6 +302,7 @@ private: void AddTempEdge(const Pointer &from, const Pointer &to); bool AddEdge(const Pointer &from, const Pointer &to); bool AddLoadEdge(const PointerOffset &fromOffset, const Inst *toObj, const ArenaTypedRefSet &srcRefSet); + bool CanStoreOverwriteRefSet(const ArenaTypedRefSet &destRefSet, const PointerOffset &toOffset); bool AddStoreEdge(const Inst *fromObj, const Inst *toObj, const PointerOffset &toOffset, const ArenaTypedRefSet &srcRefSet); @@ -778,6 +783,23 @@ bool ObjectTypePropagationVisitor::AddLoadEdge(const PointerOffset &fromOffset, return changed; } +bool ObjectTypePropagationVisitor::CanStoreOverwriteRefSet(const ArenaTypedRefSet &destRefSet, + const PointerOffset &toOffset) +{ + // Don't overwrite at UNKNOWN offsets and field values that may be read later + return !inLoop_ && toOffset.GetType() != UNKNOWN_OFFSET && !MayReadOldFieldValueAfterStore(destRefSet); +} + +bool ObjectTypePropagationVisitor::MayReadOldFieldValueAfterStore(const ArenaTypedRefSet &destRefSet) +{ + if (destRefSet.PopCount() == 1) { + // LHS object is a single possible ref, old value can't be read later. + // Even if storing at UNKNOWN index, reading it back is modeled separately + return false; + } + return true; +} + bool ObjectTypePropagationVisitor::AddStoreEdge(const Inst *fromObj, const Inst *toObj, const PointerOffset &toOffset, const ArenaTypedRefSet &srcRefSet) { @@ -797,17 +819,17 @@ bool ObjectTypePropagationVisitor::AddStoreEdge(const Inst *fromObj, const Inst auto &destRefSet = instRefSets_.at(toObj); bool changed = false; bool escape = false; - destRefSet.Visit([&toOffset, &srcRefSet, &changed, &escape, this](Ref ref) { + destRefSet.Visit([&destRefSet, &toOffset, &srcRefSet, &changed, &escape, this](Ref ref) { if (currentBlockState_->IsEscaped(ref)) { escape = true; } if (ref == ALL_REF) { return; } - auto &dstFieldSet = currentBlockState_->CreateFieldRefSet(ref, toOffset, changed); + auto &dstFieldSet = currentBlockState_->CreateFieldRefSet(destRefSet, ref, toOffset, changed); COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " ref: " << ref; COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " src: " << Printer(srcRefSet); - if (!inLoop_ && toOffset.GetType() != UNKNOWN_OFFSET) { + if (CanStoreOverwriteRefSet(destRefSet, toOffset)) { changed |= dstFieldSet != srcRefSet; dstFieldSet = srcRefSet; } else if (!dstFieldSet.Includes(srcRefSet)) { @@ -972,7 +994,8 @@ void BasicBlockState::CreateFieldRefSetForNewObject(Ref base, bool defaultConstr } } -ArenaTypedRefSet &BasicBlockState::CreateFieldRefSet(Ref base, const PointerOffset &offset, bool &changed) +ArenaTypedRefSet &BasicBlockState::CreateFieldRefSetOrDefault(Ref base, const PointerOffset &offset, + const ArenaTypedRefSet &defaultValue, bool &changed) { COMPILER_LOG(DEBUG, TYPE_PROPAGATION) << " CreateFieldRefSet " << base << ' ' << offset; auto [it1, ins1] = fieldRefs_.try_emplace(base, GetLocalAllocator()); @@ -986,12 +1009,24 @@ ArenaTypedRefSet &BasicBlockState::CreateFieldRefSet(Ref base, const PointerOffs UNREACHABLE(); } } - auto [it2, ins2] = - objectInfos.Mut(GetLocalAllocator())->try_emplace(offset, GetLocalAllocator(), ObjectTypeInfo::UNKNOWN); + auto [it2, ins2] = objectInfos.Mut(GetLocalAllocator())->try_emplace(offset, defaultValue); changed |= ins2; return it2->second; } +ArenaTypedRefSet &BasicBlockState::CreateFieldRefSet(const ArenaTypedRefSet &destRefSet, Ref base, + const PointerOffset &offset, bool &changed) +{ + if (ObjectTypePropagationVisitor::MayReadOldFieldValueAfterStore(destRefSet)) { + // the old value might show up later + return CreateFieldRefSetOrDefault(base, offset, escaped_, changed); + } else { + // the old value is completely overwritten + return CreateFieldRefSetOrDefault(base, offset, ArenaTypedRefSet(GetLocalAllocator(), ObjectTypeInfo::UNKNOWN), + changed); + } +} + const ArenaTypedRefSet &BasicBlockState::NewUnknownRef() { return escaped_; diff --git a/static_core/plugins/ets/tests/checked/type_propagation_unknown_offset.ets b/static_core/plugins/ets/tests/checked/type_propagation_unknown_offset.ets index 253d6aba0f..ed6a356b67 100644 --- a/static_core/plugins/ets/tests/checked/type_propagation_unknown_offset.ets +++ b/static_core/plugins/ets/tests/checked/type_propagation_unknown_offset.ets @@ -82,7 +82,7 @@ function loop_read_unknown() { arktest.assertEQ(__noinline__loop_read_unknown(4), false); } -//! DISABLED_CHECKER Check write to aliasing array in function parameter +//! CHECKER Check write to aliasing array in function parameter //! RUN force_jit: true, options: "--compiler-regex='.*__noinline__aliasing_write.*'", entry: "type_propagation_unknown_offset.ETSGLOBAL::aliasing_write" //! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__aliasing_write" //! PASS_BEFORE "Peepholes" @@ -91,7 +91,7 @@ function loop_read_unknown() { //! PASS_AFTER_NEXT "Cleanup" //! INST "IsInstance" -//! DISABLED_CHECKER Check write to aliasing array in function parameter (AOT) +//! CHECKER Check write to aliasing array in function parameter (AOT) //! SKIP_IF @architecture == "arm32" //! RUN_PAOC options: "--compiler-regex='.*__noinline__aliasing_write.*'" //! METHOD "type_propagation_unknown_offset.ETSGLOBAL::__noinline__aliasing_write" -- Gitee