1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // Copyright (C) 2018 Intel Corporation. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include <qv4compiler_p.h> |
6 | #include <qv4codegen_p.h> |
7 | #include <private/qv4compileddata_p.h> |
8 | #include <private/qv4staticvalue_p.h> |
9 | #include <private/qv4alloca_p.h> |
10 | #include <private/qqmljslexer_p.h> |
11 | #include <private/qqmljsast_p.h> |
12 | #include <private/qml_compile_hash_p.h> |
13 | #include <private/qqmlirbuilder_p.h> |
14 | #include <QCryptographicHash> |
15 | #include <QtEndian> |
16 | |
17 | // Efficient implementation that takes advantage of powers of two. |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | namespace QtPrivate { // Disambiguate from WTF::roundUpToMultipleOf |
21 | static inline size_t roundUpToMultipleOf(size_t divisor, size_t x) |
22 | { |
23 | Q_ASSERT(divisor && !(divisor & (divisor - 1))); |
24 | const size_t remainderMask = divisor - 1; |
25 | return (x + remainderMask) & ~remainderMask; |
26 | } |
27 | } |
28 | QT_END_NAMESPACE |
29 | |
30 | QV4::Compiler::StringTableGenerator::StringTableGenerator() |
31 | { |
32 | clear(); |
33 | } |
34 | |
35 | int QV4::Compiler::StringTableGenerator::registerString(const QString &str) |
36 | { |
37 | Q_ASSERT(!frozen); |
38 | QHash<QString, int>::ConstIterator it = stringToId.constFind(key: str); |
39 | if (it != stringToId.cend()) |
40 | return *it; |
41 | stringToId.insert(key: str, value: strings.size()); |
42 | strings.append(t: str); |
43 | stringDataSize += QV4::CompiledData::String::calculateSize(str); |
44 | return strings.size() - 1; |
45 | } |
46 | |
47 | int QV4::Compiler::StringTableGenerator::getStringId(const QString &string) const |
48 | { |
49 | Q_ASSERT(stringToId.contains(string)); |
50 | return stringToId.value(key: string); |
51 | } |
52 | |
53 | void QV4::Compiler::StringTableGenerator::clear() |
54 | { |
55 | strings.clear(); |
56 | stringToId.clear(); |
57 | stringDataSize = 0; |
58 | frozen = false; |
59 | } |
60 | |
61 | void QV4::Compiler::StringTableGenerator::initializeFromBackingUnit(const QV4::CompiledData::Unit *unit) |
62 | { |
63 | clear(); |
64 | for (uint i = 0; i < unit->stringTableSize; ++i) |
65 | registerString(str: unit->stringAtInternal(idx: i)); |
66 | backingUnitTableSize = unit->stringTableSize; |
67 | stringDataSize = 0; |
68 | } |
69 | |
70 | void QV4::Compiler::StringTableGenerator::serialize(CompiledData::Unit *unit) |
71 | { |
72 | char *dataStart = reinterpret_cast<char *>(unit); |
73 | quint32_le *stringTable = reinterpret_cast<quint32_le *>(dataStart + unit->offsetToStringTable); |
74 | char *stringData = reinterpret_cast<char *>(stringTable) |
75 | + QtPrivate::roundUpToMultipleOf(divisor: 8, x: unit->stringTableSize * sizeof(uint)); |
76 | for (int i = backingUnitTableSize ; i < strings.size(); ++i) { |
77 | const int index = i - backingUnitTableSize; |
78 | stringTable[index] = stringData - dataStart; |
79 | const QString &qstr = strings.at(i); |
80 | |
81 | QV4::CompiledData::String *s = reinterpret_cast<QV4::CompiledData::String *>(stringData); |
82 | Q_ASSERT(reinterpret_cast<uintptr_t>(s) % alignof(QV4::CompiledData::String) == 0); |
83 | Q_ASSERT(qstr.size() >= 0); |
84 | s->size = qstr.size(); |
85 | |
86 | ushort *uc = reinterpret_cast<ushort *>(reinterpret_cast<char *>(s) + sizeof(*s)); |
87 | qToLittleEndian<ushort>(source: qstr.constData(), count: s->size, dest: uc); |
88 | uc[s->size] = 0; |
89 | |
90 | stringData += QV4::CompiledData::String::calculateSize(str: qstr); |
91 | } |
92 | } |
93 | |
94 | void QV4::Compiler::JSUnitGenerator::generateUnitChecksum(QV4::CompiledData::Unit *unit) |
95 | { |
96 | #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 |
97 | QCryptographicHash hash(QCryptographicHash::Md5); |
98 | |
99 | const int checksummableDataOffset |
100 | = offsetof(QV4::CompiledData::Unit, md5Checksum) + sizeof(unit->md5Checksum); |
101 | |
102 | const char *dataPtr = reinterpret_cast<const char *>(unit) + checksummableDataOffset; |
103 | hash.addData(data: {dataPtr, qsizetype(unit->unitSize - checksummableDataOffset)}); |
104 | |
105 | QByteArray checksum = hash.result(); |
106 | Q_ASSERT(checksum.size() == sizeof(unit->md5Checksum)); |
107 | memcpy(dest: unit->md5Checksum, src: checksum.constData(), n: sizeof(unit->md5Checksum)); |
108 | #else |
109 | memset(unit->md5Checksum, 0, sizeof(unit->md5Checksum)); |
110 | #endif |
111 | } |
112 | |
113 | QV4::Compiler::JSUnitGenerator::JSUnitGenerator(QV4::Compiler::Module *module) |
114 | : module(module) |
115 | { |
116 | // Make sure the empty string always gets index 0 |
117 | registerString(str: QString()); |
118 | } |
119 | |
120 | int QV4::Compiler::JSUnitGenerator::registerGetterLookup(const QString &name, LookupMode mode) |
121 | { |
122 | return registerGetterLookup(nameIndex: registerString(str: name), mode); |
123 | } |
124 | |
125 | static QV4::CompiledData::Lookup::Mode lookupMode(QV4::Compiler::JSUnitGenerator::LookupMode mode) |
126 | { |
127 | return mode == QV4::Compiler::JSUnitGenerator::LookupForCall |
128 | ? QV4::CompiledData::Lookup::Mode_ForCall |
129 | : QV4::CompiledData::Lookup::Mode_ForStorage; |
130 | } |
131 | |
132 | int QV4::Compiler::JSUnitGenerator::registerGetterLookup(int nameIndex, LookupMode mode) |
133 | { |
134 | lookups << CompiledData::Lookup( |
135 | CompiledData::Lookup::Type_Getter, lookupMode(mode), nameIndex); |
136 | return lookups.size() - 1; |
137 | } |
138 | |
139 | int QV4::Compiler::JSUnitGenerator::registerSetterLookup(const QString &name) |
140 | { |
141 | return registerSetterLookup(nameIndex: registerString(str: name)); |
142 | } |
143 | |
144 | int QV4::Compiler::JSUnitGenerator::registerSetterLookup(int nameIndex) |
145 | { |
146 | lookups << CompiledData::Lookup( |
147 | CompiledData::Lookup::Type_Setter, |
148 | CompiledData::Lookup::Mode_ForStorage, nameIndex); |
149 | return lookups.size() - 1; |
150 | } |
151 | |
152 | int QV4::Compiler::JSUnitGenerator::registerGlobalGetterLookup(int nameIndex, LookupMode mode) |
153 | { |
154 | lookups << CompiledData::Lookup( |
155 | CompiledData::Lookup::Type_GlobalGetter, lookupMode(mode), nameIndex); |
156 | return lookups.size() - 1; |
157 | } |
158 | |
159 | int QV4::Compiler::JSUnitGenerator::registerQmlContextPropertyGetterLookup( |
160 | int nameIndex, LookupMode mode) |
161 | { |
162 | lookups << CompiledData::Lookup( |
163 | CompiledData::Lookup::Type_QmlContextPropertyGetter, lookupMode(mode), |
164 | nameIndex); |
165 | return lookups.size() - 1; |
166 | } |
167 | |
168 | int QV4::Compiler::JSUnitGenerator::registerRegExp(QQmlJS::AST::RegExpLiteral *regexp) |
169 | { |
170 | quint32 flags = 0; |
171 | if (regexp->flags & QQmlJS::Lexer::RegExp_Global) |
172 | flags |= CompiledData::RegExp::RegExp_Global; |
173 | if (regexp->flags & QQmlJS::Lexer::RegExp_IgnoreCase) |
174 | flags |= CompiledData::RegExp::RegExp_IgnoreCase; |
175 | if (regexp->flags & QQmlJS::Lexer::RegExp_Multiline) |
176 | flags |= CompiledData::RegExp::RegExp_Multiline; |
177 | if (regexp->flags & QQmlJS::Lexer::RegExp_Unicode) |
178 | flags |= CompiledData::RegExp::RegExp_Unicode; |
179 | if (regexp->flags & QQmlJS::Lexer::RegExp_Sticky) |
180 | flags |= CompiledData::RegExp::RegExp_Sticky; |
181 | |
182 | regexps.append(t: CompiledData::RegExp(flags, registerString(str: regexp->pattern.toString()))); |
183 | return regexps.size() - 1; |
184 | } |
185 | |
186 | int QV4::Compiler::JSUnitGenerator::registerConstant(QV4::ReturnedValue v) |
187 | { |
188 | int idx = constants.indexOf(t: v); |
189 | if (idx >= 0) |
190 | return idx; |
191 | constants.append(t: v); |
192 | return constants.size() - 1; |
193 | } |
194 | |
195 | QV4::ReturnedValue QV4::Compiler::JSUnitGenerator::constant(int idx) const |
196 | { |
197 | return constants.at(i: idx); |
198 | } |
199 | |
200 | int QV4::Compiler::JSUnitGenerator::registerJSClass(const QStringList &members) |
201 | { |
202 | // ### re-use existing class definitions. |
203 | |
204 | const int size = CompiledData::JSClass::calculateSize(nMembers: members.size()); |
205 | jsClassOffsets.append(t: jsClassData.size()); |
206 | const int oldSize = jsClassData.size(); |
207 | jsClassData.resize(size: jsClassData.size() + size); |
208 | memset(s: jsClassData.data() + oldSize, c: 0, n: size); |
209 | |
210 | CompiledData::JSClass *jsClass = reinterpret_cast<CompiledData::JSClass*>(jsClassData.data() + oldSize); |
211 | jsClass->nMembers = members.size(); |
212 | CompiledData::JSClassMember *member = reinterpret_cast<CompiledData::JSClassMember*>(jsClass + 1); |
213 | |
214 | for (const auto &name : members) { |
215 | member->set(nameOffset: registerString(str: name), isAccessor: false); |
216 | ++member; |
217 | } |
218 | |
219 | return jsClassOffsets.size() - 1; |
220 | } |
221 | |
222 | int QV4::Compiler::JSUnitGenerator::registerTranslation(const QV4::CompiledData::TranslationData &translation) |
223 | { |
224 | translations.append(t: translation); |
225 | return translations.size() - 1; |
226 | } |
227 | |
228 | QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorOption option) |
229 | { |
230 | const auto registerTypeStrings = [this](QQmlJS::AST::Type *type) { |
231 | if (!type) |
232 | return; |
233 | |
234 | if (type->typeArgument) { |
235 | registerString(str: type->typeArgument->toString()); |
236 | registerString(str: type->typeId->toString()); |
237 | } |
238 | registerString(str: type->toString()); |
239 | }; |
240 | |
241 | registerString(str: module->fileName); |
242 | registerString(str: module->finalUrl); |
243 | for (Context *f : std::as_const(t&: module->functions)) { |
244 | registerString(str: f->name); |
245 | registerTypeStrings(f->returnType); |
246 | for (int i = 0; i < f->arguments.size(); ++i) { |
247 | registerString(str: f->arguments.at(i).id); |
248 | if (const QQmlJS::AST::TypeAnnotation *annotation |
249 | = f->arguments.at(i).typeAnnotation.data()) { |
250 | registerTypeStrings(annotation->type); |
251 | } |
252 | } |
253 | for (int i = 0; i < f->locals.size(); ++i) |
254 | registerString(str: f->locals.at(i)); |
255 | } |
256 | for (Context *c : std::as_const(t&: module->blocks)) { |
257 | for (int i = 0; i < c->locals.size(); ++i) |
258 | registerString(str: c->locals.at(i)); |
259 | } |
260 | { |
261 | const auto registerExportEntry = [this](const Compiler::ExportEntry &entry) { |
262 | registerString(str: entry.exportName); |
263 | registerString(str: entry.moduleRequest); |
264 | registerString(str: entry.importName); |
265 | registerString(str: entry.localName); |
266 | }; |
267 | std::for_each(first: module->localExportEntries.constBegin(), last: module->localExportEntries.constEnd(), f: registerExportEntry); |
268 | std::for_each(first: module->indirectExportEntries.constBegin(), last: module->indirectExportEntries.constEnd(), f: registerExportEntry); |
269 | std::for_each(first: module->starExportEntries.constBegin(), last: module->starExportEntries.constEnd(), f: registerExportEntry); |
270 | } |
271 | { |
272 | for (const auto &entry: module->importEntries) { |
273 | registerString(str: entry.moduleRequest); |
274 | registerString(str: entry.importName); |
275 | registerString(str: entry.localName); |
276 | } |
277 | |
278 | for (const QString &request: module->moduleRequests) |
279 | registerString(str: request); |
280 | } |
281 | |
282 | Q_ALLOCA_VAR(quint32_le, blockClassAndFunctionOffsets, (module->functions.size() + module->classes.size() + module->templateObjects.size() + module->blocks.size()) * sizeof(quint32_le)); |
283 | uint jsClassDataOffset = 0; |
284 | |
285 | char *dataPtr; |
286 | CompiledData::Unit *unit; |
287 | { |
288 | QV4::CompiledData::Unit = generateHeader(option, functionOffsets: blockClassAndFunctionOffsets, jsClassDataOffset: &jsClassDataOffset); |
289 | dataPtr = reinterpret_cast<char *>(malloc(size: tempHeader.unitSize)); |
290 | memset(s: dataPtr, c: 0, n: tempHeader.unitSize); |
291 | memcpy(dest: &unit, src: &dataPtr, n: sizeof(CompiledData::Unit*)); |
292 | memcpy(dest: unit, src: &tempHeader, n: sizeof(tempHeader)); |
293 | } |
294 | |
295 | memcpy(dest: dataPtr + unit->offsetToFunctionTable, src: blockClassAndFunctionOffsets, n: unit->functionTableSize * sizeof(quint32_le)); |
296 | memcpy(dest: dataPtr + unit->offsetToClassTable, src: blockClassAndFunctionOffsets + unit->functionTableSize, n: unit->classTableSize * sizeof(quint32_le)); |
297 | memcpy(dest: dataPtr + unit->offsetToTemplateObjectTable, src: blockClassAndFunctionOffsets + unit->functionTableSize + unit->classTableSize, n: unit->templateObjectTableSize * sizeof(quint32_le)); |
298 | memcpy(dest: dataPtr + unit->offsetToBlockTable, src: blockClassAndFunctionOffsets + unit->functionTableSize + unit->classTableSize + unit->templateObjectTableSize, n: unit->blockTableSize * sizeof(quint32_le)); |
299 | |
300 | for (int i = 0; i < module->functions.size(); ++i) { |
301 | Context *function = module->functions.at(i); |
302 | if (function == module->rootContext) |
303 | unit->indexOfRootFunction = i; |
304 | |
305 | writeFunction(f: dataPtr + blockClassAndFunctionOffsets[i], irFunction: function); |
306 | } |
307 | |
308 | for (int i = 0; i < module->classes.size(); ++i) { |
309 | const Class &c = module->classes.at(i); |
310 | |
311 | writeClass(f: dataPtr + blockClassAndFunctionOffsets[i + module->functions.size()], c); |
312 | } |
313 | |
314 | for (int i = 0; i < module->templateObjects.size(); ++i) { |
315 | const TemplateObject &t = module->templateObjects.at(i); |
316 | |
317 | writeTemplateObject(f: dataPtr + blockClassAndFunctionOffsets[i + module->functions.size() + module->classes.size()], o: t); |
318 | } |
319 | |
320 | for (int i = 0; i < module->blocks.size(); ++i) { |
321 | Context *block = module->blocks.at(i); |
322 | |
323 | writeBlock(f: dataPtr + blockClassAndFunctionOffsets[i + module->classes.size() + module->templateObjects.size() + module->functions.size()], irBlock: block); |
324 | } |
325 | |
326 | CompiledData::Lookup *lookupsToWrite = reinterpret_cast<CompiledData::Lookup*>(dataPtr + unit->offsetToLookupTable); |
327 | for (const CompiledData::Lookup &l : std::as_const(t&: lookups)) |
328 | *lookupsToWrite++ = l; |
329 | |
330 | CompiledData::RegExp *regexpTable = reinterpret_cast<CompiledData::RegExp *>(dataPtr + unit->offsetToRegexpTable); |
331 | if (regexps.size()) |
332 | memcpy(dest: regexpTable, src: regexps.constData(), n: regexps.size() * sizeof(*regexpTable)); |
333 | |
334 | #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
335 | ReturnedValue *constantTable = reinterpret_cast<ReturnedValue *>(dataPtr + unit->offsetToConstantTable); |
336 | if (constants.size()) |
337 | memcpy(dest: constantTable, src: constants.constData(), n: constants.size() * sizeof(ReturnedValue)); |
338 | #else |
339 | quint64_le *constantTable = reinterpret_cast<quint64_le *>(dataPtr + unit->offsetToConstantTable); |
340 | for (int i = 0; i < constants.count(); ++i) |
341 | constantTable[i] = constants.at(i); |
342 | #endif |
343 | |
344 | { |
345 | if (jsClassData.size()) |
346 | memcpy(dest: dataPtr + jsClassDataOffset, src: jsClassData.constData(), n: jsClassData.size()); |
347 | |
348 | // write js classes and js class lookup table |
349 | quint32_le *jsClassOffsetTable = reinterpret_cast<quint32_le *>(dataPtr + unit->offsetToJSClassTable); |
350 | for (int i = 0; i < jsClassOffsets.size(); ++i) |
351 | jsClassOffsetTable[i] = jsClassDataOffset + jsClassOffsets.at(i); |
352 | } |
353 | |
354 | if (translations.size()) { |
355 | memcpy(dest: dataPtr + unit->offsetToTranslationTable, src: translations.constData(), n: translations.size() * sizeof(CompiledData::TranslationData)); |
356 | } |
357 | |
358 | { |
359 | const auto populateExportEntryTable = [this, dataPtr](const QVector<Compiler::ExportEntry> &table, quint32_le offset) { |
360 | CompiledData::ExportEntry *entryToWrite = reinterpret_cast<CompiledData::ExportEntry *>(dataPtr + offset); |
361 | for (const Compiler::ExportEntry &entry: table) { |
362 | entryToWrite->exportName = getStringId(string: entry.exportName); |
363 | entryToWrite->moduleRequest = getStringId(string: entry.moduleRequest); |
364 | entryToWrite->importName = getStringId(string: entry.importName); |
365 | entryToWrite->localName = getStringId(string: entry.localName); |
366 | entryToWrite->location = entry.location; |
367 | entryToWrite++; |
368 | } |
369 | }; |
370 | populateExportEntryTable(module->localExportEntries, unit->offsetToLocalExportEntryTable); |
371 | populateExportEntryTable(module->indirectExportEntries, unit->offsetToIndirectExportEntryTable); |
372 | populateExportEntryTable(module->starExportEntries, unit->offsetToStarExportEntryTable); |
373 | } |
374 | |
375 | { |
376 | CompiledData::ImportEntry *entryToWrite = reinterpret_cast<CompiledData::ImportEntry *>(dataPtr + unit->offsetToImportEntryTable); |
377 | for (const Compiler::ImportEntry &entry: module->importEntries) { |
378 | entryToWrite->moduleRequest = getStringId(string: entry.moduleRequest); |
379 | entryToWrite->importName = getStringId(string: entry.importName); |
380 | entryToWrite->localName = getStringId(string: entry.localName); |
381 | entryToWrite->location = entry.location; |
382 | entryToWrite++; |
383 | } |
384 | } |
385 | |
386 | { |
387 | quint32_le *moduleRequestEntryToWrite = reinterpret_cast<quint32_le *>(dataPtr + unit->offsetToModuleRequestTable); |
388 | for (const QString &moduleRequest: module->moduleRequests) { |
389 | *moduleRequestEntryToWrite = getStringId(string: moduleRequest); |
390 | moduleRequestEntryToWrite++; |
391 | } |
392 | } |
393 | |
394 | // write strings and string table |
395 | if (option == GenerateWithStringTable) |
396 | stringTable.serialize(unit); |
397 | |
398 | generateUnitChecksum(unit); |
399 | |
400 | return unit; |
401 | } |
402 | |
403 | void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::Compiler::Context *irFunction) const |
404 | { |
405 | QV4::CompiledData::Function *function = (QV4::CompiledData::Function *)f; |
406 | |
407 | quint32 currentOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: sizeof(*function))); |
408 | |
409 | function->nameIndex = getStringId(string: irFunction->name); |
410 | function->flags = 0; |
411 | if (irFunction->isStrict) |
412 | function->flags |= CompiledData::Function::IsStrict; |
413 | if (irFunction->isArrowFunction) |
414 | function->flags |= CompiledData::Function::IsArrowFunction; |
415 | if (irFunction->isGenerator) |
416 | function->flags |= CompiledData::Function::IsGenerator; |
417 | if (irFunction->returnsClosure) |
418 | function->flags |= CompiledData::Function::IsClosureWrapper; |
419 | |
420 | if (!irFunction->returnsClosure |
421 | || irFunction->innerFunctionAccessesThis |
422 | || irFunction->innerFunctionAccessesNewTarget) { |
423 | // If the inner function does things with this and new.target we need to do some work in |
424 | // the outer function. Then we shouldn't directly access the nested function. |
425 | function->nestedFunctionIndex = std::numeric_limits<uint32_t>::max(); |
426 | } else { |
427 | // Otherwise we can directly use the nested function. |
428 | function->nestedFunctionIndex |
429 | = quint32(module->functions.indexOf(t: irFunction->nestedContexts.first())); |
430 | } |
431 | |
432 | function->length = irFunction->formals ? irFunction->formals->length() : 0; |
433 | function->nFormals = irFunction->arguments.size(); |
434 | function->formalsOffset = currentOffset; |
435 | currentOffset += function->nFormals * sizeof(CompiledData::Parameter); |
436 | |
437 | const auto idGenerator = [this](const QString &str) { return getStringId(string: str); }; |
438 | |
439 | QmlIR::Parameter::initType(type: &function->returnType, idGenerator, annotation: irFunction->returnType); |
440 | |
441 | function->sizeOfLocalTemporalDeadZone = irFunction->sizeOfLocalTemporalDeadZone; |
442 | function->sizeOfRegisterTemporalDeadZone = irFunction->sizeOfRegisterTemporalDeadZone; |
443 | function->firstTemporalDeadZoneRegister = irFunction->firstTemporalDeadZoneRegister; |
444 | |
445 | function->nLocals = irFunction->locals.size(); |
446 | function->localsOffset = currentOffset; |
447 | currentOffset += function->nLocals * sizeof(quint32); |
448 | |
449 | function->nLineAndStatementNumbers |
450 | = irFunction->lineAndStatementNumberMapping.size(); |
451 | Q_ASSERT(function->lineAndStatementNumberOffset() == currentOffset); |
452 | currentOffset += function->nLineAndStatementNumbers |
453 | * sizeof(CompiledData::CodeOffsetToLineAndStatement); |
454 | |
455 | function->nRegisters = irFunction->registerCountInFunction; |
456 | |
457 | if (!irFunction->labelInfo.empty()) { |
458 | function->nLabelInfos = quint32(irFunction->labelInfo.size()); |
459 | Q_ASSERT(function->labelInfosOffset() == currentOffset); |
460 | currentOffset += function->nLabelInfos * sizeof(quint32); |
461 | } |
462 | |
463 | function->location.set(line: irFunction->line, column: irFunction->column); |
464 | |
465 | function->codeOffset = currentOffset; |
466 | function->codeSize = irFunction->code.size(); |
467 | |
468 | // write formals |
469 | CompiledData::Parameter *formals = (CompiledData::Parameter *)(f + function->formalsOffset); |
470 | for (int i = 0; i < irFunction->arguments.size(); ++i) { |
471 | auto *formal = &formals[i]; |
472 | formal->nameIndex = getStringId(string: irFunction->arguments.at(i).id); |
473 | if (QQmlJS::AST::TypeAnnotation *annotation = irFunction->arguments.at(i).typeAnnotation.data()) |
474 | QmlIR::Parameter::initType(type: &formal->type, idGenerator, annotation: annotation->type); |
475 | } |
476 | |
477 | // write locals |
478 | quint32_le *locals = (quint32_le *)(f + function->localsOffset); |
479 | for (int i = 0; i < irFunction->locals.size(); ++i) |
480 | locals[i] = getStringId(string: irFunction->locals.at(i)); |
481 | |
482 | // write line and statement numbers |
483 | memcpy(dest: f + function->lineAndStatementNumberOffset(), |
484 | src: irFunction->lineAndStatementNumberMapping.constData(), |
485 | n: irFunction->lineAndStatementNumberMapping.size() |
486 | * sizeof(CompiledData::CodeOffsetToLineAndStatement)); |
487 | |
488 | quint32_le *labels = (quint32_le *)(f + function->labelInfosOffset()); |
489 | for (unsigned u : irFunction->labelInfo) { |
490 | *labels++ = u; |
491 | } |
492 | |
493 | // write byte code |
494 | memcpy(dest: f + function->codeOffset, src: irFunction->code.constData(), n: irFunction->code.size()); |
495 | } |
496 | |
497 | static_assert(int(QV4::Compiler::Class::Method::Regular) == int(QV4::CompiledData::Method::Regular), "Incompatible layout" ); |
498 | static_assert(int(QV4::Compiler::Class::Method::Getter) == int(QV4::CompiledData::Method::Getter), "Incompatible layout" ); |
499 | static_assert(int(QV4::Compiler::Class::Method::Setter) == int(QV4::CompiledData::Method::Setter), "Incompatible layout" ); |
500 | |
501 | void QV4::Compiler::JSUnitGenerator::writeClass(char *b, const QV4::Compiler::Class &c) |
502 | { |
503 | QV4::CompiledData::Class *cls = reinterpret_cast<QV4::CompiledData::Class *>(b); |
504 | |
505 | quint32 currentOffset = sizeof(QV4::CompiledData::Class); |
506 | |
507 | QVector<Class::Method> allMethods = c.staticMethods; |
508 | allMethods += c.methods; |
509 | |
510 | cls->constructorFunction = c.constructorIndex; |
511 | cls->nameIndex = c.nameIndex; |
512 | cls->nMethods = c.methods.size(); |
513 | cls->nStaticMethods = c.staticMethods.size(); |
514 | cls->methodTableOffset = currentOffset; |
515 | CompiledData::Method *method = reinterpret_cast<CompiledData::Method *>(b + currentOffset); |
516 | |
517 | // write methods |
518 | for (int i = 0; i < allMethods.size(); ++i) { |
519 | method->name = allMethods.at(i).nameIndex; |
520 | method->type = allMethods.at(i).type; |
521 | method->function = allMethods.at(i).functionIndex; |
522 | ++method; |
523 | } |
524 | |
525 | static const bool showCode = qEnvironmentVariableIsSet(varName: "QV4_SHOW_BYTECODE" ); |
526 | if (showCode) { |
527 | qDebug() << "=== Class" << stringForIndex(index: cls->nameIndex) << "static methods" |
528 | << cls->nStaticMethods << "methods" << cls->nMethods; |
529 | qDebug() << " constructor:" << cls->constructorFunction; |
530 | for (uint i = 0; i < cls->nStaticMethods + cls->nMethods; ++i) { |
531 | QDebug output = qDebug().nospace(); |
532 | output << " " << i << ": " ; |
533 | if (i < cls->nStaticMethods) |
534 | output << "static " ; |
535 | switch (cls->methodTable()[i].type) { |
536 | case CompiledData::Method::Getter: |
537 | output << "get " ; break; |
538 | case CompiledData::Method::Setter: |
539 | output << "set " ; break; |
540 | default: |
541 | break; |
542 | } |
543 | output << stringForIndex(index: cls->methodTable()[i].name) << " " |
544 | << cls->methodTable()[i].function; |
545 | } |
546 | qDebug().space(); |
547 | } |
548 | } |
549 | |
550 | void QV4::Compiler::JSUnitGenerator::writeTemplateObject(char *b, const QV4::Compiler::TemplateObject &t) |
551 | { |
552 | QV4::CompiledData::TemplateObject *tmpl = reinterpret_cast<QV4::CompiledData::TemplateObject *>(b); |
553 | tmpl->size = t.strings.size(); |
554 | |
555 | quint32 currentOffset = sizeof(QV4::CompiledData::TemplateObject); |
556 | |
557 | quint32_le *strings = reinterpret_cast<quint32_le *>(b + currentOffset); |
558 | |
559 | // write methods |
560 | for (int i = 0; i < t.strings.size(); ++i) |
561 | strings[i] = t.strings.at(i); |
562 | strings += t.strings.size(); |
563 | |
564 | for (int i = 0; i < t.rawStrings.size(); ++i) |
565 | strings[i] = t.rawStrings.at(i); |
566 | |
567 | static const bool showCode = qEnvironmentVariableIsSet(varName: "QV4_SHOW_BYTECODE" ); |
568 | if (showCode) { |
569 | qDebug() << "=== TemplateObject size" << tmpl->size; |
570 | for (uint i = 0; i < tmpl->size; ++i) { |
571 | qDebug() << " " << i << stringForIndex(index: tmpl->stringIndexAt(i)); |
572 | qDebug() << " raw: " << stringForIndex(index: tmpl->rawStringIndexAt(i)); |
573 | } |
574 | qDebug(); |
575 | } |
576 | } |
577 | |
578 | void QV4::Compiler::JSUnitGenerator::writeBlock(char *b, QV4::Compiler::Context *irBlock) const |
579 | { |
580 | QV4::CompiledData::Block *block = reinterpret_cast<QV4::CompiledData::Block *>(b); |
581 | |
582 | quint32 currentOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: sizeof(*block))); |
583 | |
584 | block->sizeOfLocalTemporalDeadZone = irBlock->sizeOfLocalTemporalDeadZone; |
585 | block->nLocals = irBlock->locals.size(); |
586 | block->localsOffset = currentOffset; |
587 | currentOffset += block->nLocals * sizeof(quint32); |
588 | |
589 | // write locals |
590 | quint32_le *locals = (quint32_le *)(b + block->localsOffset); |
591 | for (int i = 0; i < irBlock->locals.size(); ++i) |
592 | locals[i] = getStringId(string: irBlock->locals.at(i)); |
593 | |
594 | static const bool showCode = qEnvironmentVariableIsSet(varName: "QV4_SHOW_BYTECODE" ); |
595 | if (showCode) { |
596 | qDebug() << "=== Variables for block" << irBlock->blockIndex; |
597 | for (int i = 0; i < irBlock->locals.size(); ++i) |
598 | qDebug() << " " << i << ":" << locals[i]; |
599 | qDebug(); |
600 | } |
601 | } |
602 | |
603 | QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::(QV4::Compiler::JSUnitGenerator::GeneratorOption option, quint32_le *blockAndFunctionOffsets, uint *jsClassDataOffset) |
604 | { |
605 | CompiledData::Unit unit; |
606 | memset(s: &unit, c: 0, n: sizeof(unit)); |
607 | memcpy(dest: unit.magic, src: CompiledData::magic_str, n: sizeof(unit.magic)); |
608 | unit.flags = QV4::CompiledData::Unit::IsJavascript; |
609 | unit.flags |= module->unitFlags; |
610 | unit.version = QV4_DATA_STRUCTURE_VERSION; |
611 | unit.qtVersion = QT_VERSION; |
612 | qstrcpy(dst: unit.libraryVersionHash, QML_COMPILE_HASH); |
613 | memset(s: unit.md5Checksum, c: 0, n: sizeof(unit.md5Checksum)); |
614 | memset(s: unit.dependencyMD5Checksum, c: 0, n: sizeof(unit.dependencyMD5Checksum)); |
615 | |
616 | quint32 nextOffset = sizeof(CompiledData::Unit); |
617 | |
618 | unit.functionTableSize = module->functions.size(); |
619 | unit.offsetToFunctionTable = nextOffset; |
620 | nextOffset += unit.functionTableSize * sizeof(uint); |
621 | |
622 | unit.classTableSize = module->classes.size(); |
623 | unit.offsetToClassTable = nextOffset; |
624 | nextOffset += unit.classTableSize * sizeof(uint); |
625 | |
626 | unit.templateObjectTableSize = module->templateObjects.size(); |
627 | unit.offsetToTemplateObjectTable = nextOffset; |
628 | nextOffset += unit.templateObjectTableSize * sizeof(uint); |
629 | |
630 | unit.blockTableSize = module->blocks.size(); |
631 | unit.offsetToBlockTable = nextOffset; |
632 | nextOffset += unit.blockTableSize * sizeof(uint); |
633 | |
634 | unit.lookupTableSize = lookups.size(); |
635 | unit.offsetToLookupTable = nextOffset; |
636 | nextOffset += unit.lookupTableSize * sizeof(CompiledData::Lookup); |
637 | |
638 | unit.regexpTableSize = regexps.size(); |
639 | unit.offsetToRegexpTable = nextOffset; |
640 | nextOffset += unit.regexpTableSize * sizeof(CompiledData::RegExp); |
641 | |
642 | unit.constantTableSize = constants.size(); |
643 | |
644 | // Ensure we load constants from well-aligned addresses into for example SSE registers. |
645 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 16, x: nextOffset)); |
646 | unit.offsetToConstantTable = nextOffset; |
647 | nextOffset += unit.constantTableSize * sizeof(ReturnedValue); |
648 | |
649 | unit.jsClassTableSize = jsClassOffsets.size(); |
650 | unit.offsetToJSClassTable = nextOffset; |
651 | nextOffset += unit.jsClassTableSize * sizeof(uint); |
652 | |
653 | *jsClassDataOffset = nextOffset; |
654 | nextOffset += jsClassData.size(); |
655 | |
656 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
657 | |
658 | unit.translationTableSize = translations.size(); |
659 | unit.offsetToTranslationTable = nextOffset; |
660 | nextOffset += unit.translationTableSize * sizeof(CompiledData::TranslationData); |
661 | |
662 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
663 | |
664 | const auto reserveExportTable = [&nextOffset](int count, quint32_le *tableSizePtr, quint32_le *offsetPtr) { |
665 | *tableSizePtr = count; |
666 | *offsetPtr = nextOffset; |
667 | nextOffset += count * sizeof(CompiledData::ExportEntry); |
668 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
669 | }; |
670 | |
671 | reserveExportTable(module->localExportEntries.size(), &unit.localExportEntryTableSize, &unit.offsetToLocalExportEntryTable); |
672 | reserveExportTable(module->indirectExportEntries.size(), &unit.indirectExportEntryTableSize, &unit.offsetToIndirectExportEntryTable); |
673 | reserveExportTable(module->starExportEntries.size(), &unit.starExportEntryTableSize, &unit.offsetToStarExportEntryTable); |
674 | |
675 | unit.importEntryTableSize = module->importEntries.size(); |
676 | unit.offsetToImportEntryTable = nextOffset; |
677 | nextOffset += unit.importEntryTableSize * sizeof(CompiledData::ImportEntry); |
678 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
679 | |
680 | unit.moduleRequestTableSize = module->moduleRequests.size(); |
681 | unit.offsetToModuleRequestTable = nextOffset; |
682 | nextOffset += unit.moduleRequestTableSize * sizeof(uint); |
683 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
684 | |
685 | quint32 functionSize = 0; |
686 | for (int i = 0; i < module->functions.size(); ++i) { |
687 | Context *f = module->functions.at(i); |
688 | blockAndFunctionOffsets[i] = nextOffset; |
689 | |
690 | quint32 size = QV4::CompiledData::Function::calculateSize( |
691 | nFormals: f->arguments.size(), nLocals: f->locals.size(), nLinesAndStatements: f->lineAndStatementNumberMapping.size(), |
692 | nInnerfunctions: f->nestedContexts.size(), labelInfoSize: int(f->labelInfo.size()), codeSize: f->code.size()); |
693 | functionSize += size - f->code.size(); |
694 | nextOffset += size; |
695 | } |
696 | |
697 | blockAndFunctionOffsets += module->functions.size(); |
698 | |
699 | for (int i = 0; i < module->classes.size(); ++i) { |
700 | const Class &c = module->classes.at(i); |
701 | blockAndFunctionOffsets[i] = nextOffset; |
702 | |
703 | nextOffset += QV4::CompiledData::Class::calculateSize(nStaticMethods: c.staticMethods.size(), nMethods: c.methods.size()); |
704 | } |
705 | blockAndFunctionOffsets += module->classes.size(); |
706 | |
707 | for (int i = 0; i < module->templateObjects.size(); ++i) { |
708 | const TemplateObject &t = module->templateObjects.at(i); |
709 | blockAndFunctionOffsets[i] = nextOffset; |
710 | |
711 | nextOffset += QV4::CompiledData::TemplateObject::calculateSize(size: t.strings.size()); |
712 | } |
713 | blockAndFunctionOffsets += module->templateObjects.size(); |
714 | |
715 | for (int i = 0; i < module->blocks.size(); ++i) { |
716 | Context *c = module->blocks.at(i); |
717 | blockAndFunctionOffsets[i] = nextOffset; |
718 | |
719 | nextOffset += QV4::CompiledData::Block::calculateSize(nLocals: c->locals.size()); |
720 | } |
721 | |
722 | if (option == GenerateWithStringTable) { |
723 | unit.stringTableSize = stringTable.stringCount(); |
724 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
725 | unit.offsetToStringTable = nextOffset; |
726 | nextOffset += stringTable.sizeOfTableAndData(); |
727 | } else { |
728 | unit.stringTableSize = 0; |
729 | unit.offsetToStringTable = 0; |
730 | } |
731 | unit.indexOfRootFunction = -1; |
732 | unit.sourceFileIndex = getStringId(string: module->fileName); |
733 | unit.finalUrlIndex = getStringId(string: module->finalUrl); |
734 | unit.sourceTimeStamp = module->sourceTimeStamp.isValid() ? module->sourceTimeStamp.toMSecsSinceEpoch() : 0; |
735 | unit.offsetToQmlUnit = 0; |
736 | |
737 | unit.unitSize = nextOffset; |
738 | |
739 | static const bool showStats = qEnvironmentVariableIsSet(varName: "QML_SHOW_UNIT_STATS" ); |
740 | if (showStats) { |
741 | qDebug() << "Generated JS unit that is" << unit.unitSize << "bytes contains:" ; |
742 | qDebug() << " " << functionSize << "bytes for non-code function data for" << unit.functionTableSize << "functions" ; |
743 | qDebug() << " " << translations.size() * sizeof(CompiledData::TranslationData) << "bytes for" << translations.size() << "translations" ; |
744 | } |
745 | |
746 | return unit; |
747 | } |
748 | |