1 /**
2 * Copyright (c) 2021-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 "libpandafile/bytecode_instruction.h"
17 #include "libpandafile/bytecode_instruction-inl.h"
18 #include "libpandafile/file_items.h"
19 #include "libpandafile/debug_info_updater-inl.h"
20
21 #include "linker_context.h"
22
23 namespace ark::static_linker {
24
25 namespace {
26
27 class LinkerDebugInfoUpdater : public panda_file::DebugInfoUpdater<LinkerDebugInfoUpdater> {
28 public:
29 using Super = panda_file::DebugInfoUpdater<LinkerDebugInfoUpdater>;
30
LinkerDebugInfoUpdater(const panda_file::File * file,panda_file::ItemContainer * cont)31 LinkerDebugInfoUpdater(const panda_file::File *file, panda_file::ItemContainer *cont) : Super(file), cont_(cont) {}
32
GetOrCreateStringItem(const std::string & s)33 panda_file::StringItem *GetOrCreateStringItem(const std::string &s)
34 {
35 return cont_->GetOrCreateStringItem(s);
36 }
37
GetType(panda_file::File::EntityId typeId,const std::string & typeName)38 panda_file::BaseClassItem *GetType([[maybe_unused]] panda_file::File::EntityId typeId, const std::string &typeName)
39 {
40 auto *cm = cont_->GetClassMap();
41 auto iter = cm->find(typeName);
42 if (iter != cm->end()) {
43 return iter->second;
44 }
45 return nullptr;
46 }
47
48 private:
49 panda_file::ItemContainer *cont_;
50 };
51
52 class LinkerDebugInfoScrapper : public panda_file::DebugInfoUpdater<LinkerDebugInfoScrapper> {
53 public:
54 using Super = panda_file::DebugInfoUpdater<LinkerDebugInfoScrapper>;
55
LinkerDebugInfoScrapper(const panda_file::File * file,CodePatcher * patcher,panda_file::ItemContainer * cont)56 LinkerDebugInfoScrapper(const panda_file::File *file, CodePatcher *patcher, panda_file::ItemContainer *cont)
57 : Super(file), patcher_(patcher), cont_(cont)
58 {
59 }
60
GetOrCreateStringItem(const std::string & s)61 panda_file::StringItem *GetOrCreateStringItem(const std::string &s)
62 {
63 patcher_->Add(s);
64 return nullptr;
65 }
66
GetType(panda_file::File::EntityId typeId,const std::string & typeName)67 panda_file::BaseClassItem *GetType([[maybe_unused]] panda_file::File::EntityId typeId, const std::string &typeName)
68 {
69 auto *cm = cont_->GetClassMap();
70 if (cm->find(typeName) == cm->end()) {
71 // This action must create a string for primitive types.
72 GetOrCreateStringItem(typeName);
73 }
74 return nullptr;
75 }
76
77 private:
78 CodePatcher *patcher_;
79 panda_file::ItemContainer *cont_;
80 };
81 } // namespace
82
Add(Change c)83 void CodePatcher::Add(Change c)
84 {
85 changes_.emplace_back(std::move(c));
86 }
87
Devour(CodePatcher && p)88 void CodePatcher::Devour(CodePatcher &&p)
89 {
90 const auto oldSize = changes_.size();
91 changes_.insert(changes_.end(), std::move_iterator(p.changes_.begin()), std::move_iterator(p.changes_.end()));
92 const auto newSize = changes_.size();
93 p.changes_.clear();
94
95 ranges_.emplace_back(oldSize, newSize);
96 }
97
AddRange(std::pair<size_t,size_t> range)98 void CodePatcher::AddRange(std::pair<size_t, size_t> range)
99 {
100 ranges_.push_back(range);
101 }
102
ApplyLiteralArrayChange(LiteralArrayChange & lc,Context * ctx)103 void CodePatcher::ApplyLiteralArrayChange(LiteralArrayChange &lc, Context *ctx)
104 {
105 auto id = ctx->literalArrayId_++;
106 lc.it = ctx->GetContainer().GetOrCreateLiteralArrayItem(std::to_string(id));
107
108 auto &oldIts = lc.old->GetItems();
109 auto newIts = std::vector<panda_file::LiteralItem>();
110 newIts.reserve(oldIts.size());
111
112 for (const auto &i : oldIts) {
113 using LIT = panda_file::LiteralItem::Type;
114 switch (i.GetType()) {
115 case LIT::B1:
116 case LIT::B2:
117 case LIT::B4:
118 case LIT::B8:
119 newIts.emplace_back(i);
120 break;
121 case LIT::STRING: {
122 auto str = ctx->StringFromOld(i.GetValue<panda_file::StringItem *>());
123 newIts.emplace_back(str);
124 break;
125 }
126 case LIT::METHOD: {
127 auto meth = i.GetValue<panda_file::MethodItem *>();
128 auto iter = ctx->knownItems_.find(meth);
129 ASSERT(iter != ctx->knownItems_.end());
130 ASSERT(iter->second->GetItemType() == panda_file::ItemTypes::METHOD_ITEM);
131 newIts.emplace_back(static_cast<panda_file::MethodItem *>(iter->second));
132 break;
133 }
134 default:
135 UNREACHABLE();
136 }
137 }
138
139 lc.it->AddItems(newIts);
140 }
141
ApplyDeps(Context * ctx)142 void CodePatcher::ApplyDeps(Context *ctx)
143 {
144 for (auto &v : changes_) {
145 std::visit(
146 [this, ctx](auto &a) {
147 using T = std::remove_cv_t<std::remove_reference_t<decltype(a)>>;
148 // IndexedChange, StringChange, LiteralArrayChange, std::string, std::function<void()>>
149 if constexpr (std::is_same_v<T, IndexedChange>) {
150 a.mi->AddIndexDependency(a.it);
151 } else if constexpr (std::is_same_v<T, StringChange>) {
152 a.it = ctx->GetContainer().GetOrCreateStringItem(a.str);
153 } else if constexpr (std::is_same_v<T, LiteralArrayChange>) {
154 ApplyLiteralArrayChange(a, ctx);
155 } else if constexpr (std::is_same_v<T, std::string>) {
156 ctx->GetContainer().GetOrCreateStringItem(a);
157 } else if constexpr (std::is_same_v<T, std::function<void()>>) {
158 // nothing
159 } else {
160 UNREACHABLE();
161 }
162 },
163 v);
164 }
165 }
166
Patch(const std::pair<size_t,size_t> range)167 void CodePatcher::Patch(const std::pair<size_t, size_t> range)
168 {
169 for (size_t i = range.first; i < range.second; i++) {
170 auto &v = changes_[i];
171 std::visit(
172 [](auto &a) {
173 using T = std::remove_cv_t<std::remove_reference_t<decltype(a)>>;
174 if constexpr (std::is_same_v<T, IndexedChange>) {
175 auto idx = a.it->GetIndex(a.mi);
176 a.inst.UpdateId(BytecodeId(idx));
177 } else if constexpr (std::is_same_v<T, StringChange>) {
178 auto off = a.it->GetOffset();
179 a.inst.UpdateId(BytecodeId(off));
180 } else if constexpr (std::is_same_v<T, LiteralArrayChange>) {
181 auto off = a.it->GetIndex();
182 a.inst.UpdateId(BytecodeId(off));
183 } else if constexpr (std::is_same_v<T, std::string>) {
184 // nothing
185 } else if constexpr (std::is_same_v<T, std::function<void()>>) {
186 a();
187 } else {
188 UNREACHABLE();
189 }
190 },
191 v);
192 }
193 }
194
MakeChangeWithId(CodePatcher & p,CodeData * data)195 void Context::MakeChangeWithId(CodePatcher &p, CodeData *data)
196 {
197 using Flags = ark::BytecodeInst<ark::BytecodeInstMode::FAST>::Flags;
198 using EntityId = panda_file::File::EntityId;
199
200 const auto myId = EntityId(data->omi->GetOffset());
201 auto *items = data->fileReader->GetItems();
202 auto inst = BytecodeInstruction(data->code->data());
203 size_t offset = 0;
204 const auto limit = data->code->size();
205
206 auto filePtr = data->fileReader->GetFilePtr();
207
208 using Resolver = EntityId (panda_file::File::*)(EntityId id, panda_file::File::Index idx) const;
209
210 auto makeWithId = [&p, &inst, &filePtr, &items, &myId, &data, this](Resolver resolve) {
211 auto idx = inst.GetId().AsIndex();
212 auto oldId = (filePtr->*resolve)(myId, idx);
213 auto iter = items->find(oldId);
214 ASSERT(iter != items->end());
215 auto asIndexed = static_cast<panda_file::IndexedItem *>(iter->second);
216 auto found = knownItems_.find(asIndexed);
217 ASSERT(found != knownItems_.end());
218 p.Add(CodePatcher::IndexedChange {inst, data->nmi, static_cast<panda_file::IndexedItem *>(found->second)});
219 };
220
221 while (offset < limit) {
222 if (inst.HasFlag(Flags::TYPE_ID)) {
223 makeWithId(&panda_file::File::ResolveClassIndex);
224 // inst.UpdateId()
225 } else if (inst.HasFlag(Flags::METHOD_ID)) {
226 makeWithId(&panda_file::File::ResolveMethodIndex);
227 } else if (inst.HasFlag(Flags::FIELD_ID)) {
228 makeWithId(&panda_file::File::ResolveFieldIndex);
229 } else if (inst.HasFlag(Flags::STRING_ID)) {
230 BytecodeId bId = inst.GetId();
231 auto oldId = bId.AsFileId();
232
233 auto sData = filePtr->GetStringData(oldId);
234 auto itemStr = std::string(utf::Mutf8AsCString(sData.data));
235 p.Add(CodePatcher::StringChange {inst, std::move(itemStr)});
236 } else if (inst.HasFlag(Flags::LITERALARRAY_ID)) {
237 BytecodeId bId = inst.GetId();
238 auto oldIdx = bId.AsRawValue();
239 auto arrs = filePtr->GetLiteralArrays();
240 ASSERT(oldIdx < arrs.size());
241 auto off = arrs[oldIdx];
242 auto iter = items->find(EntityId(off));
243 ASSERT(iter != items->end());
244 ASSERT(iter->second->GetItemType() == panda_file::ItemTypes::LITERAL_ARRAY_ITEM);
245 p.Add(CodePatcher::LiteralArrayChange {inst, static_cast<panda_file::LiteralArrayItem *>(iter->second)});
246 }
247
248 offset += inst.GetSize();
249 inst = inst.GetNext();
250 }
251 }
252
ProcessCodeData(CodePatcher & p,CodeData * data)253 void Context::ProcessCodeData(CodePatcher &p, CodeData *data)
254 {
255 if (data->code != nullptr) {
256 MakeChangeWithId(p, data);
257 }
258
259 auto *dbg = data->omi->GetDebugInfo();
260 if (dbg == nullptr || conf_.stripDebugInfo) {
261 return;
262 }
263
264 // Collect string items for each method with debug information.
265 auto file = data->fileReader->GetFilePtr();
266 auto scrapper = LinkerDebugInfoScrapper(file, &p, &cont_);
267 auto off = dbg->GetOffset();
268 ASSERT(off != 0);
269 auto eId = panda_file::File::EntityId(off);
270 scrapper.Scrap(eId);
271
272 auto newDbg = data->nmi->GetDebugInfo();
273 p.Add([file, this, newDbg, eId, patchLnp = data->patchLnp]() {
274 auto updater = LinkerDebugInfoUpdater(file, &cont_);
275
276 auto *constantPool = newDbg->GetConstantPool();
277 if (patchLnp) {
278 // Original `LineNumberProgram` - must emit both instructions and their arguments.
279 auto *lnpItem = newDbg->GetLineNumberProgram();
280 updater.Emit(lnpItem, constantPool, eId);
281 } else {
282 // `LineNumberProgram` is reused and its instructions will be emitted by owner-method.
283 // Still need to emit instructions' arguments, which are unique for each method.
284 panda_file::LineNumberProgramItemBase lnpItem;
285 updater.Emit(&lnpItem, constantPool, eId);
286 }
287 });
288 }
289
290 } // namespace ark::static_linker
291