// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include using namespace std; enum Flags { NoFlags = 0, Optimize = 1 << 1, DebugInfo = 1 << 2 }; struct Case { Case() {} Case(const QByteArray &f, const QByteArray &c) : file(f), code(c) {} QByteArray file; QByteArray code; QByteArray gist; QByteArray extraCxxFlags; bool useExceptions; }; struct Suite { Suite() : flags(0) {} void clear() { cases.clear(); } QByteArray title; int flags; QByteArray cmd; QVector cases; }; Q_DECLARE_METATYPE(Case) Q_DECLARE_METATYPE(Suite) struct TempStuff { TempStuff() : buildTemp(QLatin1String("qt_tst_codesize")) { buildPath = buildTemp.path(); buildTemp.setAutoRemove(false); QVERIFY(!buildPath.isEmpty()); } QByteArray input; QTemporaryDir buildTemp; QString buildPath; }; class tst_CodeSize : public QObject { Q_OBJECT public: tst_CodeSize() { m_makeBinary = "make"; #ifdef Q_OS_WIN # ifdef Q_CC_MSVC m_makeBinary = "nmake"; # else m_makeBinary = "mingw32-make"; # endif #endif m_keepTemp = false; m_forceKeepTemp = false; } private slots: void initTestCase(); void codesize(); void codesize_data(); void init(); void cleanup(); private: void disarm() { t->buildTemp.setAutoRemove(!keepTemp()); } bool keepTemp() const { return m_keepTemp || m_forceKeepTemp; } TempStuff *t; QByteArray m_qmakeBinary; QProcessEnvironment m_env; QString m_makeBinary; bool m_keepTemp; bool m_forceKeepTemp; }; void tst_CodeSize::initTestCase() { m_qmakeBinary = qgetenv("QTC_QMAKE_PATH_FOR_TEST"); if (m_qmakeBinary.isEmpty()) m_qmakeBinary = "qmake"; qDebug() << "QMake : " << m_qmakeBinary.constData(); m_forceKeepTemp = qgetenv("QTC_KEEP_TEMP_FOR_TEST").toInt(); qDebug() << "Force keep temp : " << m_forceKeepTemp; } void tst_CodeSize::init() { t = new TempStuff(); } void tst_CodeSize::cleanup() { if (!t->buildTemp.autoRemove()) { QFile logger(t->buildPath + QLatin1String("/input.txt")); logger.open(QIODevice::ReadWrite); logger.write(t->input); } delete t; } void tst_CodeSize::codesize() { QFETCH(const Suite, suite); static int suiteCount = 0; ++suiteCount; QFile bigPro(t->buildPath + QLatin1String("/doit.pro")); QVERIFY(bigPro.open(QIODevice::ReadWrite)); bigPro.write("\nTEMPLATE = subdirs\n"); bigPro.write("\nCONFIG += ordered\n"); QFile mainPro(t->buildPath + "/main.pro"); QVERIFY(mainPro.open(QIODevice::ReadWrite)); mainPro.write("\n\nSOURCES += main.cpp"); mainPro.write("\nCONFIG += c++11\n"); QFile mainCpp(t->buildPath + QLatin1String("/main.cpp")); QVERIFY(mainCpp.open(QIODevice::ReadWrite)); mainCpp.write("\n" "int main()\n" "{\n" "}\n"); mainCpp.close(); for (const Case &c : suite.cases) { QByteArray caseProName = c.file + ".pro"; bigPro.write("\nSUBDIRS += " + caseProName); mainPro.write("\nLIBS += -l" + c.file); QFile casePro(t->buildPath + '/' + caseProName); QVERIFY(casePro.open(QIODevice::ReadWrite)); casePro.write("\nTEMPLATE = lib"); casePro.write("\nTARGET = " + c.file + "\n"); casePro.write("\nCONFIG += staticlib\n"); casePro.write("\nCONFIG += c++11\n"); casePro.write("\nCONFIG -= app_bundle\n"); if (suite.flags & Optimize) { casePro.write("\nCONFIG -= debug"); casePro.write("\nCONFIG += release"); } else { casePro.write("\nCONFIG -= release"); casePro.write("\nCONFIG += debug"); } casePro.write("\nSOURCES += " + c.file + ".cpp"); if (!c.extraCxxFlags.isEmpty()) casePro.write("\nQMAKE_CXXFLAGS += " + c.extraCxxFlags); QFile caseCpp(t->buildPath + QLatin1Char('/') + QLatin1String(c.file + ".cpp")); QVERIFY(caseCpp.open(QIODevice::ReadWrite)); QByteArray fullCode = c.code; caseCpp.write(fullCode); caseCpp.close(); } #ifdef Q_OS_WIN mainPro.write("\nLIBS += -L$$OUT_PWD\\release"); #else mainPro.write("\nLIBS += -L$$OUT_PWD"); #endif mainPro.close(); bigPro.write("\nSUBDIRS += main.pro"); bigPro.close(); QProcess qmake; qmake.setWorkingDirectory(t->buildPath); qDebug() << "Starting qmake: " << QString::fromLatin1(m_qmakeBinary + " -r doit.pro"); qmake.start(QString::fromLatin1(m_qmakeBinary), {"-r", "doit.pro"}); // QVERIFY(qmake.waitForFinished()); qmake.waitForFinished(); QByteArray output = qmake.readAllStandardOutput(); QByteArray error = qmake.readAllStandardError(); //qDebug() << "stdout: " << output; if (!error.isEmpty()) { qDebug() << error; QVERIFY(false); } QProcess make; make.setWorkingDirectory(t->buildPath); make.setProcessEnvironment(m_env); make.start(m_makeBinary, QStringList()); QVERIFY(make.waitForFinished()); output = make.readAllStandardOutput(); error = make.readAllStandardError(); //qDebug() << "stdout: " << output; if (make.exitCode()) { qDebug() << error; // qDebug() << "\n------------------ CODE --------------------"; // qDebug() << fullCode; // qDebug() << "\n------------------ CODE --------------------"; qDebug() << ".pro: " << qPrintable(bigPro.fileName()); } cout << "\n################################################################\n\n" << suite.title.data(); bool ok = true; int i = 0; for (const Case &c : std::as_const(suite.cases)) { ++i; cout << "\n\n===================== VARIANT " << suiteCount << '.' << i << ' ' << " ================================" << "\n\nCode: " << c.gist.data() << "\nOptimized: " << ((suite.flags & Optimize) ? "Yes" : "No") << "\nExtra CXX Flags: " << c.extraCxxFlags.data(); #ifdef Q_OS_WIN # ifdef Q_CC_MSVC QString arguments = QString("release\\" + c.file + ".obj"); # else QString arguments = QString("release\\" + c.file + ".o"); # endif #else QString arguments = QString(c.file + ".o"); #endif const int index = suite.cmd.indexOf(' '); QString command = suite.cmd.left(index); arguments = QString::fromLatin1(suite.cmd.mid(index + 1)) + ' ' + arguments; QProcess final; final.setWorkingDirectory(t->buildPath); final.setProcessEnvironment(m_env); cout << "\nCommand: " << suite.cmd.data() << arguments.data() << "\n\n--------------------- OUTPUT " << suiteCount << '.' << i << ' ' << " ---------------------------------\n\n"; final.start(command, arguments.split(' ')); QVERIFY(final.waitForFinished()); QByteArray stdOut = final.readAllStandardOutput(); QByteArray stdErr = final.readAllStandardError(); cout << stdOut.data(); if (!stdErr.isEmpty()) { ok = false; qDebug() << "ERR: " << stdErr; break; } } cout << "\n#################################################################\n"; QVERIFY(ok); disarm(); } void tst_CodeSize::codesize_data() { QTest::addColumn("suite"); Case c; Suite s; s.flags = Optimize; // FIXME: Cannot be hardcoded. Assume matching qmake for now. #ifdef Q_CC_MSVC s.cmd = "dumpbin /DISASM /SECTION:.text"; #else # ifdef Q_OS_MAC s.cmd = "otool -t -v"; # else s.cmd = "objdump -D -j.text"; # endif #endif s.title = "This 'test' compares different approaches to return something \n" "like an immutable string from a function."; c.file = "latin1string"; c.gist = "QString f1() { return QLatin1String(\"foo\"); }\n"; c.code = "#include \n" + c.gist; s.cases.append(c); c.file = "qstringliteral"; c.gist = "QString f2() { return QStringLiteral(\"foo\"); }\n"; c.code = "#include \n" + c.gist; s.cases.append(c); c.file = "std_string"; c.gist = "std::string f3() { return \"foo\"; }\n"; c.code = "#include \n" + c.gist; s.cases.append(c); c.file = "std_string_2"; c.gist = "std::string f4() { return \"foo\"; }\n"; c.code = "#include \n" + c.gist; c.extraCxxFlags = "-fno-stack-protector"; s.cases.append(c); c.file = "char_pointer"; c.gist = "const char *f5() { return \"foo\"; }\n"; c.code = c.gist; s.cases.append(c); QTest::newRow("return_string") << s; s.clear(); c.file = "one"; c.gist = "one"; c.code = "#include \n" "int x(int a) { return a * a; }\n" "void bar(std::function);\n" "void foo() { bar([] { return x(1); }); }\n"; s.cases.append(c); c.file = "two"; c.gist = "two"; c.code = "#include \n" "int x(int a) { return a * a; }\n" "void bar(std::function);\n" "void foo1() { int a = 1; bar([a] { return x(a); }); }\n" "void foo2() { int a = 2; bar([a] { return x(a); }); }\n"; s.cases.append(c); QTest::newRow("lambdas") << s; s.clear(); QByteArray std_tie_code = "struct QMakeStepConfig\n" "{\n" " enum TargetArchConfig { NoArch, X86, X86_64, PowerPC, PowerPC64 };\n" " enum OsType { NoOsType, IphoneSimulator, IphoneOS };\n" "\n" " QMakeStepConfig()\n" " : e1(NoArch),\n" " e2(NoOsType),\n" " b1(false),\n" " b2(false),\n" " b3(false),\n" " b4(false)\n" " {}\n" "\n" " TargetArchConfig e1;\n" " OsType e2;\n" " bool b1;\n" " bool b2;\n" " bool b3;\n" " bool b4;\n" "};\n"; c.file = "std__tie"; c.gist = "std::tie"; c.code = "#include \n" + std_tie_code + "bool operator ==(const QMakeStepConfig &a, const QMakeStepConfig &b) {\n" " return std::tie(a.e1, a.e2, a.b1, a.b2, a.b3, a.b4)\n" " == std::tie(b.e1, b.e2, b.b1, b.b2, b.b3, b.b4);\n" "}\n"; s.cases.append(c); c.file = "conventional"; c.gist = "conventional"; c.code = "" + std_tie_code + "bool operator ==(const QMakeStepConfig &a, const QMakeStepConfig &b) {\n" " return a.e1 == b.e1 && a.e2 == b.e2 && a.b1 == b.b1\n" " && a.b2 == b.b2 && a.b3 == b.b3 && a.b4 == b.b4;\n" "}\n"; s.cases.append(c); QTest::newRow("std_tie") << s; s.clear(); QByteArray mapInitPrefix = "#include \n" "#include \n" "#include \n" "using namespace Qt;\n"; QByteArray mapInitData = " X(\"SPACE\", Key_Space)\n" " X(\"TAB\", Key_Tab)\n" " X(\"NL\", Key_Return)\n" " X(\"NEWLINE\", Key_Return)\n" " X(\"LINEFEED\", Key_Return)\n" " X(\"LF\", Key_Return)\n" " X(\"CR\", Key_Return)\n" " X(\"RETURN\", Key_Return)\n" " X(\"ENTER\", Key_Return)\n" " X(\"BS\", Key_Backspace)\n" " X(\"BACKSPACE\", Key_Backspace)\n" " X(\"ESC\", Key_Escape)\n" " X(\"BAR\", Key_Bar)\n" " X(\"BSLASH\", Key_Backslash)\n" " X(\"DEL\", Key_Delete)\n" " X(\"DELETE\", Key_Delete)\n" " X(\"KDEL\", Key_Delete)\n" " X(\"UP\", Key_Up)\n" " X(\"DOWN\", Key_Down)\n" " X(\"LEFT\", Key_Left)\n" " Y(\"RIGHT\", Key_Right)\n"; c.file = "init_list_std_map_qlatin1string"; c.gist = "init_list_std_map_qlatin1string"; c.code = mapInitPrefix + "#define X(a, b) { QLatin1String(a), b },\n" "#define Y(a, b) { QLatin1String(a), b }\n" "const std::map &vimKeyNames() {\n" " static const std::map k = {\n" + mapInitData + "};\n" " return k;\n" "}"; s.cases.append(c); // Result: 1116 bytes gcc 4.9.1 x86_64 c.file = "init_list_qmap_qlatin1string"; c.gist = "init_list_qmap_qlatin1string"; c.code = mapInitPrefix + "#define X(a, b) { QLatin1String(a), b },\n" "#define Y(a, b) { QLatin1String(a), b }\n" "const QMap &vimKeyNames() {\n" " static const QMap k = {\n" + mapInitData + "};\n" " return k;\n" "}"; s.cases.append(c); // Result: 2953 bytes c.file = "init_list_qmap_qstringliteral"; c.gist = "init_list_qmap_qstringliteral"; c.code = mapInitPrefix + "#define X(a, b) { QStringLiteral(a), b },\n" "#define Y(a, b) { QStringLiteral(a), b }\n" "const QMap &vimKeyNames() {\n" " static const QMap k = {\n" + mapInitData + "};\n" " return k;\n" "}"; s.cases.append(c); // Result: 1286 bytes c.file = "init_list_qmap_insert"; c.gist = "init_list_qmap_insert"; c.code = mapInitPrefix + "#define X(a, b) k.insert(QLatin1String(a), b);\n" "#define Y(a, b) k.insert(QLatin1String(a), b);\n" "const QMap &vimKeyNames() {\n" " static QMap k;\n" " if (k.isEmpty()) {\n" + mapInitData + "\n}\n" " return k;\n" "}"; s.cases.append(c); // Result: 7412 bytes QTest::newRow("map_init") << s; s.clear(); } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); tst_CodeSize test; return QTest::qExec(&test, argc, argv); } #include "tst_codesize.moc"