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