/src/hermes/lib/BCGen/HBC/BytecodeStream.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #include "hermes/BCGen/HBC/BytecodeStream.h" |
9 | | |
10 | | using namespace hermes; |
11 | | using namespace hbc; |
12 | | |
13 | | // ============================ File ============================ |
14 | 0 | void BytecodeSerializer::serialize(BytecodeModule &BM, const SHA1 &sourceHash) { |
15 | 0 | bytecodeModule_ = &BM; |
16 | 0 | uint32_t cjsModuleCount = BM.getBytecodeOptions().cjsModulesStaticallyResolved |
17 | 0 | ? BM.getCJSModuleTableStatic().size() |
18 | 0 | : BM.getCJSModuleTable().size(); |
19 | 0 | BytecodeFileHeader header{ |
20 | 0 | MAGIC, |
21 | 0 | BYTECODE_VERSION, |
22 | 0 | sourceHash, |
23 | 0 | fileLength_, |
24 | 0 | BM.getGlobalFunctionIndex(), |
25 | 0 | BM.getNumFunctions(), |
26 | 0 | static_cast<uint32_t>(BM.getStringKinds().size()), |
27 | 0 | BM.getIdentifierCount(), |
28 | 0 | BM.getStringTableSize(), |
29 | 0 | overflowStringEntryCount_, |
30 | 0 | BM.getStringStorageSize(), |
31 | 0 | static_cast<uint32_t>(BM.getBigIntTable().size()), |
32 | 0 | static_cast<uint32_t>(BM.getBigIntStorage().size()), |
33 | 0 | static_cast<uint32_t>(BM.getRegExpTable().size()), |
34 | 0 | static_cast<uint32_t>(BM.getRegExpStorage().size()), |
35 | 0 | BM.getArrayBufferSize(), |
36 | 0 | BM.getObjectKeyBufferSize(), |
37 | 0 | BM.getObjectValueBufferSize(), |
38 | 0 | BM.getSegmentID(), |
39 | 0 | cjsModuleCount, |
40 | 0 | static_cast<uint32_t>(BM.getFunctionSourceTable().size()), |
41 | 0 | debugInfoOffset_, |
42 | 0 | BM.getBytecodeOptions()}; |
43 | 0 | writeBinary(header); |
44 | | // Sizes of file and function headers are tuned for good cache line packing. |
45 | | // If you reorder the format, try to avoid headers crossing cache lines. |
46 | 0 | visitBytecodeSegmentsInOrder(*this); |
47 | 0 | serializeFunctionsBytecode(BM); |
48 | |
|
49 | 0 | for (auto &entry : BM.getFunctionTable()) { |
50 | 0 | serializeFunctionInfo(*entry); |
51 | 0 | } |
52 | |
|
53 | 0 | serializeDebugInfo(BM); |
54 | |
|
55 | 0 | SHA1 fileHash{}; |
56 | 0 | if (!isLayout_) { |
57 | 0 | auto hash = outputHasher_.result(); |
58 | 0 | assert(hash.size() == sizeof(fileHash) && "Incorrect length of SHA1 hash"); |
59 | 0 | std::copy(hash.begin(), hash.end(), fileHash.begin()); |
60 | 0 | } |
61 | | // Even in layout mode, we "write" a footer (with an ignored zero hash), |
62 | | // so that fileLength_ is set correctly. |
63 | 0 | writeBinary(BytecodeFileFooter{fileHash}); |
64 | |
|
65 | 0 | if (isLayout_) { |
66 | 0 | finishLayout(BM); |
67 | 0 | serialize(BM, sourceHash); |
68 | 0 | } |
69 | 0 | } |
70 | | |
71 | 0 | void BytecodeSerializer::finishLayout(BytecodeModule &BM) { |
72 | 0 | fileLength_ = loc_; |
73 | 0 | assert(fileLength_ > 0 && "Empty file after layout"); |
74 | 0 | isLayout_ = false; |
75 | 0 | loc_ = 0; |
76 | 0 | } |
77 | | |
78 | | // ========================== Function Table ========================== |
79 | 0 | void BytecodeSerializer::serializeFunctionTable(BytecodeModule &BM) { |
80 | 0 | for (auto &entry : BM.getFunctionTable()) { |
81 | 0 | if (options_.stripDebugInfoSection) { |
82 | | // Change flag on the actual BF, so it's seen by serializeFunctionInfo. |
83 | 0 | entry->mutableFlags().hasDebugInfo = false; |
84 | 0 | } |
85 | 0 | FunctionHeader header = entry->getHeader(); |
86 | 0 | writeBinary(SmallFuncHeader(header)); |
87 | 0 | } |
88 | 0 | } |
89 | | |
90 | | // ========================== DebugInfo ========================== |
91 | 0 | void BytecodeSerializer::serializeDebugInfo(BytecodeModule &BM) { |
92 | 0 | pad(BYTECODE_ALIGNMENT); |
93 | 0 | const DebugInfo &info = BM.getDebugInfo(); |
94 | 0 | debugInfoOffset_ = loc_; |
95 | |
|
96 | 0 | if (options_.stripDebugInfoSection) { |
97 | 0 | const DebugInfoHeader empty = {0, 0, 0, 0, 0, 0, 0}; |
98 | 0 | writeBinary(empty); |
99 | 0 | return; |
100 | 0 | } |
101 | | |
102 | 0 | const llvh::ArrayRef<StringTableEntry> filenameTable = |
103 | 0 | info.getFilenameTable(); |
104 | 0 | const auto filenameStorage = info.getFilenameStorage(); |
105 | 0 | const DebugInfo::DebugFileRegionList &files = info.viewFiles(); |
106 | 0 | const StreamVector<uint8_t> &data = info.viewData(); |
107 | 0 | uint32_t scopeDescOffset = info.scopeDescDataOffset(); |
108 | 0 | uint32_t tCalleeOffset = info.textifiedCalleeOffset(); |
109 | 0 | uint32_t stOffset = info.stringTableOffset(); |
110 | |
|
111 | 0 | DebugInfoHeader header{ |
112 | 0 | (uint32_t)filenameTable.size(), |
113 | 0 | (uint32_t)filenameStorage.size(), |
114 | 0 | (uint32_t)files.size(), |
115 | 0 | scopeDescOffset, |
116 | 0 | tCalleeOffset, |
117 | 0 | stOffset, |
118 | 0 | (uint32_t)data.size()}; |
119 | 0 | writeBinary(header); |
120 | 0 | writeBinaryArray(filenameTable); |
121 | 0 | writeBinaryArray(filenameStorage); |
122 | 0 | for (auto &file : files) { |
123 | 0 | writeBinary(file); |
124 | 0 | } |
125 | 0 | writeBinaryArray(data.getData()); |
126 | 0 | } |
127 | | |
128 | | // ===================== CommonJS Module Table ====================== |
129 | 0 | void BytecodeSerializer::serializeCJSModuleTable(BytecodeModule &BM) { |
130 | 0 | pad(BYTECODE_ALIGNMENT); |
131 | |
|
132 | 0 | for (const auto &it : BM.getCJSModuleTable()) { |
133 | 0 | writeBinary(it.first); |
134 | 0 | writeBinary(it.second); |
135 | 0 | } |
136 | |
|
137 | 0 | writeBinaryArray(BM.getCJSModuleTableStatic()); |
138 | 0 | } |
139 | | |
140 | | // ===================== Function Source Table ====================== |
141 | 0 | void BytecodeSerializer::serializeFunctionSourceTable(BytecodeModule &BM) { |
142 | 0 | pad(BYTECODE_ALIGNMENT); |
143 | |
|
144 | 0 | writeBinaryArray(BM.getFunctionSourceTable()); |
145 | 0 | } |
146 | | |
147 | | // ==================== Exception Handler Table ===================== |
148 | 0 | void BytecodeSerializer::serializeExceptionHandlerTable(BytecodeFunction &BF) { |
149 | 0 | if (!BF.hasExceptionHandlers()) |
150 | 0 | return; |
151 | | |
152 | 0 | pad(INFO_ALIGNMENT); |
153 | 0 | ExceptionHandlerTableHeader header{BF.getExceptionHandlerCount()}; |
154 | 0 | writeBinary(header); |
155 | |
|
156 | 0 | writeBinaryArray(BF.getExceptionHandlers()); |
157 | 0 | } |
158 | | |
159 | | // ========================= Array Buffer ========================== |
160 | 0 | void BytecodeSerializer::serializeArrayBuffer(BytecodeModule &BM) { |
161 | 0 | writeBinaryArray(BM.getArrayBuffer()); |
162 | 0 | } |
163 | | |
164 | 0 | void BytecodeSerializer::serializeObjectBuffer(BytecodeModule &BM) { |
165 | 0 | auto objectKeyValBufferPair = BM.getObjectBuffer(); |
166 | |
|
167 | 0 | writeBinaryArray(objectKeyValBufferPair.first); |
168 | 0 | writeBinaryArray(objectKeyValBufferPair.second); |
169 | 0 | } |
170 | | |
171 | 0 | void BytecodeSerializer::serializeDebugOffsets(BytecodeFunction &BF) { |
172 | 0 | if (options_.stripDebugInfoSection || !BF.hasDebugInfo()) { |
173 | 0 | return; |
174 | 0 | } |
175 | | |
176 | 0 | pad(INFO_ALIGNMENT); |
177 | 0 | auto *offsets = BF.getDebugOffsets(); |
178 | 0 | writeBinary(*offsets); |
179 | 0 | } |
180 | | |
181 | | // ============================ Function ============================ |
182 | 0 | void BytecodeSerializer::serializeFunctionsBytecode(BytecodeModule &BM) { |
183 | | // Map from opcodes and jumptables to offsets, used to deduplicate bytecode. |
184 | 0 | using DedupKey = llvh::ArrayRef<opcode_atom_t>; |
185 | 0 | llvh::DenseMap<DedupKey, uint32_t> bcMap; |
186 | 0 | for (auto &entry : BM.getFunctionTable()) { |
187 | 0 | if (options_.optimizationEnabled) { |
188 | | // If identical bytecode exists, we'll reuse it. |
189 | 0 | bool reuse = false; |
190 | 0 | if (isLayout_) { |
191 | | // Deduplicate the bytecode during layout phase. |
192 | 0 | DedupKey key = entry->getOpcodeArray(); |
193 | 0 | auto pair = |
194 | 0 | bcMap.insert(std::make_pair(key, static_cast<uint32_t>(loc_))); |
195 | 0 | if (!pair.second) { |
196 | 0 | reuse = true; |
197 | 0 | entry->setOffset(pair.first->second); |
198 | 0 | } |
199 | 0 | } else { |
200 | | // Cheaply determine whether bytecode was deduplicated. |
201 | 0 | assert(entry->getOffset() && "Function lacks offset after layout"); |
202 | 0 | assert(entry->getOffset() <= loc_ && "Function has too large offset"); |
203 | 0 | reuse = entry->getOffset() < loc_; |
204 | 0 | } |
205 | 0 | if (reuse) { |
206 | 0 | continue; |
207 | 0 | } |
208 | 0 | } |
209 | | |
210 | | // Set the offset of this function's bytecode. |
211 | 0 | if (isLayout_) { |
212 | 0 | entry->setOffset(loc_); |
213 | 0 | } |
214 | | |
215 | | // Serialize opcodes. |
216 | 0 | writeBinaryArray(entry->getOpcodesOnly()); |
217 | | |
218 | | // Serialize any jump table after the opcode block. |
219 | 0 | if (!entry->getJumpTablesOnly().empty()) { |
220 | 0 | pad(sizeof(uint32_t)); |
221 | 0 | writeBinaryArray(entry->getJumpTablesOnly()); |
222 | 0 | } |
223 | 0 | if (options_.padFunctionBodiesPercent) { |
224 | 0 | size_t size = entry->getOpcodesOnly().size(); |
225 | 0 | size = (size * options_.padFunctionBodiesPercent) / 100; |
226 | 0 | while (size--) |
227 | 0 | writeBinary('\0'); |
228 | 0 | pad(sizeof(uint32_t)); |
229 | 0 | } |
230 | 0 | } |
231 | 0 | } |
232 | | |
233 | 0 | void BytecodeSerializer::serializeFunctionInfo(BytecodeFunction &BF) { |
234 | | // Set the offset of this function's info. Any subsection that is present is |
235 | | // aligned to INFO_ALIGNMENT, so we also align the recorded offset to that. |
236 | 0 | if (isLayout_) { |
237 | 0 | BF.setInfoOffset(llvh::alignTo(loc_, INFO_ALIGNMENT)); |
238 | 0 | } |
239 | | |
240 | | // Write large header if it doesn't fit in a small. |
241 | 0 | FunctionHeader header = BF.getHeader(); |
242 | 0 | if (SmallFuncHeader(header).flags.overflowed) { |
243 | 0 | pad(INFO_ALIGNMENT); |
244 | 0 | writeBinary(header); |
245 | 0 | } |
246 | | |
247 | | // Serialize exception handlers. |
248 | 0 | serializeExceptionHandlerTable(BF); |
249 | | |
250 | | // Add offset in debug info (if function has debug info). |
251 | 0 | serializeDebugOffsets(BF); |
252 | 0 | } |
253 | | |
254 | 0 | void BytecodeSerializer::visitFunctionHeaders() { |
255 | 0 | pad(BYTECODE_ALIGNMENT); |
256 | 0 | serializeFunctionTable(*bytecodeModule_); |
257 | 0 | } |
258 | | |
259 | 0 | void BytecodeSerializer::visitStringKinds() { |
260 | 0 | pad(BYTECODE_ALIGNMENT); |
261 | 0 | writeBinaryArray(bytecodeModule_->getStringKinds()); |
262 | 0 | } |
263 | | |
264 | 0 | void BytecodeSerializer::visitIdentifierHashes() { |
265 | 0 | pad(BYTECODE_ALIGNMENT); |
266 | 0 | writeBinaryArray(bytecodeModule_->getIdentifierHashes()); |
267 | 0 | } |
268 | | |
269 | 0 | void BytecodeSerializer::visitSmallStringTable() { |
270 | 0 | pad(BYTECODE_ALIGNMENT); |
271 | 0 | uint32_t overflowCount = 0; |
272 | 0 | for (auto &entry : bytecodeModule_->getStringTable()) { |
273 | 0 | SmallStringTableEntry small(entry, overflowCount); |
274 | 0 | writeBinary(small); |
275 | 0 | overflowCount += small.isOverflowed(); |
276 | 0 | } |
277 | 0 | overflowStringEntryCount_ = overflowCount; |
278 | 0 | } |
279 | | |
280 | 0 | void BytecodeSerializer::visitOverflowStringTable() { |
281 | 0 | pad(BYTECODE_ALIGNMENT); |
282 | 0 | llvh::SmallVector<OverflowStringTableEntry, 64> overflow; |
283 | 0 | for (auto &entry : bytecodeModule_->getStringTable()) { |
284 | 0 | SmallStringTableEntry small(entry, overflow.size()); |
285 | 0 | if (small.isOverflowed()) { |
286 | 0 | overflow.emplace_back(entry.getOffset(), entry.getLength()); |
287 | 0 | } |
288 | 0 | } |
289 | 0 | writeBinaryArray(llvh::makeArrayRef(overflow)); |
290 | 0 | } |
291 | | |
292 | 0 | void BytecodeSerializer::visitStringStorage() { |
293 | 0 | pad(BYTECODE_ALIGNMENT); |
294 | 0 | writeBinaryArray(bytecodeModule_->getStringStorage()); |
295 | 0 | } |
296 | | |
297 | 0 | void BytecodeSerializer::visitArrayBuffer() { |
298 | 0 | pad(BYTECODE_ALIGNMENT); |
299 | 0 | serializeArrayBuffer(*bytecodeModule_); |
300 | 0 | } |
301 | | |
302 | 0 | void BytecodeSerializer::visitObjectKeyBuffer() { |
303 | 0 | pad(BYTECODE_ALIGNMENT); |
304 | 0 | auto objectKeyValBufferPair = bytecodeModule_->getObjectBuffer(); |
305 | 0 | writeBinaryArray(objectKeyValBufferPair.first); |
306 | 0 | } |
307 | | |
308 | 0 | void BytecodeSerializer::visitObjectValueBuffer() { |
309 | 0 | pad(BYTECODE_ALIGNMENT); |
310 | 0 | auto objectKeyValBufferPair = bytecodeModule_->getObjectBuffer(); |
311 | 0 | writeBinaryArray(objectKeyValBufferPair.second); |
312 | 0 | } |
313 | | |
314 | 0 | void BytecodeSerializer::visitBigIntTable() { |
315 | 0 | pad(BYTECODE_ALIGNMENT); |
316 | 0 | writeBinaryArray(bytecodeModule_->getBigIntTable()); |
317 | 0 | } |
318 | | |
319 | 0 | void BytecodeSerializer::visitBigIntStorage() { |
320 | 0 | pad(BYTECODE_ALIGNMENT); |
321 | 0 | writeBinaryArray(bytecodeModule_->getBigIntStorage()); |
322 | 0 | } |
323 | | |
324 | 0 | void BytecodeSerializer::visitRegExpTable() { |
325 | 0 | pad(BYTECODE_ALIGNMENT); |
326 | 0 | writeBinaryArray(bytecodeModule_->getRegExpTable()); |
327 | 0 | } |
328 | | |
329 | 0 | void BytecodeSerializer::visitRegExpStorage() { |
330 | 0 | pad(BYTECODE_ALIGNMENT); |
331 | 0 | writeBinaryArray(bytecodeModule_->getRegExpStorage()); |
332 | 0 | } |
333 | | |
334 | 0 | void BytecodeSerializer::visitCJSModuleTable() { |
335 | 0 | pad(BYTECODE_ALIGNMENT); |
336 | 0 | serializeCJSModuleTable(*bytecodeModule_); |
337 | 0 | } |
338 | | |
339 | 0 | void BytecodeSerializer::visitFunctionSourceTable() { |
340 | 0 | pad(BYTECODE_ALIGNMENT); |
341 | 0 | serializeFunctionSourceTable(*bytecodeModule_); |
342 | 0 | } |