• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "libabckit/include/cpp/abckit_cpp.h"
17 
18 #include "helpers/helpers.h"
19 #include "helpers/helpers_runtime.h"
20 #include "libabckit/src/logger.h"
21 
22 #include <gtest/gtest.h>
23 
24 #include <sstream>
25 #include <string>
26 #include <optional>
27 #include <unordered_map>
28 
29 namespace {
30 
31 class GTestAssertErrorHandler final : public abckit::IErrorHandler {
32 public:
HandleError(abckit::Exception && err)33     void HandleError(abckit::Exception &&err) override
34     {
35         EXPECT_TRUE(false) << "Abckit expection raised: " << err.what();
36     }
37 };
38 
39 struct ConstantMeta {
40     std::string_view modulePath;
41     std::string_view objName;
42     std::string_view fieldName;
43     bool fieldValue;
44 };
45 
46 using ConstantMetaCollection = std::vector<ConstantMeta>;
47 using ConstantMetaIterator = typename ConstantMetaCollection::const_iterator;
48 using SuspectsType = std::unordered_multimap<abckit::core::ImportDescriptor, ConstantMetaIterator>;
49 
50 using InstructionCallback = std::function<bool(abckit::Instruction)>;
51 using FunctionCallback = std::function<bool(abckit::core::Function)>;
52 using ClassCallback = std::function<bool(abckit::core::Class)>;
53 using NamespaceCallback = std::function<bool(abckit::core::Namespace)>;
54 
EnumerateAllMethods(const abckit::core::Module & mod,const FunctionCallback & fnUserCallback)55 void EnumerateAllMethods(const abckit::core::Module &mod, const FunctionCallback &fnUserCallback)
56 {
57     ClassCallback clsCallback;
58 
59     FunctionCallback fnCallback = [&](const abckit::core::Function &fun) -> bool {
60         if (!fnUserCallback(fun)) {
61             return false;
62         }
63         fun.EnumerateNestedFunctions(fnCallback);
64         fun.EnumerateNestedClasses(clsCallback);
65         return true;
66     };
67 
68     clsCallback = [&](const abckit::core::Class &cls) -> bool {
69         cls.EnumerateMethods(fnCallback);
70         return true;
71     };
72 
73     NamespaceCallback nsCallback = [&](const abckit::core::Namespace &ns) -> bool {
74         ns.EnumerateNamespaces(nsCallback);
75         ns.EnumerateClasses(clsCallback);
76         ns.EnumerateTopLevelFunctions(fnCallback);
77         return true;
78     };
79 
80     mod.EnumerateNamespaces(nsCallback);
81     mod.EnumerateClasses(clsCallback);
82     mod.EnumerateTopLevelFunctions(fnCallback);
83 }
84 
EnumerateGraphInsts(const abckit::Graph & graph,const InstructionCallback & cb)85 inline void EnumerateGraphInsts(const abckit::Graph &graph, const InstructionCallback &cb)
86 {
87     bool askedContinue = true;
88     graph.EnumerateBasicBlocksRpo([&](const abckit::BasicBlock &bb) -> bool {
89         for (auto inst = bb.GetFirstInst(); !!inst && askedContinue; inst = inst.GetNext()) {
90             askedContinue = cb(inst);
91         }
92         return true;
93     });
94 }
95 
GraphInstsFindIf(const abckit::Graph & graph,const InstructionCallback & cb)96 std::optional<abckit::Instruction> GraphInstsFindIf(const abckit::Graph &graph, const InstructionCallback &cb)
97 {
98     std::optional<abckit::Instruction> maybeInst;
99     EnumerateGraphInsts(graph, [&](const abckit::Instruction &inst) -> bool {
100         if (!maybeInst && cb(inst)) {
101             maybeInst = inst;
102         }
103         return !maybeInst;
104     });
105     return maybeInst;
106 }
107 
GetSuspects(const abckit::core::Module & mod,const ConstantMetaCollection & constants)108 SuspectsType GetSuspects(const abckit::core::Module &mod, const ConstantMetaCollection &constants)
109 {
110     SuspectsType suspects;
111     mod.EnumerateImports([&](const abckit::core::ImportDescriptor &idesc) {
112         auto importName = idesc.GetName();
113         auto modulePath = idesc.GetImportedModule().GetName();
114 
115         for (auto itConst = constants.begin(); itConst != constants.end(); ++itConst) {
116             if (std::tie(itConst->modulePath, itConst->objName) != std::tie(modulePath, importName)) {
117                 continue;
118             }
119             suspects.emplace(idesc, itConst);
120         }
121         return true;
122     });
123     return suspects;
124 }
125 
GetConstantMetaIterator(const abckit::Instruction & inst,const ConstantMetaCollection & constants,const SuspectsType & suspects)126 ConstantMetaIterator GetConstantMetaIterator(const abckit::Instruction &inst, const ConstantMetaCollection &constants,
127                                              const SuspectsType &suspects)
128 {
129     auto end = constants.end();
130 
131     if (inst.GetGraph()->DynIsa().GetOpcode(inst) != ABCKIT_ISA_API_DYNAMIC_OPCODE_LDOBJBYNAME) {
132         return end;
133     }
134     auto ldExternalModuleVar = inst.GetInput(0);
135     if (ldExternalModuleVar.GetGraph()->DynIsa().GetOpcode(ldExternalModuleVar) !=
136         ABCKIT_ISA_API_DYNAMIC_OPCODE_LDEXTERNALMODULEVAR) {
137         return end;
138     }
139 
140     auto idesc = ldExternalModuleVar.GetGraph()->DynIsa().GetImportDescriptor(ldExternalModuleVar);
141     auto range = suspects.equal_range(idesc);
142     if (range.first == range.second) {
143         return end;
144     }
145 
146     auto propName = inst.GetString();  // "isDebug"
147     for (auto it = range.first; it != range.second; ++it) {
148         const ConstantMetaIterator &itCM = it->second;
149         if (itCM->fieldName == propName) {
150             return itCM;
151         }
152     }
153 
154     return end;
155 }
156 
ReplaceUsers(const abckit::Instruction & oldInst,const abckit::Instruction & newInst)157 void ReplaceUsers(const abckit::Instruction &oldInst, const abckit::Instruction &newInst)
158 {
159     oldInst.VisitUsers([&](const abckit::Instruction &user) -> bool {
160         size_t inputCount = user.GetInputCount();
161         for (size_t idx = 0; idx < inputCount; ++idx) {
162             if (user.GetInput(idx) == oldInst) {
163                 user.SetInput(idx, newInst);
164             }
165         }
166         return true;
167     });
168 }
169 
ReplaceLdObjByNameWithBoolean(abckit::Graph & graph,abckit::Instruction ldObjByName,const ConstantMeta & constMeta)170 void ReplaceLdObjByNameWithBoolean(abckit::Graph &graph, abckit::Instruction ldObjByName, const ConstantMeta &constMeta)
171 {
172     const bool fieldVal = constMeta.fieldValue;
173     abckit::BasicBlock bb = ldObjByName.GetBasicBlock();
174     // abckit::Graph& graph = *const_cast<abckit::Graph*>(bb.GetGraph());
175     auto value = fieldVal ? graph.DynIsa().CreateLdtrue() : graph.DynIsa().CreateLdfalse();
176     value.InsertAfter(ldObjByName);
177     ReplaceUsers(ldObjByName, value);
178 
179     auto ldExternalModuleVar = ldObjByName.GetInput(0);
180     bool isRemovable = true;
181     std::vector<abckit::Instruction> removableChecks;
182     ldExternalModuleVar.VisitUsers([&](abckit::Instruction user) -> bool {
183         if (user != ldObjByName) {
184             if (user.GetGraph()->DynIsa().GetOpcode(user) !=
185                 ABCKIT_ISA_API_DYNAMIC_OPCODE_THROW_UNDEFINEDIFHOLEWITHNAME) {
186                 isRemovable = false;
187             } else {
188                 removableChecks.push_back(std::move(user));
189             }
190         }
191         return true;
192     });
193 
194     if (isRemovable) {
195         for (auto &inst : removableChecks) {
196             inst.Remove();
197         }
198         ldExternalModuleVar.Remove();
199     }
200 
201     ldObjByName.Remove();
202 }
203 
ReplaceModuleVarByConstant(abckit::Graph & graph,const ConstantMetaCollection & constants,const SuspectsType & suspects)204 bool ReplaceModuleVarByConstant(abckit::Graph &graph, const ConstantMetaCollection &constants,
205                                 const SuspectsType &suspects)
206 {
207     // find the following pattern:
208     // ...
209     // 1. ldExternalModuleVar (importdescriptor)
210     // 2. ldObjByName v1, "isDebug"
211     //
212     // and replace it by
213     // 3. ldtrue
214 
215     std::unordered_map<abckit::Instruction, ConstantMetaIterator> moduleVars;
216     EnumerateGraphInsts(graph, [&](const abckit::Instruction &inst) {
217         if (auto itCM = GetConstantMetaIterator(inst, constants, suspects); itCM != constants.end()) {
218             moduleVars.emplace(inst, itCM);
219         }
220         return true;
221     });
222 
223     for (auto &[ldObjByName, itCM] : moduleVars) {
224         EXPECT_TRUE(!!ldObjByName);
225         EXPECT_TRUE(itCM != constants.end());
226         ReplaceLdObjByNameWithBoolean(graph, ldObjByName, *itCM);
227     }
228 
229     return !moduleVars.empty();
230 }
231 
GetInstAsBool(const abckit::Instruction & inst)232 bool GetInstAsBool(const abckit::Instruction &inst)
233 {
234     switch (inst.GetGraph()->DynIsa().GetOpcode(inst)) {
235         case ABCKIT_ISA_API_DYNAMIC_OPCODE_CONSTANT:
236             return inst.GetConstantValueI64() != 0;
237         case ABCKIT_ISA_API_DYNAMIC_OPCODE_LDTRUE:
238         case ABCKIT_ISA_API_DYNAMIC_OPCODE_ISTRUE:
239         case ABCKIT_ISA_API_DYNAMIC_OPCODE_CALLRUNTIME_ISTRUE:
240             return true;
241         case ABCKIT_ISA_API_DYNAMIC_OPCODE_LDFALSE:
242         case ABCKIT_ISA_API_DYNAMIC_OPCODE_ISFALSE:
243         case ABCKIT_ISA_API_DYNAMIC_OPCODE_CALLRUNTIME_ISFALSE:
244             return false;
245         default:
246             EXPECT_TRUE(false);  // UNREACHABLE
247     }
248     return false;
249 }
250 
CanBeRemoved(const abckit::Instruction & inst)251 bool CanBeRemoved(const abckit::Instruction &inst)
252 {
253     switch (inst.GetGraph()->DynIsa().GetOpcode(inst)) {
254         case ABCKIT_ISA_API_DYNAMIC_OPCODE_CONSTANT:
255         case ABCKIT_ISA_API_DYNAMIC_OPCODE_LDTRUE:
256         case ABCKIT_ISA_API_DYNAMIC_OPCODE_ISTRUE:
257         case ABCKIT_ISA_API_DYNAMIC_OPCODE_LDFALSE:
258         case ABCKIT_ISA_API_DYNAMIC_OPCODE_ISFALSE:
259             return true;
260         default:
261             return false;
262     }
263 }
264 
RemoveUnusedInsts(abckit::Graph & graph)265 bool RemoveUnusedInsts(abckit::Graph &graph)
266 {
267     bool graphUpdated = false;
268     bool hasUnprocessedInsts = true;
269 
270     while (hasUnprocessedInsts) {
271         std::vector<abckit::Instruction> removables;
272         EnumerateGraphInsts(graph, [&](abckit::Instruction inst) {
273             if (inst.GetUserCount() == 0 && CanBeRemoved(inst)) {
274                 removables.push_back(std::move(inst));
275             }
276             return true;
277         });
278 
279         if (removables.empty()) {
280             hasUnprocessedInsts = false;
281             continue;
282         }
283 
284         EXPECT_TRUE(!removables.empty());
285         for (auto &inst : removables) {
286             inst.Remove();
287         }
288         graphUpdated = true;
289     }
290 
291     return graphUpdated;
292 }
293 
LoweringConstantsSinglePass(abckit::Graph & graph)294 bool LoweringConstantsSinglePass(abckit::Graph &graph)
295 {
296     std::vector<abckit::Instruction> boolUsers;
297     auto fnInstCollectBoolUsers = [&boolUsers](const abckit::Instruction &user) -> bool {
298         auto userOp = user.GetGraph()->DynIsa().GetOpcode(user);
299         if (userOp == ABCKIT_ISA_API_DYNAMIC_OPCODE_ISTRUE || userOp == ABCKIT_ISA_API_DYNAMIC_OPCODE_ISFALSE ||
300             userOp == ABCKIT_ISA_API_DYNAMIC_OPCODE_CALLRUNTIME_ISTRUE ||
301             userOp == ABCKIT_ISA_API_DYNAMIC_OPCODE_CALLRUNTIME_ISFALSE) {
302             boolUsers.emplace_back(user);
303         }
304         return true;
305     };
306 
307     std::optional<abckit::Instruction> maybeLdBool = GraphInstsFindIf(graph, [&](const abckit::Instruction &inst) {
308         auto op = inst.GetGraph()->DynIsa().GetOpcode(inst);
309         if (op != ABCKIT_ISA_API_DYNAMIC_OPCODE_LDTRUE && op != ABCKIT_ISA_API_DYNAMIC_OPCODE_LDFALSE) {
310             return false;
311         }
312 
313         inst.VisitUsers(fnInstCollectBoolUsers);
314         return !boolUsers.empty();
315     });
316 
317     if (!maybeLdBool) {
318         return false;
319     }
320 
321     auto ldBoolVal = GetInstAsBool(*maybeLdBool);
322 
323     for (abckit::Instruction &isBool : boolUsers) {
324         auto isBoolVal = GetInstAsBool(isBool);
325         auto ldBool = ldBoolVal == isBoolVal ? graph.DynIsa().CreateLdtrue() : graph.DynIsa().CreateLdfalse();
326         ldBool.InsertAfter(isBool);
327         ReplaceUsers(isBool, ldBool);
328         isBool.Remove();
329     }
330 
331     return true;
332 }
333 
LoweringConstants(abckit::Graph & graph)334 bool LoweringConstants(abckit::Graph &graph)
335 {
336     bool graphUpdated = false;
337 
338     while (bool hasUpdates = LoweringConstantsSinglePass(graph)) {
339         graphUpdated |= hasUpdates;
340     };
341 
342     graphUpdated |= RemoveUnusedInsts(graph);
343     return graphUpdated;
344 }
345 
IsEliminatableIfInst(const abckit::Instruction & inst)346 bool IsEliminatableIfInst(const abckit::Instruction &inst)
347 {
348     if (inst.GetGraph()->DynIsa().GetOpcode(inst) != ABCKIT_ISA_API_DYNAMIC_OPCODE_IF || inst.GetInputCount() != 2U) {
349         return false;
350     }
351     auto ldBool = inst.GetInput(0);
352     auto op = ldBool.GetGraph()->DynIsa().GetOpcode(ldBool);
353     if (op != ABCKIT_ISA_API_DYNAMIC_OPCODE_LDTRUE && op != ABCKIT_ISA_API_DYNAMIC_OPCODE_LDFALSE) {
354         return false;
355     }
356 
357     auto constInst = inst.GetInput(1);
358     return constInst.GetGraph()->DynIsa().GetOpcode(constInst) == ABCKIT_ISA_API_DYNAMIC_OPCODE_CONSTANT;
359 }
360 
DeleteUnreachableBranch(abckit::Graph & graph,abckit::Instruction & ifInst)361 void DeleteUnreachableBranch(abckit::Graph &graph, abckit::Instruction &ifInst)
362 {
363     auto ldBool = ifInst.GetInput(0);     // 0: ldtrue or ldfalse
364     auto constInst = ifInst.GetInput(1);  // 1: ConstantInt
365     const bool valLhs = GetInstAsBool(ldBool);
366     const bool valRhs = GetInstAsBool(constInst);
367 
368     auto conditionCode = ifInst.GetGraph()->DynIsa().GetConditionCode(ifInst);
369     EXPECT_TRUE(conditionCode == ABCKIT_ISA_API_DYNAMIC_CONDITION_CODE_CC_EQ ||
370                 conditionCode == ABCKIT_ISA_API_DYNAMIC_CONDITION_CODE_CC_NE);
371     bool result = (conditionCode == ABCKIT_ISA_API_DYNAMIC_CONDITION_CODE_CC_EQ && valLhs == valRhs) ||
372                   (conditionCode == ABCKIT_ISA_API_DYNAMIC_CONDITION_CODE_CC_NE && valLhs != valRhs);
373 
374     auto bb = ifInst.GetBasicBlock();
375     // if true ==> delete false branch
376     ifInst.Remove();
377 
378     bb.EraseSuccBlock(result ? 1 : 0);
379     graph.RunPassRemoveUnreachableBlocks();
380 }
381 
EliminateBranchWithSuspect(abckit::core::Function & method,const ConstantMetaCollection & constants,const SuspectsType & suspects)382 void EliminateBranchWithSuspect(abckit::core::Function &method, const ConstantMetaCollection &constants,
383                                 const SuspectsType &suspects)
384 {
385     auto graph = method.CreateGraph();
386 
387     bool graphUpdated = ReplaceModuleVarByConstant(graph, constants, suspects);
388     std::optional<abckit::Instruction> maybeRemovable;
389     do {
390         if (maybeRemovable) {
391             DeleteUnreachableBranch(graph, *maybeRemovable);
392             graphUpdated = true;
393         }
394 
395         graphUpdated |= LoweringConstants(graph);
396 
397         maybeRemovable =
398             GraphInstsFindIf(graph, [&](const abckit::Instruction &inst) { return IsEliminatableIfInst(inst); });
399     } while (maybeRemovable);
400 
401     if (graphUpdated) {
402         method.SetGraph(graph);
403     }
404 }
405 
Run(abckit::File & file,const ConstantMetaCollection & constants)406 void Run(abckit::File &file, const ConstantMetaCollection &constants)
407 {
408     if (constants.empty()) {
409         return;
410     }
411 
412     file.EnumerateModules([&](const abckit::core::Module &mod) {
413         const auto suspects = GetSuspects(mod, constants);
414         if (!suspects.empty()) {
415             EnumerateAllMethods(mod, [&](abckit::core::Function method) {
416                 EliminateBranchWithSuspect(method, constants, suspects);
417                 return true;
418             });
419         }
420         return true;
421     });
422 }
423 
MethodHasBranch(const abckit::File & file,const std::string & moduleName,const std::string & methodName)424 bool MethodHasBranch(const abckit::File &file, const std::string &moduleName, const std::string &methodName)
425 {
426     std::optional<abckit::core::Function> foundMethod;
427 
428     file.EnumerateModules([&](const abckit::core::Module &mod) {
429         if (foundMethod || mod.GetName() != moduleName) {
430             return !foundMethod;
431         }
432 
433         EnumerateAllMethods(mod, [&](const abckit::core::Function &method) {
434             if (foundMethod || method.GetName() != methodName) {
435                 return !foundMethod;
436             }
437             foundMethod = method;
438             return !foundMethod;
439         });
440 
441         return !foundMethod;
442     });
443     EXPECT_TRUE(foundMethod.has_value());
444 
445     auto graph = foundMethod->CreateGraph();
446     auto maybeIfInst = GraphInstsFindIf(graph, [&](const abckit::Instruction &inst) {
447         return inst.GetGraph()->DynIsa().GetOpcode(inst) == ABCKIT_ISA_API_DYNAMIC_OPCODE_IF;
448     });
449     return maybeIfInst.has_value();
450 }
451 
452 }  // namespace
453 
454 namespace libabckit::test {
455 
456 static constexpr bool CONFIG_IS_DEBUG_ORIGIN_VALUE = false;
457 
458 /*
459  * @param value: the value of isDebug when we handle, it can be different from configIsDebugOriginValue
460  */
GetExpectOutput(bool value)461 static std::string GetExpectOutput(bool value)
462 {
463     std::stringstream expectOutput;
464     expectOutput << std::boolalpha;
465     expectOutput << "Config.isDebug = " << value << std::endl;
466     expectOutput << "myfoo: Config.isDebug is " << value << std::endl;
467     expectOutput << "Mybar.test1: Config.isDebug is " << value << std::endl;
468     expectOutput << "Mybar.test2: Config.isDebug is " << value << std::endl;
469     return expectOutput.str();
470 }
471 
GeneralBranchEliminatorTest(bool configIsDebugFinal)472 static void GeneralBranchEliminatorTest(bool configIsDebugFinal)
473 {
474     const std::string sandboxPath = ABCKIT_ABC_DIR "clean_scenarios/cpp_api/dynamic/branch_eliminator/";
475     const std::string inputPath = sandboxPath + "branch_eliminator.abc";
476     const std::string outputPath = sandboxPath + "branch_eliminator_modified.abc";
477 
478     abckit::File file(inputPath, std::make_unique<GTestAssertErrorHandler>());
479 
480     const auto origOutput = helpers::ExecuteDynamicAbc(inputPath, "branch_eliminator");
481     auto expectedOrigOutput = GetExpectOutput(CONFIG_IS_DEBUG_ORIGIN_VALUE);
482 
483     EXPECT_EQ(origOutput, expectedOrigOutput);
484     ASSERT_TRUE(MethodHasBranch(file, "modules/myfoo", "myfoo"));
485     ASSERT_TRUE(MethodHasBranch(file, "modules/mybar", "test1"));
486     ASSERT_TRUE(MethodHasBranch(file, "modules/mybar", "test2"));
487 
488     // Delete true branch
489     const ConstantMetaCollection constants = {{"modules/config", "Config", "isDebug", configIsDebugFinal}};
490     Run(file, constants);
491 
492     ASSERT_FALSE(MethodHasBranch(file, "modules/myfoo", "myfoo"));
493     ASSERT_FALSE(MethodHasBranch(file, "modules/mybar", "test1"));
494     ASSERT_FALSE(MethodHasBranch(file, "modules/mybar", "test2"));
495 
496     file.WriteAbc(outputPath);
497 
498     auto modOutput = helpers::ExecuteDynamicAbc(outputPath, "branch_eliminator");
499     auto expectedModOutput = GetExpectOutput(configIsDebugFinal);
500     EXPECT_EQ(modOutput, expectedModOutput);
501 }
502 
503 class AbckitScenarioCppTestClean : public ::testing::Test {};
504 
505 // Test: test-kind=scenario, abc-kind=ArkTS1, category=positive, extension=cpp
TEST_F(AbckitScenarioCppTestClean,LibAbcKitTestDynamicBranchEliminatorClean1)506 TEST_F(AbckitScenarioCppTestClean, LibAbcKitTestDynamicBranchEliminatorClean1)
507 {
508     GeneralBranchEliminatorTest(false);
509 }
510 
511 // Test: test-kind=scenario, abc-kind=ArkTS1, category=positive, extension=cpp
TEST_F(AbckitScenarioCppTestClean,LibAbcKitTestDynamicBranchEliminatorClean2)512 TEST_F(AbckitScenarioCppTestClean, LibAbcKitTestDynamicBranchEliminatorClean2)
513 {
514     GeneralBranchEliminatorTest(true);
515 }
516 
517 }  // namespace libabckit::test
518