diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -81,9 +81,12 @@ bool searchDylibsFirst = false; bool saveTemps = false; bool adhocCodesign = false; + bool timeTraceEnabled = false; uint32_t headerPad; uint32_t dylibCompatibilityVersion = 0; uint32_t dylibCurrentVersion = 0; + uint32_t timeTraceGranularity; + std::string progName; llvm::StringRef installName; llvm::StringRef outputFile; llvm::StringRef ltoObjPath; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -41,6 +41,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/TarWriter.h" #include "llvm/Support/TargetSelect.h" +#include "llvm/Support/TimeProfiler.h" #include "llvm/TextAPI/MachO/PackedVersion.h" #include @@ -935,114 +936,137 @@ : "")); } - initLLVM(); // must be run before any call to addFile() + config->progName = argsArr[0]; - // This loop should be reserved for options whose exact ordering matters. - // Other options should be handled via filtered() and/or getLastArg(). - for (const Arg *arg : args) { - const Option &opt = arg->getOption(); - warnIfDeprecatedOption(opt); - warnIfUnimplementedOption(opt); + config->timeTraceEnabled = args.hasArg(OPT_time_trace); - switch (opt.getID()) { - case OPT_INPUT: - addFile(arg->getValue(), false); - break; - case OPT_weak_library: - if (auto *dylibFile = - dyn_cast_or_null(addFile(arg->getValue(), false))) - dylibFile->forceWeakImport = true; - break; - case OPT_filelist: - addFileList(arg->getValue()); - break; - case OPT_force_load: - addFile(arg->getValue(), true); - break; - case OPT_l: - case OPT_weak_l: - addLibrary(arg->getValue(), opt.getID() == OPT_weak_l); - break; - case OPT_framework: - case OPT_weak_framework: - addFramework(arg->getValue(), opt.getID() == OPT_weak_framework); - break; - default: - break; + // Initialize time trace profiler. + if (config->timeTraceEnabled) + timeTraceProfilerInitialize(config->timeTraceGranularity, config->progName); + + { + llvm::TimeTraceScope timeScope("Link", StringRef("ExecuteLinker")); + + initLLVM(); // must be run before any call to addFile() + + // This loop should be reserved for options whose exact ordering matters. + // Other options should be handled via filtered() and/or getLastArg(). + for (const Arg *arg : args) { + const Option &opt = arg->getOption(); + warnIfDeprecatedOption(opt); + warnIfUnimplementedOption(opt); + + switch (opt.getID()) { + case OPT_INPUT: + addFile(arg->getValue(), false); + break; + case OPT_weak_library: + if (auto *dylibFile = + dyn_cast_or_null(addFile(arg->getValue(), false))) + dylibFile->forceWeakImport = true; + break; + case OPT_filelist: + addFileList(arg->getValue()); + break; + case OPT_force_load: + addFile(arg->getValue(), true); + break; + case OPT_l: + case OPT_weak_l: + addLibrary(arg->getValue(), opt.getID() == OPT_weak_l); + break; + case OPT_framework: + case OPT_weak_framework: + addFramework(arg->getValue(), opt.getID() == OPT_weak_framework); + break; + default: + break; + } } - } - config->isPic = config->outputType == MH_DYLIB || - config->outputType == MH_BUNDLE || isPie(args); - - // Now that all dylibs have been loaded, search for those that should be - // re-exported. - for (const Arg *arg : args.filtered(OPT_sub_library, OPT_sub_umbrella)) { - config->hasReexports = true; - StringRef searchName = arg->getValue(); - std::vector extensions; - if (arg->getOption().getID() == OPT_sub_library) - extensions = {".dylib", ".tbd"}; - else - extensions = {".tbd"}; - if (!markReexport(searchName, extensions)) - error(arg->getSpelling() + " " + searchName + - " does not match a supplied dylib"); - } + config->isPic = config->outputType == MH_DYLIB || + config->outputType == MH_BUNDLE || isPie(args); + + // Now that all dylibs have been loaded, search for those that should be + // re-exported. + for (const Arg *arg : args.filtered(OPT_sub_library, OPT_sub_umbrella)) { + config->hasReexports = true; + StringRef searchName = arg->getValue(); + std::vector extensions; + if (arg->getOption().getID() == OPT_sub_library) + extensions = {".dylib", ".tbd"}; + else + extensions = {".tbd"}; + if (!markReexport(searchName, extensions)) + error(arg->getSpelling() + " " + searchName + + " does not match a supplied dylib"); + } - // Parse LTO options. - if (const Arg *arg = args.getLastArg(OPT_mcpu)) - parseClangOption(saver.save("-mcpu=" + StringRef(arg->getValue())), - arg->getSpelling()); + // Parse LTO options. + if (const Arg *arg = args.getLastArg(OPT_mcpu)) + parseClangOption(saver.save("-mcpu=" + StringRef(arg->getValue())), + arg->getSpelling()); - for (const Arg *arg : args.filtered(OPT_mllvm)) - parseClangOption(arg->getValue(), arg->getSpelling()); + for (const Arg *arg : args.filtered(OPT_mllvm)) + parseClangOption(arg->getValue(), arg->getSpelling()); - compileBitcodeFiles(); - replaceCommonSymbols(); + compileBitcodeFiles(); + replaceCommonSymbols(); - StringRef orderFile = args.getLastArgValue(OPT_order_file); - if (!orderFile.empty()) - parseOrderFile(orderFile); + StringRef orderFile = args.getLastArgValue(OPT_order_file); + if (!orderFile.empty()) + parseOrderFile(orderFile); - if (config->outputType == MH_EXECUTE && isa(config->entry)) { - error("undefined symbol: " + toString(*config->entry)); - return false; - } - // FIXME: This prints symbols that are undefined both in input files and - // via -u flag twice. - for (const Symbol *undefined : config->explicitUndefineds) { - if (isa(undefined)) { - error("undefined symbol: " + toString(*undefined) + - "\n>>> referenced by flag -u " + toString(*undefined)); + if (config->outputType == MH_EXECUTE && isa(config->entry)) { + error("undefined symbol: " + toString(*config->entry)); return false; } - } + // FIXME: This prints symbols that are undefined both in input files and + // via -u flag twice. + for (const Symbol *undefined : config->explicitUndefineds) { + if (isa(undefined)) { + error("undefined symbol: " + toString(*undefined) + + "\n>>> referenced by flag -u " + toString(*undefined)); + return false; + } + } - createSyntheticSections(); - symtab->addDSOHandle(in.header); + createSyntheticSections(); + symtab->addDSOHandle(in.header); - for (const Arg *arg : args.filtered(OPT_sectcreate)) { - StringRef segName = arg->getValue(0); - StringRef sectName = arg->getValue(1); - StringRef fileName = arg->getValue(2); - Optional buffer = readFile(fileName); - if (buffer) - inputFiles.insert(make(*buffer, segName, sectName)); - } + for (const Arg *arg : args.filtered(OPT_sectcreate)) { + StringRef segName = arg->getValue(0); + StringRef sectName = arg->getValue(1); + StringRef fileName = arg->getValue(2); + Optional buffer = readFile(fileName); + if (buffer) + inputFiles.insert(make(*buffer, segName, sectName)); + } - // Initialize InputSections. - for (const InputFile *file : inputFiles) { - for (const SubsectionMap &map : file->subsections) { - for (const auto &p : map) { - InputSection *isec = p.second; - inputSections.push_back(isec); + // Initialize InputSections. + for (const InputFile *file : inputFiles) { + for (const SubsectionMap &map : file->subsections) { + for (const auto &p : map) { + InputSection *isec = p.second; + inputSections.push_back(isec); + } } } + + // Write to an output file. + writeResult(); } - // Write to an output file. - writeResult(); + if (config->timeTraceEnabled) { + if (auto E = timeTraceProfilerWrite( + args.getLastArgValue(OPT_time_trace_file_eq).str(), + config->outputFile)) { + handleAllErrors(std::move(E), + [&](const StringError &SE) { error(SE.getMessage()); }); + } + + timeTraceProfilerCleanup(); + } if (canExitEarly) exitLld(errorCount() ? 1 : 0); diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -35,7 +35,10 @@ def no_lto_legacy_pass_manager : Flag<["--"], "no-lto-legacy-pass-manager">, HelpText<"Use the new pass manager in LLVM">, Group; - +def time_trace: Flag<["--"], "time-trace">, HelpText<"Record time trace">; +def time_trace_granularity: Flag<["--"], "time-trace-granularity">, + HelpText<"Minimum time granularity (in microseconds) traced by time profiler">; +def time_trace_file_eq: Flag<["--"], "time-trace-file=">, HelpText<"Specify time trace output file">; // This is a complete Options.td compiled from Apple's ld(1) manpage // dated 2018-03-07 and cross checked with ld64 source code in repo diff --git a/lld/test/MachO/time-trace.s b/lld/test/MachO/time-trace.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/time-trace.s @@ -0,0 +1,42 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o + +# Test implicit trace file name +# RUN: ld.lld --time-trace --time-trace-granularity=0 -o %t1.macho %t.o +# RUN: cat %t1.macho.time-trace \ +# RUN: | %python -c 'import json, sys; json.dump(json.loads(sys.stdin.read()), sys.stdout, sort_keys=True, indent=2)' \ +# RUN: | FileCheck %s + +# Test specified trace file name +# RUN: ld.lld --time-trace --time-trace-file=%t2.json --time-trace-granularity=0 -o %t2.macho %t.o +# RUN: cat %t2.json \ +# RUN: | %python -c 'import json, sys; json.dump(json.loads(sys.stdin.read()), sys.stdout, sort_keys=True, indent=2)' \ +# RUN: | FileCheck %s + +# Test trace requested to stdout +# RUN: ld.lld --time-trace --time-trace-file=- --time-trace-granularity=0 -o %t3.macho %t.o \ +# RUN: | %python -c 'import json, sys; json.dump(json.loads(sys.stdin.read()), sys.stdout, sort_keys=True, indent=2)' \ +# RUN: | FileCheck %s + +# CHECK: "beginningOfTime": {{[0-9]{16},}} +# CHECK-NEXT: "traceEvents": [ + +# Check one event has correct fields +# CHECK: "dur": +# CHECK-NEXT: "name": +# CHECK-NEXT: "ph": +# CHECK-NEXT: "pid": +# CHECK-NEXT: "tid": +# CHECK-NEXT: "ts": + +# Check there is an ExecuteLinker event +# CHECK: "name": "ExecuteLinker" + +# Check process_name entry field +# CHECK: "name": "ld.lld{{(.exe)?}}" +# CHECK: "name": "process_name" +# CHECK: "name": "thread_name" + +.globl _start +_start: + ret