1 /*
2 * Copyright (c) 2023-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 "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 CallInst *callInit = FindCtorCall(inst);
96 if (callInit == nullptr) {
97 return;
98 }
99
100 if (inst->GetBasicBlock() != callInit->GetBasicBlock()) {
101 return;
102 }
103
104 // The optimization is correct only if there are no side-effects between NewObject and constructor call.
105 // For simplicity, we abort it if any instruction except NullCheck and SaveState appears in-between.
106 // Moreover, when we are inside a try block, local register state also matters, because it may be used inside
107 // catch blocks. In such case we also abort if there are any instructions in corresponding bytecode.
108 const auto graph = static_cast<BytecodeOptPeepholes *>(v)->GetGraph();
109 const size_t newobjSize =
110 BytecodeInstruction::Size( // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
111 BytecodeInstruction(graph->GetRuntime()->GetMethodCode(graph->GetMethod()) + inst->GetPc()).GetFormat());
112 if (inst->GetBasicBlock()->IsTry() && callInit->GetPc() - inst->GetPc() > newobjSize) {
113 return;
114 }
115
116 Inst *nullCheck = nullptr;
117 for (auto *i = inst->GetNext(); i != callInit; i = i->GetNext()) {
118 if (i->GetOpcode() != Opcode::SaveState && i->GetOpcode() != Opcode::NullCheck) {
119 return;
120 }
121
122 if (i->GetOpcode() == Opcode::SaveState) {
123 continue;
124 }
125
126 for (auto nullCheckInput : i->GetInputs()) {
127 if (nullCheckInput.GetInst() == inst) {
128 ASSERT(nullCheck == nullptr);
129 nullCheck = i;
130 }
131 }
132 if (nullCheck == nullptr) {
133 return;
134 }
135 }
136
137 auto load = static_cast<compiler::ClassInst *>(inst->GetInput(0U).GetInst());
138 auto *initObject = CreateInitObject(v, load, callInit);
139 callInit->InsertBefore(initObject);
140 initObject->SetPc(callInit->GetPc());
141
142 ReplaceNewObjectUsers(inst, nullCheck, initObject);
143 inst->ClearFlag(compiler::inst_flags::NO_DCE);
144 if (nullCheck != nullptr) {
145 nullCheck->ReplaceUsers(initObject);
146 nullCheck->ClearFlag(compiler::inst_flags::NO_DCE);
147 nullCheck->RemoveInputs();
148 nullCheck->GetBasicBlock()->ReplaceInst(nullCheck,
149 static_cast<BytecodeOptPeepholes *>(v)->GetGraph()->CreateInstNOP());
150 }
151 ASSERT(!callInit->HasUsers());
152 callInit->ClearFlag(compiler::inst_flags::NO_DCE);
153
154 static_cast<BytecodeOptPeepholes *>(v)->SetIsApplied();
155 }
156
157 } // namespace ark::bytecodeopt
158