1 /*
2 * Copyright (c) 2023-2025 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 "bytecodeopt_peepholes.h"
17
18 #include "libpandafile/bytecode_instruction-inl.h"
19
20 namespace ark::bytecodeopt {
RunImpl()21 bool BytecodeOptPeepholes::RunImpl()
22 {
23 VisitGraph();
24 return IsApplied();
25 }
26
FindCtorCall(Inst * newObject)27 CallInst *FindCtorCall(Inst *newObject)
28 {
29 auto *graph = newObject->GetBasicBlock()->GetGraph();
30 auto *adapter = graph->GetRuntime();
31 for (auto &user : newObject->GetUsers()) {
32 auto *inst = user.GetInst();
33 if (inst->GetOpcode() == Opcode::NullCheck) {
34 return FindCtorCall(inst);
35 }
36 if (inst->GetOpcode() != Opcode::CallStatic) {
37 continue;
38 }
39 auto call = inst->CastToCallStatic();
40 auto callFirstArg = call->GetInput(0U).GetInst();
41 if (callFirstArg->GetOpcode() == Opcode::NewObject) {
42 auto newObjectAsArg = callFirstArg->CastToNewObject();
43 if (newObjectAsArg != newObject) {
44 continue;
45 }
46 }
47 if (adapter->IsConstructor(call->GetCallMethod(), graph->GetLanguage())) {
48 return call;
49 }
50 }
51
52 return nullptr;
53 }
54
CreateInitObject(compiler::GraphVisitor * v,compiler::ClassInst * load,const CallInst * callInit)55 CallInst *CreateInitObject(compiler::GraphVisitor *v, compiler::ClassInst *load, const CallInst *callInit)
56 {
57 auto *graph = static_cast<BytecodeOptPeepholes *>(v)->GetGraph();
58 auto *initObject = static_cast<CallInst *>(graph->CreateInst(compiler::Opcode::InitObject));
59 initObject->SetType(compiler::DataType::REFERENCE);
60
61 auto inputTypesCount = callInit->GetInputsCount();
62 initObject->AllocateInputTypes(graph->GetAllocator(), inputTypesCount);
63 initObject->AppendInput(load, compiler::DataType::REFERENCE);
64 for (size_t i = 1; i < inputTypesCount; ++i) {
65 auto inputInst = callInit->GetInput(i).GetInst();
66 initObject->AppendInput(inputInst, inputInst->GetType());
67 }
68
69 initObject->SetCallMethodId(callInit->GetCallMethodId());
70 initObject->SetCallMethod(static_cast<const CallInst *>(callInit)->GetCallMethod());
71 return initObject;
72 }
73
ReplaceNewObjectUsers(Inst * newObject,Inst * nullCheck,CallInst * initObject)74 void ReplaceNewObjectUsers(Inst *newObject, Inst *nullCheck, CallInst *initObject)
75 {
76 for (auto it = newObject->GetUsers().begin(); it != newObject->GetUsers().end();
77 it = newObject->GetUsers().begin()) {
78 auto user = it->GetInst();
79 if (user != nullCheck) {
80 user->SetInput(it->GetIndex(), initObject);
81 } else {
82 newObject->RemoveUser(&(*it));
83 }
84 }
85
86 // Update throwable instructions data
87 auto graph = newObject->GetBasicBlock()->GetGraph();
88 if (graph->IsInstThrowable(newObject)) {
89 graph->ReplaceThrowableInst(newObject, initObject);
90 }
91 }
92
VisitNewObject(GraphVisitor * v,Inst * inst)93 void BytecodeOptPeepholes::VisitNewObject(GraphVisitor *v, Inst *inst)
94 {
95 ASSERT(inst != nullptr);
96 CallInst *callInit = FindCtorCall(inst);
97 if (callInit == nullptr || inst->GetBasicBlock() != callInit->GetBasicBlock()) {
98 return;
99 }
100
101 // The optimization is correct only if there are no side-effects between NewObject and constructor call.
102 // For simplicity, we abort it if any instruction except NullCheck and SaveState appears in-between.
103 // Moreover, when we are inside a try block, local register state also matters, because it may be used inside
104 // catch blocks. In such case we also abort if there are any instructions in corresponding bytecode.
105 const auto graph = static_cast<BytecodeOptPeepholes *>(v)->GetGraph();
106 const size_t newobjSize =
107 BytecodeInstruction::Size( // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
108 BytecodeInstruction(graph->GetRuntime()->GetMethodCode(graph->GetMethod()) + inst->GetPc()).GetFormat());
109 if (inst->GetBasicBlock()->IsTry() && callInit->GetPc() - inst->GetPc() > newobjSize) {
110 return;
111 }
112
113 Inst *nullCheck = nullptr;
114 for (auto *i = inst->GetNext(); i != callInit; i = i->GetNext()) {
115 if (i->GetOpcode() != Opcode::SaveState && i->GetOpcode() != Opcode::NullCheck) {
116 return;
117 }
118
119 if (i->GetOpcode() == Opcode::SaveState) {
120 continue;
121 }
122
123 for (auto nullCheckInput : i->GetInputs()) {
124 if (nullCheckInput.GetInst() == inst) {
125 ASSERT(nullCheck == nullptr);
126 nullCheck = i;
127 }
128 }
129 if (nullCheck == nullptr) {
130 return;
131 }
132 }
133
134 auto load = static_cast<compiler::ClassInst *>(inst->GetInput(0U).GetInst());
135 auto *initObject = CreateInitObject(v, load, callInit);
136 callInit->InsertBefore(initObject);
137 initObject->SetPc(callInit->GetPc());
138
139 ReplaceNewObjectUsers(inst, nullCheck, initObject);
140 inst->ClearFlag(compiler::inst_flags::NO_DCE);
141 if (nullCheck != nullptr) {
142 nullCheck->ReplaceUsers(initObject);
143 nullCheck->ClearFlag(compiler::inst_flags::NO_DCE);
144 nullCheck->RemoveInputs();
145 nullCheck->GetBasicBlock()->ReplaceInst(nullCheck,
146 static_cast<BytecodeOptPeepholes *>(v)->GetGraph()->CreateInstNOP());
147 }
148 ASSERT(!callInit->HasUsers());
149 callInit->ClearFlag(compiler::inst_flags::NO_DCE);
150
151 static_cast<BytecodeOptPeepholes *>(v)->SetIsApplied();
152 }
153
154 } // namespace ark::bytecodeopt
155