| 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 | // The JSClass object and its members are stored contiguously in the jsClassData. |
| 201 | // In order to get to the members you have to skip over the JSClass, therefore +1. |
| 202 | static constexpr qsizetype jsClassMembersOffset = 1; |
| 203 | |
| 204 | int QV4::Compiler::JSUnitGenerator::registerJSClass(const QStringList &members) |
| 205 | { |
| 206 | // ### re-use existing class definitions. |
| 207 | |
| 208 | const int size = CompiledData::JSClass::calculateSize(nMembers: members.size()); |
| 209 | jsClassOffsets.append(t: jsClassData.size()); |
| 210 | const int oldSize = jsClassData.size(); |
| 211 | jsClassData.resize(size: jsClassData.size() + size); |
| 212 | memset(s: jsClassData.data() + oldSize, c: 0, n: size); |
| 213 | |
| 214 | CompiledData::JSClass *jsClass = reinterpret_cast<CompiledData::JSClass*>(jsClassData.data() + oldSize); |
| 215 | jsClass->nMembers = members.size(); |
| 216 | CompiledData::JSClassMember *member |
| 217 | = reinterpret_cast<CompiledData::JSClassMember*>(jsClass + jsClassMembersOffset); |
| 218 | |
| 219 | for (const auto &name : members) { |
| 220 | member->set(nameOffset: registerString(str: name), isAccessor: false); |
| 221 | ++member; |
| 222 | } |
| 223 | |
| 224 | return jsClassOffsets.size() - 1; |
| 225 | } |
| 226 | |
| 227 | int QV4::Compiler::JSUnitGenerator::jsClassSize(int jsClassId) const |
| 228 | { |
| 229 | const CompiledData::JSClass *jsClass |
| 230 | = reinterpret_cast<const CompiledData::JSClass*>( |
| 231 | jsClassData.data() + jsClassOffsets[jsClassId]); |
| 232 | return jsClass->nMembers; |
| 233 | } |
| 234 | |
| 235 | QString QV4::Compiler::JSUnitGenerator::jsClassMember(int jsClassId, int member) const |
| 236 | { |
| 237 | const CompiledData::JSClass *jsClass = reinterpret_cast<const CompiledData::JSClass*>( |
| 238 | jsClassData.data() + jsClassOffsets[jsClassId]); |
| 239 | Q_ASSERT(member >= 0); |
| 240 | Q_ASSERT(uint(member) < jsClass->nMembers); |
| 241 | const CompiledData::JSClassMember *members |
| 242 | = reinterpret_cast<const CompiledData::JSClassMember*>(jsClass + jsClassMembersOffset); |
| 243 | return stringForIndex(index: members[member].nameOffset()); |
| 244 | } |
| 245 | |
| 246 | int QV4::Compiler::JSUnitGenerator::registerTranslation(const QV4::CompiledData::TranslationData &translation) |
| 247 | { |
| 248 | translations.append(t: translation); |
| 249 | return translations.size() - 1; |
| 250 | } |
| 251 | |
| 252 | QV4::CompiledData::Unit *QV4::Compiler::JSUnitGenerator::generateUnit(GeneratorOption option) |
| 253 | { |
| 254 | const auto registerTypeStrings = [this](QQmlJS::AST::Type *type) { |
| 255 | if (!type) |
| 256 | return; |
| 257 | |
| 258 | if (type->typeArgument) { |
| 259 | registerString(str: type->typeArgument->toString()); |
| 260 | registerString(str: type->typeId->toString()); |
| 261 | } |
| 262 | registerString(str: type->toString()); |
| 263 | }; |
| 264 | |
| 265 | registerString(str: module->fileName); |
| 266 | registerString(str: module->finalUrl); |
| 267 | for (Context *f : std::as_const(t&: module->functions)) { |
| 268 | registerString(str: f->name); |
| 269 | registerTypeStrings(f->returnType); |
| 270 | for (int i = 0; i < f->arguments.size(); ++i) { |
| 271 | registerString(str: f->arguments.at(i).id); |
| 272 | if (const QQmlJS::AST::TypeAnnotation *annotation |
| 273 | = f->arguments.at(i).typeAnnotation.data()) { |
| 274 | registerTypeStrings(annotation->type); |
| 275 | } |
| 276 | } |
| 277 | for (int i = 0; i < f->locals.size(); ++i) |
| 278 | registerString(str: f->locals.at(i)); |
| 279 | } |
| 280 | for (Context *c : std::as_const(t&: module->blocks)) { |
| 281 | for (int i = 0; i < c->locals.size(); ++i) |
| 282 | registerString(str: c->locals.at(i)); |
| 283 | } |
| 284 | { |
| 285 | const auto registerExportEntry = [this](const Compiler::ExportEntry &entry) { |
| 286 | registerString(str: entry.exportName); |
| 287 | registerString(str: entry.moduleRequest); |
| 288 | registerString(str: entry.importName); |
| 289 | registerString(str: entry.localName); |
| 290 | }; |
| 291 | std::for_each(first: module->localExportEntries.constBegin(), last: module->localExportEntries.constEnd(), f: registerExportEntry); |
| 292 | std::for_each(first: module->indirectExportEntries.constBegin(), last: module->indirectExportEntries.constEnd(), f: registerExportEntry); |
| 293 | std::for_each(first: module->starExportEntries.constBegin(), last: module->starExportEntries.constEnd(), f: registerExportEntry); |
| 294 | } |
| 295 | { |
| 296 | for (const auto &entry: module->importEntries) { |
| 297 | registerString(str: entry.moduleRequest); |
| 298 | registerString(str: entry.importName); |
| 299 | registerString(str: entry.localName); |
| 300 | } |
| 301 | |
| 302 | for (const QString &request: module->moduleRequests) |
| 303 | registerString(str: request); |
| 304 | } |
| 305 | |
| 306 | Q_ALLOCA_VAR(quint32_le, blockClassAndFunctionOffsets, (module->functions.size() + module->classes.size() + module->templateObjects.size() + module->blocks.size()) * sizeof(quint32_le)); |
| 307 | uint jsClassDataOffset = 0; |
| 308 | |
| 309 | char *dataPtr; |
| 310 | CompiledData::Unit *unit; |
| 311 | { |
| 312 | QV4::CompiledData::Unit = generateHeader(option, functionOffsets: blockClassAndFunctionOffsets, jsClassDataOffset: &jsClassDataOffset); |
| 313 | dataPtr = reinterpret_cast<char *>(malloc(size: tempHeader.unitSize)); |
| 314 | memset(s: dataPtr, c: 0, n: tempHeader.unitSize); |
| 315 | memcpy(dest: &unit, src: &dataPtr, n: sizeof(CompiledData::Unit*)); |
| 316 | memcpy(dest: unit, src: &tempHeader, n: sizeof(tempHeader)); |
| 317 | } |
| 318 | |
| 319 | memcpy(dest: dataPtr + unit->offsetToFunctionTable, src: blockClassAndFunctionOffsets, n: unit->functionTableSize * sizeof(quint32_le)); |
| 320 | memcpy(dest: dataPtr + unit->offsetToClassTable, src: blockClassAndFunctionOffsets + unit->functionTableSize, n: unit->classTableSize * sizeof(quint32_le)); |
| 321 | memcpy(dest: dataPtr + unit->offsetToTemplateObjectTable, src: blockClassAndFunctionOffsets + unit->functionTableSize + unit->classTableSize, n: unit->templateObjectTableSize * sizeof(quint32_le)); |
| 322 | memcpy(dest: dataPtr + unit->offsetToBlockTable, src: blockClassAndFunctionOffsets + unit->functionTableSize + unit->classTableSize + unit->templateObjectTableSize, n: unit->blockTableSize * sizeof(quint32_le)); |
| 323 | |
| 324 | for (int i = 0; i < module->functions.size(); ++i) { |
| 325 | Context *function = module->functions.at(i); |
| 326 | if (function == module->rootContext) |
| 327 | unit->indexOfRootFunction = i; |
| 328 | |
| 329 | writeFunction(f: dataPtr + blockClassAndFunctionOffsets[i], irFunction: function); |
| 330 | } |
| 331 | |
| 332 | for (int i = 0; i < module->classes.size(); ++i) { |
| 333 | const Class &c = module->classes.at(i); |
| 334 | |
| 335 | writeClass(f: dataPtr + blockClassAndFunctionOffsets[i + module->functions.size()], c); |
| 336 | } |
| 337 | |
| 338 | for (int i = 0; i < module->templateObjects.size(); ++i) { |
| 339 | const TemplateObject &t = module->templateObjects.at(i); |
| 340 | |
| 341 | writeTemplateObject(f: dataPtr + blockClassAndFunctionOffsets[i + module->functions.size() + module->classes.size()], o: t); |
| 342 | } |
| 343 | |
| 344 | for (int i = 0; i < module->blocks.size(); ++i) { |
| 345 | Context *block = module->blocks.at(i); |
| 346 | |
| 347 | writeBlock(f: dataPtr + blockClassAndFunctionOffsets[i + module->classes.size() + module->templateObjects.size() + module->functions.size()], irBlock: block); |
| 348 | } |
| 349 | |
| 350 | CompiledData::Lookup *lookupsToWrite = reinterpret_cast<CompiledData::Lookup*>(dataPtr + unit->offsetToLookupTable); |
| 351 | for (const CompiledData::Lookup &l : std::as_const(t&: lookups)) |
| 352 | *lookupsToWrite++ = l; |
| 353 | |
| 354 | CompiledData::RegExp *regexpTable = reinterpret_cast<CompiledData::RegExp *>(dataPtr + unit->offsetToRegexpTable); |
| 355 | if (regexps.size()) |
| 356 | memcpy(dest: regexpTable, src: regexps.constData(), n: regexps.size() * sizeof(*regexpTable)); |
| 357 | |
| 358 | #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
| 359 | ReturnedValue *constantTable = reinterpret_cast<ReturnedValue *>(dataPtr + unit->offsetToConstantTable); |
| 360 | if (constants.size()) |
| 361 | memcpy(dest: constantTable, src: constants.constData(), n: constants.size() * sizeof(ReturnedValue)); |
| 362 | #else |
| 363 | quint64_le *constantTable = reinterpret_cast<quint64_le *>(dataPtr + unit->offsetToConstantTable); |
| 364 | for (int i = 0; i < constants.count(); ++i) |
| 365 | constantTable[i] = constants.at(i); |
| 366 | #endif |
| 367 | |
| 368 | { |
| 369 | if (jsClassData.size()) |
| 370 | memcpy(dest: dataPtr + jsClassDataOffset, src: jsClassData.constData(), n: jsClassData.size()); |
| 371 | |
| 372 | // write js classes and js class lookup table |
| 373 | quint32_le *jsClassOffsetTable = reinterpret_cast<quint32_le *>(dataPtr + unit->offsetToJSClassTable); |
| 374 | for (int i = 0; i < jsClassOffsets.size(); ++i) |
| 375 | jsClassOffsetTable[i] = jsClassDataOffset + jsClassOffsets.at(i); |
| 376 | } |
| 377 | |
| 378 | if (translations.size()) { |
| 379 | memcpy(dest: dataPtr + unit->offsetToTranslationTable, src: translations.constData(), n: translations.size() * sizeof(CompiledData::TranslationData)); |
| 380 | } |
| 381 | |
| 382 | { |
| 383 | const auto populateExportEntryTable = [this, dataPtr](const QVector<Compiler::ExportEntry> &table, quint32_le offset) { |
| 384 | CompiledData::ExportEntry *entryToWrite = reinterpret_cast<CompiledData::ExportEntry *>(dataPtr + offset); |
| 385 | for (const Compiler::ExportEntry &entry: table) { |
| 386 | entryToWrite->exportName = getStringId(string: entry.exportName); |
| 387 | entryToWrite->moduleRequest = getStringId(string: entry.moduleRequest); |
| 388 | entryToWrite->importName = getStringId(string: entry.importName); |
| 389 | entryToWrite->localName = getStringId(string: entry.localName); |
| 390 | entryToWrite->location = entry.location; |
| 391 | entryToWrite++; |
| 392 | } |
| 393 | }; |
| 394 | populateExportEntryTable(module->localExportEntries, unit->offsetToLocalExportEntryTable); |
| 395 | populateExportEntryTable(module->indirectExportEntries, unit->offsetToIndirectExportEntryTable); |
| 396 | populateExportEntryTable(module->starExportEntries, unit->offsetToStarExportEntryTable); |
| 397 | } |
| 398 | |
| 399 | { |
| 400 | CompiledData::ImportEntry *entryToWrite = reinterpret_cast<CompiledData::ImportEntry *>(dataPtr + unit->offsetToImportEntryTable); |
| 401 | for (const Compiler::ImportEntry &entry: module->importEntries) { |
| 402 | entryToWrite->moduleRequest = getStringId(string: entry.moduleRequest); |
| 403 | entryToWrite->importName = getStringId(string: entry.importName); |
| 404 | entryToWrite->localName = getStringId(string: entry.localName); |
| 405 | entryToWrite->location = entry.location; |
| 406 | entryToWrite++; |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | { |
| 411 | quint32_le *moduleRequestEntryToWrite = reinterpret_cast<quint32_le *>(dataPtr + unit->offsetToModuleRequestTable); |
| 412 | for (const QString &moduleRequest: module->moduleRequests) { |
| 413 | *moduleRequestEntryToWrite = getStringId(string: moduleRequest); |
| 414 | moduleRequestEntryToWrite++; |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | // write strings and string table |
| 419 | if (option == GenerateWithStringTable) |
| 420 | stringTable.serialize(unit); |
| 421 | |
| 422 | generateUnitChecksum(unit); |
| 423 | |
| 424 | return unit; |
| 425 | } |
| 426 | |
| 427 | void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::Compiler::Context *irFunction) const |
| 428 | { |
| 429 | QV4::CompiledData::Function *function = (QV4::CompiledData::Function *)f; |
| 430 | |
| 431 | quint32 currentOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: sizeof(*function))); |
| 432 | |
| 433 | function->nameIndex = getStringId(string: irFunction->name); |
| 434 | function->flags = 0; |
| 435 | if (irFunction->isStrict) |
| 436 | function->flags |= CompiledData::Function::IsStrict; |
| 437 | if (irFunction->isArrowFunction) |
| 438 | function->flags |= CompiledData::Function::IsArrowFunction; |
| 439 | if (irFunction->isGenerator) |
| 440 | function->flags |= CompiledData::Function::IsGenerator; |
| 441 | if (irFunction->returnsClosure) |
| 442 | function->flags |= CompiledData::Function::IsClosureWrapper; |
| 443 | |
| 444 | if (!irFunction->returnsClosure |
| 445 | || irFunction->innerFunctionAccessesThis |
| 446 | || irFunction->innerFunctionAccessesNewTarget) { |
| 447 | // If the inner function does things with this and new.target we need to do some work in |
| 448 | // the outer function. Then we shouldn't directly access the nested function. |
| 449 | function->nestedFunctionIndex = std::numeric_limits<uint32_t>::max(); |
| 450 | } else { |
| 451 | // Otherwise we can directly use the nested function. |
| 452 | function->nestedFunctionIndex |
| 453 | = quint32(module->functions.indexOf(t: irFunction->nestedContexts.first())); |
| 454 | } |
| 455 | |
| 456 | function->length = irFunction->formals ? irFunction->formals->length() : 0; |
| 457 | function->nFormals = irFunction->arguments.size(); |
| 458 | function->formalsOffset = currentOffset; |
| 459 | currentOffset += function->nFormals * sizeof(CompiledData::Parameter); |
| 460 | |
| 461 | const auto idGenerator = [this](const QString &str) { return getStringId(string: str); }; |
| 462 | |
| 463 | QmlIR::Parameter::initType(type: &function->returnType, idGenerator, annotation: irFunction->returnType); |
| 464 | |
| 465 | function->sizeOfLocalTemporalDeadZone = irFunction->sizeOfLocalTemporalDeadZone; |
| 466 | function->sizeOfRegisterTemporalDeadZone = irFunction->sizeOfRegisterTemporalDeadZone; |
| 467 | function->firstTemporalDeadZoneRegister = irFunction->firstTemporalDeadZoneRegister; |
| 468 | |
| 469 | function->nLocals = irFunction->locals.size(); |
| 470 | function->localsOffset = currentOffset; |
| 471 | currentOffset += function->nLocals * sizeof(quint32); |
| 472 | |
| 473 | function->nLineAndStatementNumbers |
| 474 | = irFunction->lineAndStatementNumberMapping.size(); |
| 475 | Q_ASSERT(function->lineAndStatementNumberOffset() == currentOffset); |
| 476 | currentOffset += function->nLineAndStatementNumbers |
| 477 | * sizeof(CompiledData::CodeOffsetToLineAndStatement); |
| 478 | |
| 479 | function->nRegisters = irFunction->registerCountInFunction; |
| 480 | |
| 481 | if (!irFunction->labelInfo.empty()) { |
| 482 | function->nLabelInfos = quint32(irFunction->labelInfo.size()); |
| 483 | Q_ASSERT(function->labelInfosOffset() == currentOffset); |
| 484 | currentOffset += function->nLabelInfos * sizeof(quint32); |
| 485 | } |
| 486 | |
| 487 | function->location.set(line: irFunction->line, column: irFunction->column); |
| 488 | |
| 489 | function->codeOffset = currentOffset; |
| 490 | function->codeSize = irFunction->code.size(); |
| 491 | |
| 492 | // write formals |
| 493 | CompiledData::Parameter *formals = (CompiledData::Parameter *)(f + function->formalsOffset); |
| 494 | for (int i = 0; i < irFunction->arguments.size(); ++i) { |
| 495 | auto *formal = &formals[i]; |
| 496 | formal->nameIndex = getStringId(string: irFunction->arguments.at(i).id); |
| 497 | if (QQmlJS::AST::TypeAnnotation *annotation = irFunction->arguments.at(i).typeAnnotation.data()) |
| 498 | QmlIR::Parameter::initType(type: &formal->type, idGenerator, annotation: annotation->type); |
| 499 | } |
| 500 | |
| 501 | // write locals |
| 502 | quint32_le *locals = (quint32_le *)(f + function->localsOffset); |
| 503 | for (int i = 0; i < irFunction->locals.size(); ++i) |
| 504 | locals[i] = getStringId(string: irFunction->locals.at(i)); |
| 505 | |
| 506 | // write line and statement numbers |
| 507 | memcpy(dest: f + function->lineAndStatementNumberOffset(), |
| 508 | src: irFunction->lineAndStatementNumberMapping.constData(), |
| 509 | n: irFunction->lineAndStatementNumberMapping.size() |
| 510 | * sizeof(CompiledData::CodeOffsetToLineAndStatement)); |
| 511 | |
| 512 | quint32_le *labels = (quint32_le *)(f + function->labelInfosOffset()); |
| 513 | for (unsigned u : irFunction->labelInfo) { |
| 514 | *labels++ = u; |
| 515 | } |
| 516 | |
| 517 | // write byte code |
| 518 | memcpy(dest: f + function->codeOffset, src: irFunction->code.constData(), n: irFunction->code.size()); |
| 519 | } |
| 520 | |
| 521 | static_assert(int(QV4::Compiler::Class::Method::Regular) == int(QV4::CompiledData::Method::Regular), "Incompatible layout" ); |
| 522 | static_assert(int(QV4::Compiler::Class::Method::Getter) == int(QV4::CompiledData::Method::Getter), "Incompatible layout" ); |
| 523 | static_assert(int(QV4::Compiler::Class::Method::Setter) == int(QV4::CompiledData::Method::Setter), "Incompatible layout" ); |
| 524 | |
| 525 | void QV4::Compiler::JSUnitGenerator::writeClass(char *b, const QV4::Compiler::Class &c) |
| 526 | { |
| 527 | QV4::CompiledData::Class *cls = reinterpret_cast<QV4::CompiledData::Class *>(b); |
| 528 | |
| 529 | quint32 currentOffset = sizeof(QV4::CompiledData::Class); |
| 530 | |
| 531 | QVector<Class::Method> allMethods = c.staticMethods; |
| 532 | allMethods += c.methods; |
| 533 | |
| 534 | cls->constructorFunction = c.constructorIndex; |
| 535 | cls->nameIndex = c.nameIndex; |
| 536 | cls->nMethods = c.methods.size(); |
| 537 | cls->nStaticMethods = c.staticMethods.size(); |
| 538 | cls->methodTableOffset = currentOffset; |
| 539 | CompiledData::Method *method = reinterpret_cast<CompiledData::Method *>(b + currentOffset); |
| 540 | |
| 541 | // write methods |
| 542 | for (int i = 0; i < allMethods.size(); ++i) { |
| 543 | method->name = allMethods.at(i).nameIndex; |
| 544 | method->type = allMethods.at(i).type; |
| 545 | method->function = allMethods.at(i).functionIndex; |
| 546 | ++method; |
| 547 | } |
| 548 | |
| 549 | static const bool showCode = qEnvironmentVariableIsSet(varName: "QV4_SHOW_BYTECODE" ); |
| 550 | if (showCode) { |
| 551 | qDebug() << "=== Class" << stringForIndex(index: cls->nameIndex) << "static methods" |
| 552 | << cls->nStaticMethods << "methods" << cls->nMethods; |
| 553 | qDebug() << " constructor:" << cls->constructorFunction; |
| 554 | for (uint i = 0; i < cls->nStaticMethods + cls->nMethods; ++i) { |
| 555 | QDebug output = qDebug().nospace(); |
| 556 | output << " " << i << ": " ; |
| 557 | if (i < cls->nStaticMethods) |
| 558 | output << "static " ; |
| 559 | switch (cls->methodTable()[i].type) { |
| 560 | case CompiledData::Method::Getter: |
| 561 | output << "get " ; break; |
| 562 | case CompiledData::Method::Setter: |
| 563 | output << "set " ; break; |
| 564 | default: |
| 565 | break; |
| 566 | } |
| 567 | output << stringForIndex(index: cls->methodTable()[i].name) << " " |
| 568 | << cls->methodTable()[i].function; |
| 569 | } |
| 570 | qDebug().space(); |
| 571 | } |
| 572 | } |
| 573 | |
| 574 | void QV4::Compiler::JSUnitGenerator::writeTemplateObject(char *b, const QV4::Compiler::TemplateObject &t) |
| 575 | { |
| 576 | QV4::CompiledData::TemplateObject *tmpl = reinterpret_cast<QV4::CompiledData::TemplateObject *>(b); |
| 577 | tmpl->size = t.strings.size(); |
| 578 | |
| 579 | quint32 currentOffset = sizeof(QV4::CompiledData::TemplateObject); |
| 580 | |
| 581 | quint32_le *strings = reinterpret_cast<quint32_le *>(b + currentOffset); |
| 582 | |
| 583 | // write methods |
| 584 | for (int i = 0; i < t.strings.size(); ++i) |
| 585 | strings[i] = t.strings.at(i); |
| 586 | strings += t.strings.size(); |
| 587 | |
| 588 | for (int i = 0; i < t.rawStrings.size(); ++i) |
| 589 | strings[i] = t.rawStrings.at(i); |
| 590 | |
| 591 | static const bool showCode = qEnvironmentVariableIsSet(varName: "QV4_SHOW_BYTECODE" ); |
| 592 | if (showCode) { |
| 593 | qDebug() << "=== TemplateObject size" << tmpl->size; |
| 594 | for (uint i = 0; i < tmpl->size; ++i) { |
| 595 | qDebug() << " " << i << stringForIndex(index: tmpl->stringIndexAt(i)); |
| 596 | qDebug() << " raw: " << stringForIndex(index: tmpl->rawStringIndexAt(i)); |
| 597 | } |
| 598 | qDebug(); |
| 599 | } |
| 600 | } |
| 601 | |
| 602 | void QV4::Compiler::JSUnitGenerator::writeBlock(char *b, QV4::Compiler::Context *irBlock) const |
| 603 | { |
| 604 | QV4::CompiledData::Block *block = reinterpret_cast<QV4::CompiledData::Block *>(b); |
| 605 | |
| 606 | quint32 currentOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: sizeof(*block))); |
| 607 | |
| 608 | block->sizeOfLocalTemporalDeadZone = irBlock->sizeOfLocalTemporalDeadZone; |
| 609 | block->nLocals = irBlock->locals.size(); |
| 610 | block->localsOffset = currentOffset; |
| 611 | currentOffset += block->nLocals * sizeof(quint32); |
| 612 | |
| 613 | // write locals |
| 614 | quint32_le *locals = (quint32_le *)(b + block->localsOffset); |
| 615 | for (int i = 0; i < irBlock->locals.size(); ++i) |
| 616 | locals[i] = getStringId(string: irBlock->locals.at(i)); |
| 617 | |
| 618 | static const bool showCode = qEnvironmentVariableIsSet(varName: "QV4_SHOW_BYTECODE" ); |
| 619 | if (showCode) { |
| 620 | qDebug() << "=== Variables for block" << irBlock->blockIndex; |
| 621 | for (int i = 0; i < irBlock->locals.size(); ++i) |
| 622 | qDebug() << " " << i << ":" << locals[i]; |
| 623 | qDebug(); |
| 624 | } |
| 625 | } |
| 626 | |
| 627 | QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::(QV4::Compiler::JSUnitGenerator::GeneratorOption option, quint32_le *blockAndFunctionOffsets, uint *jsClassDataOffset) |
| 628 | { |
| 629 | CompiledData::Unit unit; |
| 630 | memset(s: &unit, c: 0, n: sizeof(unit)); |
| 631 | memcpy(dest: unit.magic, src: CompiledData::magic_str, n: sizeof(unit.magic)); |
| 632 | unit.flags = QV4::CompiledData::Unit::IsJavascript; |
| 633 | unit.flags |= module->unitFlags; |
| 634 | unit.version = QV4_DATA_STRUCTURE_VERSION; |
| 635 | unit.qtVersion = QT_VERSION; |
| 636 | qstrcpy(dst: unit.libraryVersionHash, QML_COMPILE_HASH); |
| 637 | memset(s: unit.md5Checksum, c: 0, n: sizeof(unit.md5Checksum)); |
| 638 | memset(s: unit.dependencyMD5Checksum, c: 0, n: sizeof(unit.dependencyMD5Checksum)); |
| 639 | |
| 640 | quint32 nextOffset = sizeof(CompiledData::Unit); |
| 641 | |
| 642 | unit.functionTableSize = module->functions.size(); |
| 643 | unit.offsetToFunctionTable = nextOffset; |
| 644 | nextOffset += unit.functionTableSize * sizeof(uint); |
| 645 | |
| 646 | unit.classTableSize = module->classes.size(); |
| 647 | unit.offsetToClassTable = nextOffset; |
| 648 | nextOffset += unit.classTableSize * sizeof(uint); |
| 649 | |
| 650 | unit.templateObjectTableSize = module->templateObjects.size(); |
| 651 | unit.offsetToTemplateObjectTable = nextOffset; |
| 652 | nextOffset += unit.templateObjectTableSize * sizeof(uint); |
| 653 | |
| 654 | unit.blockTableSize = module->blocks.size(); |
| 655 | unit.offsetToBlockTable = nextOffset; |
| 656 | nextOffset += unit.blockTableSize * sizeof(uint); |
| 657 | |
| 658 | unit.lookupTableSize = lookups.size(); |
| 659 | unit.offsetToLookupTable = nextOffset; |
| 660 | nextOffset += unit.lookupTableSize * sizeof(CompiledData::Lookup); |
| 661 | |
| 662 | unit.regexpTableSize = regexps.size(); |
| 663 | unit.offsetToRegexpTable = nextOffset; |
| 664 | nextOffset += unit.regexpTableSize * sizeof(CompiledData::RegExp); |
| 665 | |
| 666 | unit.constantTableSize = constants.size(); |
| 667 | |
| 668 | // Ensure we load constants from well-aligned addresses into for example SSE registers. |
| 669 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 16, x: nextOffset)); |
| 670 | unit.offsetToConstantTable = nextOffset; |
| 671 | nextOffset += unit.constantTableSize * sizeof(ReturnedValue); |
| 672 | |
| 673 | unit.jsClassTableSize = jsClassOffsets.size(); |
| 674 | unit.offsetToJSClassTable = nextOffset; |
| 675 | nextOffset += unit.jsClassTableSize * sizeof(uint); |
| 676 | |
| 677 | *jsClassDataOffset = nextOffset; |
| 678 | nextOffset += jsClassData.size(); |
| 679 | |
| 680 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
| 681 | |
| 682 | unit.translationTableSize = translations.size(); |
| 683 | unit.offsetToTranslationTable = nextOffset; |
| 684 | nextOffset += unit.translationTableSize * sizeof(CompiledData::TranslationData); |
| 685 | if (unit.translationTableSize != 0) { |
| 686 | constexpr auto spaceForTranslationContextId = sizeof(quint32_le); |
| 687 | nextOffset += spaceForTranslationContextId; |
| 688 | } |
| 689 | |
| 690 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
| 691 | |
| 692 | const auto reserveExportTable = [&nextOffset](int count, quint32_le *tableSizePtr, quint32_le *offsetPtr) { |
| 693 | *tableSizePtr = count; |
| 694 | *offsetPtr = nextOffset; |
| 695 | nextOffset += count * sizeof(CompiledData::ExportEntry); |
| 696 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
| 697 | }; |
| 698 | |
| 699 | reserveExportTable(module->localExportEntries.size(), &unit.localExportEntryTableSize, &unit.offsetToLocalExportEntryTable); |
| 700 | reserveExportTable(module->indirectExportEntries.size(), &unit.indirectExportEntryTableSize, &unit.offsetToIndirectExportEntryTable); |
| 701 | reserveExportTable(module->starExportEntries.size(), &unit.starExportEntryTableSize, &unit.offsetToStarExportEntryTable); |
| 702 | |
| 703 | unit.importEntryTableSize = module->importEntries.size(); |
| 704 | unit.offsetToImportEntryTable = nextOffset; |
| 705 | nextOffset += unit.importEntryTableSize * sizeof(CompiledData::ImportEntry); |
| 706 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
| 707 | |
| 708 | unit.moduleRequestTableSize = module->moduleRequests.size(); |
| 709 | unit.offsetToModuleRequestTable = nextOffset; |
| 710 | nextOffset += unit.moduleRequestTableSize * sizeof(uint); |
| 711 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
| 712 | |
| 713 | quint32 functionSize = 0; |
| 714 | for (int i = 0; i < module->functions.size(); ++i) { |
| 715 | Context *f = module->functions.at(i); |
| 716 | blockAndFunctionOffsets[i] = nextOffset; |
| 717 | |
| 718 | quint32 size = QV4::CompiledData::Function::calculateSize( |
| 719 | nFormals: f->arguments.size(), nLocals: f->locals.size(), nLinesAndStatements: f->lineAndStatementNumberMapping.size(), |
| 720 | nInnerfunctions: f->nestedContexts.size(), labelInfoSize: int(f->labelInfo.size()), codeSize: f->code.size()); |
| 721 | functionSize += size - f->code.size(); |
| 722 | nextOffset += size; |
| 723 | } |
| 724 | |
| 725 | blockAndFunctionOffsets += module->functions.size(); |
| 726 | |
| 727 | for (int i = 0; i < module->classes.size(); ++i) { |
| 728 | const Class &c = module->classes.at(i); |
| 729 | blockAndFunctionOffsets[i] = nextOffset; |
| 730 | |
| 731 | nextOffset += QV4::CompiledData::Class::calculateSize(nStaticMethods: c.staticMethods.size(), nMethods: c.methods.size()); |
| 732 | } |
| 733 | blockAndFunctionOffsets += module->classes.size(); |
| 734 | |
| 735 | for (int i = 0; i < module->templateObjects.size(); ++i) { |
| 736 | const TemplateObject &t = module->templateObjects.at(i); |
| 737 | blockAndFunctionOffsets[i] = nextOffset; |
| 738 | |
| 739 | nextOffset += QV4::CompiledData::TemplateObject::calculateSize(size: t.strings.size()); |
| 740 | } |
| 741 | blockAndFunctionOffsets += module->templateObjects.size(); |
| 742 | |
| 743 | for (int i = 0; i < module->blocks.size(); ++i) { |
| 744 | Context *c = module->blocks.at(i); |
| 745 | blockAndFunctionOffsets[i] = nextOffset; |
| 746 | |
| 747 | nextOffset += QV4::CompiledData::Block::calculateSize(nLocals: c->locals.size()); |
| 748 | } |
| 749 | |
| 750 | if (option == GenerateWithStringTable) { |
| 751 | unit.stringTableSize = stringTable.stringCount(); |
| 752 | nextOffset = static_cast<quint32>(QtPrivate::roundUpToMultipleOf(divisor: 8, x: nextOffset)); |
| 753 | unit.offsetToStringTable = nextOffset; |
| 754 | nextOffset += stringTable.sizeOfTableAndData(); |
| 755 | } else { |
| 756 | unit.stringTableSize = 0; |
| 757 | unit.offsetToStringTable = 0; |
| 758 | } |
| 759 | unit.indexOfRootFunction = -1; |
| 760 | unit.sourceFileIndex = getStringId(string: module->fileName); |
| 761 | unit.finalUrlIndex = getStringId(string: module->finalUrl); |
| 762 | unit.sourceTimeStamp = module->sourceTimeStamp.isValid() ? module->sourceTimeStamp.toMSecsSinceEpoch() : 0; |
| 763 | unit.offsetToQmlUnit = 0; |
| 764 | |
| 765 | unit.unitSize = nextOffset; |
| 766 | |
| 767 | static const bool showStats = qEnvironmentVariableIsSet(varName: "QML_SHOW_UNIT_STATS" ); |
| 768 | if (showStats) { |
| 769 | qDebug() << "Generated JS unit that is" << unit.unitSize << "bytes contains:" ; |
| 770 | qDebug() << " " << functionSize << "bytes for non-code function data for" << unit.functionTableSize << "functions" ; |
| 771 | qDebug() << " " << translations.size() * sizeof(CompiledData::TranslationData) << "bytes for" << translations.size() << "translations" ; |
| 772 | } |
| 773 | |
| 774 | return unit; |
| 775 | } |
| 776 | |