From 8617c403ec441fe09a2c6e3a1db9359c9dae5edf Mon Sep 17 00:00:00 2001 From: "Sergey V. Ignatov" Date: Fri, 31 Mar 2023 12:26:55 +0300 Subject: [PATCH] [Compiler] Support LoadObjFromConst and FunctionImmediate in VN and LICM Signed-off-by: Sergey V. Ignatov --- compiler/optimizer/ir/graph_checker.cpp | 5 +- compiler/optimizer/ir/inst.cpp | 12 +- compiler/optimizer/ir/inst.h | 14 ++ compiler/optimizer/ir/instructions.yaml | 4 +- compiler/optimizer/ir/ir_constructor.h | 16 ++ compiler/optimizer/optimizations/licm.cpp | 14 ++ compiler/optimizer/optimizations/licm.h | 2 + compiler/tests/licm_test.cpp | 116 +++++++++++ compiler/tests/vn_test.cpp | 227 ++++++++++++++++++++++ tests/checked/README.md | 1 + tests/checked/checker.rb | 17 ++ 11 files changed, 424 insertions(+), 4 deletions(-) diff --git a/compiler/optimizer/ir/graph_checker.cpp b/compiler/optimizer/ir/graph_checker.cpp index 7f87292c5..dfb35b844 100644 --- a/compiler/optimizer/ir/graph_checker.cpp +++ b/compiler/optimizer/ir/graph_checker.cpp @@ -22,6 +22,7 @@ #include "optimizer/analysis/loop_analyzer.h" #include "optimizer/ir/analysis.h" #include "optimizer/ir/datatype.h" +#include "optimizer/ir/inst.h" #include "optimizer/optimizations/cleanup.h" #include "inst_checker_gen.h" @@ -301,7 +302,9 @@ void GraphChecker::CheckDataFlow(BasicBlock *block) ASSERT_EXT_PRINT(*block->AllInstsSafeReverse().begin() == inst, "Last block instruction isn't correct"); } // Some Inst with reference type must have no_hoist flags. - if ((inst->IsMovableObject() && inst->GetOpcode() != Opcode::CastValueToAnyType) || inst->IsCheck()) { + if ((inst->IsMovableObject() && inst->GetOpcode() != Opcode::CastValueToAnyType && + inst->GetOpcode() != Opcode::FunctionImmediate && inst->GetOpcode() != Opcode::LoadObjFromConst) || + inst->IsCheck()) { ASSERT_EXT(inst->IsNotHoistable()); } diff --git a/compiler/optimizer/ir/inst.cpp b/compiler/optimizer/ir/inst.cpp index f387327bc..393e8e6d7 100644 --- a/compiler/optimizer/ir/inst.cpp +++ b/compiler/optimizer/ir/inst.cpp @@ -237,6 +237,16 @@ void ClassImmediateInst::SetVnObject(VnObject *vn_obj) vn_obj->Add(reinterpret_cast(GetClass())); } +void LoadObjFromConstInst::SetVnObject(VnObject *vn_obj) +{ + vn_obj->Add(static_cast(GetObjPtr())); +} + +void FunctionImmediateInst::SetVnObject(VnObject *vn_obj) +{ + vn_obj->Add(static_cast(GetFunctionPtr())); +} + bool CastInst::IsDynamicCast() const { return DataType::IsFloatType(GetInputType(0U)) && DataType::GetCommonType(GetType()) == DataType::INT64 && @@ -646,7 +656,7 @@ bool Inst::IsAccWrite() const bool Inst::IsReferenceOrAny() const { if (GetType() == DataType::ANY) { - if (opcode_ == Opcode::GetAnyTypeName) { + if (opcode_ == Opcode::GetAnyTypeName || opcode_ == Opcode::FunctionImmediate) { return true; } auto any_type = GetAnyType(); diff --git a/compiler/optimizer/ir/inst.h b/compiler/optimizer/ir/inst.h index 244c84017..f2942497b 100644 --- a/compiler/optimizer/ir/inst.h +++ b/compiler/optimizer/ir/inst.h @@ -5658,6 +5658,13 @@ public: return function_ptr_; } + void SetFunctionPtr(uintptr_t ptr) + { + function_ptr_ = ptr; + } + + void SetVnObject(VnObject *vn_obj) override; + private: uintptr_t function_ptr_ {0}; }; @@ -5689,6 +5696,13 @@ public: return object_ptr_; } + void SetObjPtr(uintptr_t ptr) + { + object_ptr_ = ptr; + } + + void SetVnObject(VnObject *vn_obj) override; + private: uintptr_t object_ptr_ {0}; }; diff --git a/compiler/optimizer/ir/instructions.yaml b/compiler/optimizer/ir/instructions.yaml index ff29bc026..bb2affbcd 100644 --- a/compiler/optimizer/ir/instructions.yaml +++ b/compiler/optimizer/ir/instructions.yaml @@ -567,14 +567,14 @@ instructions: - opcode: FunctionImmediate base: FunctionImmediateInst signature: [d-any] - flags: [load, no_cse, ifcvt, no_hoist] + flags: [load, ifcvt] description: >- Get function from the immediate. - opcode: LoadObjFromConst base: LoadObjFromConstInst signature: [d-ref-any] - flags: [load, no_cse, ifcvt, no_hoist] + flags: [load, ifcvt] description: >- Load object from handle/constant memory. diff --git a/compiler/optimizer/ir/ir_constructor.h b/compiler/optimizer/ir/ir_constructor.h index 6261a30a9..39a971b67 100644 --- a/compiler/optimizer/ir/ir_constructor.h +++ b/compiler/optimizer/ir/ir_constructor.h @@ -1003,6 +1003,22 @@ public: return *this; } + IrConstructor &SetPtr(uintptr_t ptr) + { + auto *inst = CurrentInst(); + switch (inst->GetOpcode()) { + case Opcode::LoadObjFromConst: + inst->CastToLoadObjFromConst()->SetObjPtr(ptr); + break; + case Opcode::FunctionImmediate: + inst->CastToFunctionImmediate()->SetFunctionPtr(ptr); + break; + default: + UNREACHABLE(); + } + return *this; + } + IrConstructor &SetAccessMode(DynObjectAccessMode mode) { auto *inst = CurrentInst(); diff --git a/compiler/optimizer/optimizations/licm.cpp b/compiler/optimizer/optimizations/licm.cpp index 29b199846..5415018de 100644 --- a/compiler/optimizer/optimizations/licm.cpp +++ b/compiler/optimizer/optimizations/licm.cpp @@ -18,6 +18,7 @@ #include "optimizer/analysis/bounds_analysis.h" #include "optimizer/analysis/dominators_tree.h" #include "optimizer/analysis/loop_analyzer.h" +#include "optimizer/ir/inst.h" #include "optimizer/optimizations/licm.h" #include "optimizer/ir/basicblock.h" #include "optimizer/ir/analysis.h" @@ -159,6 +160,7 @@ void Licm::VisitLoop(Loop *loop) Inst *last_inst = pre_header->GetLastInst(); // Move instructions for (auto inst : hoistable_instructions_) { + Inst *target = nullptr; if (inst->IsResolver()) { auto ss = FindSaveStateForResolver(inst, pre_header); if (ss != nullptr) { @@ -170,6 +172,15 @@ void Licm::VisitLoop(Loop *loop) } else { continue; } + } else if (inst->IsMovableObject()) { + target = inst->GetPrev(); + if (target == nullptr) { + target = inst->GetNext(); + } + if (target == nullptr) { + target = GetGraph()->CreateInstNOP(); + inst->GetBasicBlock()->InsertAfter(target, inst); + } } inst->GetBasicBlock()->EraseInst(inst); if (last_inst == nullptr || last_inst->IsPhi()) { @@ -180,6 +191,9 @@ void Licm::VisitLoop(Loop *loop) } GetGraph()->GetEventWriter().EventLicm(inst->GetId(), inst->GetPc(), loop->GetId(), pre_header->GetLoop()->GetId()); + if (inst->IsMovableObject()) { + ssb_.SearchAndCreateMissingObjInSaveState(GetGraph(), inst, target); + } last_inst = inst; } } diff --git a/compiler/optimizer/optimizations/licm.h b/compiler/optimizer/optimizations/licm.h index e6e9bb721..fbc888d86 100644 --- a/compiler/optimizer/optimizations/licm.h +++ b/compiler/optimizer/optimizations/licm.h @@ -19,6 +19,7 @@ #include "optimizer/ir/graph.h" #include "optimizer/pass.h" #include "compiler_options.h" +#include "optimizer/ir/analysis.h" namespace panda::compiler { class Licm : public Optimization { @@ -58,6 +59,7 @@ private: uint32_t hoisted_inst_count_ {0}; Marker marker_loop_exit_ {UNDEF_MARKER}; Marker marker_hoist_inst_ {UNDEF_MARKER}; + SaveStateBridgesBuilder ssb_; InstVector hoistable_instructions_; }; } // namespace panda::compiler diff --git a/compiler/tests/licm_test.cpp b/compiler/tests/licm_test.cpp index 7b6d34d45..534ea4ea5 100644 --- a/compiler/tests/licm_test.cpp +++ b/compiler/tests/licm_test.cpp @@ -778,6 +778,122 @@ TEST_F(LicmTest, ClassImmediate) ASSERT_TRUE(graph->RunPass(HOST_LIMIT)); ASSERT_TRUE(GraphComparator().Compare(graph, graph1)); } + +TEST_F(LicmTest, LoadObjFromConst) +{ + auto obj1 = static_cast(1); + auto graph = CreateEmptyGraph(); + GRAPH(graph) + { + CONSTANT(40, 0xa); + CONSTANT(41, 0xb); + BASIC_BLOCK(2, 5) {} + BASIC_BLOCK(5, 4, 3) + { + INST(13, Opcode::Compare).b().SrcType(DataType::INT32).CC(CC_GE).Inputs(40, 41); + INST(14, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(13); + } + BASIC_BLOCK(4, 5) + { + INST(2, Opcode::LoadObjFromConst).ref().SetPtr(obj1); + INST(20, Opcode::SaveState).NoVregs(); + INST(15, Opcode::CallStatic).b().InputsAutoType(2, 20); + } + BASIC_BLOCK(3, -1) + { + INST(17, Opcode::ReturnVoid); + } + } + auto graph1 = CreateEmptyGraph(); + GRAPH(graph1) + { + CONSTANT(40, 0xa); + CONSTANT(41, 0xb); + BASIC_BLOCK(2, 5) + { + INST(13, Opcode::Compare).b().SrcType(DataType::INT32).CC(CC_GE).Inputs(40, 41); + INST(2, Opcode::LoadObjFromConst).ref().SetPtr(obj1); + } + BASIC_BLOCK(5, 4, 3) + { + INST(14, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(13); + } + BASIC_BLOCK(4, 5) + { + INST(20, Opcode::SaveState).Inputs(2).SrcVregs({VirtualRegister::BRIDGE}); + INST(15, Opcode::CallStatic).b().InputsAutoType(2, 20); + } + BASIC_BLOCK(3, -1) + { + INST(17, Opcode::ReturnVoid); + } + } + GraphChecker(graph).Check(); + ASSERT_TRUE(graph->RunPass(HOST_LIMIT)); + ASSERT_TRUE(GraphComparator().Compare(graph, graph1)); +} + +TEST_F(LicmTest, FunctionImmediate) +{ + auto fptr = static_cast(1); + auto graph = CreateEmptyGraph(); + graph->SetDynamicMethod(); +#ifndef NDEBUG + graph->SetDynUnitTestFlag(); +#endif + GRAPH(graph) + { + CONSTANT(40, 0xa); + CONSTANT(41, 0xb); + BASIC_BLOCK(2, 5) {} + BASIC_BLOCK(5, 4, 3) + { + INST(13, Opcode::Compare).b().SrcType(DataType::INT32).CC(CC_GE).Inputs(40, 41); + INST(14, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(13); + } + BASIC_BLOCK(4, 5) + { + INST(2, Opcode::FunctionImmediate).any().SetPtr(fptr); + INST(20, Opcode::SaveState).NoVregs(); + INST(15, Opcode::CallStatic).b().InputsAutoType(2, 20); + } + BASIC_BLOCK(3, -1) + { + INST(17, Opcode::ReturnVoid); + } + } + auto graph1 = CreateEmptyGraph(); + graph1->SetDynamicMethod(); +#ifndef NDEBUG + graph1->SetDynUnitTestFlag(); +#endif + GRAPH(graph1) + { + CONSTANT(40, 0xa); + CONSTANT(41, 0xb); + BASIC_BLOCK(2, 5) + { + INST(13, Opcode::Compare).b().SrcType(DataType::INT32).CC(CC_GE).Inputs(40, 41); + INST(2, Opcode::FunctionImmediate).any().SetPtr(fptr); + } + BASIC_BLOCK(5, 4, 3) + { + INST(14, Opcode::IfImm).SrcType(DataType::BOOL).CC(CC_NE).Imm(0).Inputs(13); + } + BASIC_BLOCK(4, 5) + { + INST(20, Opcode::SaveState).Inputs(2).SrcVregs({VirtualRegister::BRIDGE}); + INST(15, Opcode::CallStatic).b().InputsAutoType(2, 20); + } + BASIC_BLOCK(3, -1) + { + INST(17, Opcode::ReturnVoid); + } + } + GraphChecker(graph).Check(); + ASSERT_TRUE(graph->RunPass(HOST_LIMIT)); + ASSERT_TRUE(GraphComparator().Compare(graph, graph1)); +} // NOLINTEND(readability-magic-numbers) } // namespace panda::compiler diff --git a/compiler/tests/vn_test.cpp b/compiler/tests/vn_test.cpp index 2fc391bf0..d1e06798b 100644 --- a/compiler/tests/vn_test.cpp +++ b/compiler/tests/vn_test.cpp @@ -1454,6 +1454,233 @@ TEST_F(VNTest, ClassImmediateCseNotApplied) ASSERT_TRUE(GraphComparator().Compare(GetGraph(), clone)); } +TEST_F(VNTest, LoadObjFromConstCseApplied) +{ + auto obj = static_cast(1); + auto graph1 = CreateGraphWithDefaultRuntime(); + GRAPH(graph1) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::LoadObjFromConst).ref().SetPtr(obj); + INST(2, Opcode::LoadObjFromConst).ref().SetPtr(obj); + INST(3, Opcode::ReturnVoid); + } + } + GraphChecker(graph1).Check(); + ASSERT_TRUE(graph1->RunPass()); + + auto graph2 = CreateGraphWithDefaultRuntime(); + GRAPH(graph2) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::LoadObjFromConst).ref().SetPtr(obj); + INST(20, Opcode::SaveState).NoVregs(); + INST(14, Opcode::CallStatic).b().InputsAutoType(1, 20); + INST(2, Opcode::LoadObjFromConst).ref().SetPtr(obj); + INST(3, Opcode::Return).ref().Inputs(2); + } + } + GraphChecker(graph2).Check(); + ASSERT_TRUE(graph2->RunPass()); + ASSERT_TRUE(graph2->RunPass()); + auto graph3 = CreateGraphWithDefaultRuntime(); + GRAPH(graph3) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::LoadObjFromConst).ref().SetPtr(obj); + INST(20, Opcode::SaveState).Inputs(1).SrcVregs({VirtualRegister::BRIDGE}); + INST(14, Opcode::CallStatic).b().InputsAutoType(1, 20); + INST(3, Opcode::Return).ref().Inputs(1); + } + } + ASSERT_TRUE(GraphComparator().Compare(graph2, graph3)); +} + +TEST_F(VNTest, FunctionImmediateCseApplied) +{ + auto fptr = static_cast(1); + auto graph1 = CreateGraphWithDefaultRuntime(); + graph1->SetDynamicMethod(); +#ifndef NDEBUG + graph1->SetDynUnitTestFlag(); +#endif + GRAPH(graph1) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::FunctionImmediate).any().SetPtr(fptr); + INST(2, Opcode::FunctionImmediate).any().SetPtr(fptr); + INST(3, Opcode::ReturnVoid); + } + } + GraphChecker(graph1).Check(); + ASSERT_TRUE(graph1->RunPass()); + auto graph2 = CreateGraphWithDefaultRuntime(); + graph2->SetDynamicMethod(); +#ifndef NDEBUG + graph2->SetDynUnitTestFlag(); +#endif + GRAPH(graph2) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::FunctionImmediate).any().SetPtr(fptr); + INST(20, Opcode::SaveState).NoVregs(); + INST(14, Opcode::CallStatic).b().InputsAutoType(1, 20); + INST(2, Opcode::FunctionImmediate).any().SetPtr(fptr); + INST(3, Opcode::ReturnVoid); + } + } + GraphChecker(graph2).Check(); + ASSERT_TRUE(graph2->RunPass()); + ASSERT_TRUE(graph2->RunPass()); + auto graph3 = CreateGraphWithDefaultRuntime(); + graph3->SetDynamicMethod(); +#ifndef NDEBUG + graph3->SetDynUnitTestFlag(); +#endif + GRAPH(graph3) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::FunctionImmediate).any().SetPtr(fptr); + INST(20, Opcode::SaveState).Inputs(1).SrcVregs({VirtualRegister::BRIDGE}); + INST(14, Opcode::CallStatic).b().InputsAutoType(1, 20); + INST(3, Opcode::ReturnVoid); + } + } + ASSERT_TRUE(GraphComparator().Compare(graph2, graph3)); +} + +TEST_F(VNTest, LoadObjFromConstCseNotApplied) +{ + auto obj1 = static_cast(1); + auto obj2 = static_cast(2); + GRAPH(GetGraph()) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::LoadObjFromConst).ref().SetPtr(obj1); + INST(2, Opcode::LoadObjFromConst).ref().SetPtr(obj2); + INST(3, Opcode::ReturnVoid); + } + } + auto clone = GraphCloner(GetGraph(), GetGraph()->GetAllocator(), GetGraph()->GetLocalAllocator()).CloneGraph(); + GraphChecker(GetGraph()).Check(); + ASSERT_FALSE(GetGraph()->RunPass()); + ASSERT_TRUE(GraphComparator().Compare(GetGraph(), clone)); +} + +TEST_F(VNTest, FunctionImmediateCseNotApplied) +{ + auto fptr1 = static_cast(1); + auto fptr2 = static_cast(2); + auto graph = GetGraph(); + graph->SetDynamicMethod(); +#ifndef NDEBUG + graph->SetDynUnitTestFlag(); +#endif + GRAPH(graph) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::FunctionImmediate).any().SetPtr(fptr1); + INST(2, Opcode::FunctionImmediate).any().SetPtr(fptr2); + INST(3, Opcode::ReturnVoid); + } + } + auto clone = GraphCloner(graph, graph->GetAllocator(), GetGraph()->GetLocalAllocator()).CloneGraph(); + GraphChecker(graph).Check(); + ASSERT_FALSE(graph->RunPass()); + ASSERT_TRUE(GraphComparator().Compare(graph, clone)); +} + +TEST_F(VNTest, LoadObjFromConstBridgeCreator) +{ + auto obj = static_cast(1); + auto graph = CreateGraphWithDefaultRuntime(); + GRAPH(graph) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::LoadObjFromConst).ref().SetPtr(obj); + INST(2, Opcode::SaveState); + INST(3, Opcode::CallStatic).v0id().InputsAutoType(1, 2); + INST(4, Opcode::LoadObjFromConst).ref().SetPtr(obj); + INST(5, Opcode::SaveState); + INST(6, Opcode::CallStatic).v0id().InputsAutoType(4, 5); + INST(7, Opcode::ReturnVoid); + } + } + GraphChecker(graph).Check(); + ASSERT_TRUE(graph->RunPass()); + ASSERT_TRUE(graph->RunPass()); + + auto graph_after = CreateGraphWithDefaultRuntime(); + GRAPH(graph_after) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::LoadObjFromConst).ref().SetPtr(obj); + INST(2, Opcode::SaveState).Inputs(1).SrcVregs({VirtualRegister::BRIDGE}); + INST(3, Opcode::CallStatic).v0id().InputsAutoType(1, 2); + INST(5, Opcode::SaveState); + INST(6, Opcode::CallStatic).v0id().InputsAutoType(1, 5); + INST(7, Opcode::ReturnVoid); + } + } + GraphChecker(graph_after).Check(); + ASSERT_TRUE(GraphComparator().Compare(graph, graph_after)); +} + +TEST_F(VNTest, FunctionImmediateBridgeCreator) +{ + auto fptr1 = static_cast(1); + auto graph = CreateGraphWithDefaultRuntime(); + graph->SetDynamicMethod(); +#ifndef NDEBUG + graph->SetDynUnitTestFlag(); +#endif + GRAPH(graph) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::FunctionImmediate).any().SetPtr(fptr1); + INST(2, Opcode::SaveState); + INST(3, Opcode::CallStatic).v0id().InputsAutoType(1, 2); + INST(4, Opcode::FunctionImmediate).any().SetPtr(fptr1); + INST(5, Opcode::SaveState); + INST(6, Opcode::CallStatic).v0id().InputsAutoType(4, 5); + INST(7, Opcode::ReturnVoid); + } + } + GraphChecker(graph).Check(); + ASSERT_TRUE(graph->RunPass()); + ASSERT_TRUE(graph->RunPass()); + + auto graph_after = CreateGraphWithDefaultRuntime(); + graph_after->SetDynamicMethod(); +#ifndef NDEBUG + graph_after->SetDynUnitTestFlag(); +#endif + GRAPH(graph_after) + { + BASIC_BLOCK(2, -1) + { + INST(1, Opcode::FunctionImmediate).any().SetPtr(fptr1); + INST(2, Opcode::SaveState).Inputs(1).SrcVregs({VirtualRegister::BRIDGE}); + INST(3, Opcode::CallStatic).v0id().InputsAutoType(1, 2); + INST(5, Opcode::SaveState); + INST(6, Opcode::CallStatic).v0id().InputsAutoType(1, 5); + INST(7, Opcode::ReturnVoid); + } + } + GraphChecker(graph_after).Check(); + ASSERT_TRUE(GraphComparator().Compare(graph, graph_after)); +} // NOLINTEND(readability-magic-numbers) } // namespace panda::compiler diff --git a/tests/checked/README.md b/tests/checked/README.md index b39d3c0f5..e75fadae0 100644 --- a/tests/checked/README.md +++ b/tests/checked/README.md @@ -40,5 +40,6 @@ Each command is a valid code in the `ruby` language. * **ASM_INST** (inst: pattern) select a specified instruction in disasm file, next "ASM*" checks will be applied only for this instruction's code. * **ASM/ASM_NEXT/ASM_NOT/ASM_NEXT_NOT** (inst: pattern) same as other similar checks, but search only in a current disasm scope, defined by `ASM_METHOD` or `ASM_INST`. If none of these checks were specified, then search will be applied in the whole disasm file. +* **IN_BLOCK** (block: pattern) limits the search for instructions to one block. The block is defined by lines "props: ..." and "succs: ...". The search pattern is found in the first line "props: rest\_of\_line\_for\_matching". You can define only one block for searching and you can't return to the search in the entire method after in the currently analized compiler pass. *pattern* can be a string(surrounded by quotes) or regex(surrounded by slashes): string - `"SearchPattern"`, regex - `/SearchPattern/`. diff --git a/tests/checked/checker.rb b/tests/checked/checker.rb index 0134de958..dc49a5357 100755 --- a/tests/checked/checker.rb +++ b/tests/checked/checker.rb @@ -98,6 +98,17 @@ class SearchScope @current_index = 0 end + def find_block(match) + @lines = @lines.drop(@current_index) + @current_index = 0 + find(match) + @lines = @lines.drop(@current_index - 1) + @current_index = 0 + find(/succs:/) + @lines = @lines.slice(0, @current_index) + @current_index = 0 + end + def self.from_file(fname, name) SearchScope.new(File.readlines(fname), name) end @@ -424,6 +435,12 @@ class Checker raise_error "IR_COUNT mismatch: expected=#{count}, real=#{real_count}" unless real_count == count end + def IN_BLOCK(match) + return if @options.release + + @ir_scope.find_block("prop: " + match) + end + def LLVM_METHOD(match) return if @options.release -- Gitee