Index: llvm/test/tools/sizediff/Inputs/basic-a.opt.yaml =================================================================== --- /dev/null +++ llvm/test/tools/sizediff/Inputs/basic-a.opt.yaml @@ -0,0 +1,27 @@ +--- !Analysis +Pass: asm-printer +Name: InstructionCount +DebugLoc: { File: foo.c, Line: 4, Column: 0 } +Function: foo +Args: + - NumInstructions: '17' + - String: ' instructions in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +DebugLoc: { File: foo.c, Line: 10, Column: 0 } +Function: bar +Args: + - NumInstructions: '26' + - String: ' instructions in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +DebugLoc: { File: foo.c, Line: 10, Column: 0 } +Function: pluto +Args: + - NumInstructions: '24' + - String: ' instructions in function' +... Index: llvm/test/tools/sizediff/Inputs/basic-b.opt.yaml =================================================================== --- /dev/null +++ llvm/test/tools/sizediff/Inputs/basic-b.opt.yaml @@ -0,0 +1,26 @@ +--- !Analysis +Pass: asm-printer +Name: InstructionCount +DebugLoc: { File: foo.c, Line: 4, Column: 0 } +Function: foo +Args: + - NumInstructions: '16' + - String: ' instructions in function' +--- !Analysis +Pass: asm-printer +Name: InstructionCount +DebugLoc: { File: foo.c, Line: 10, Column: 0 } +Function: bar +Args: + - NumInstructions: '22' + - String: ' instructions in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +DebugLoc: { File: foo.c, Line: 10, Column: 0 } +Function: baz +Args: + - NumInstructions: '42' + - String: ' instructions in function' +... Index: llvm/test/tools/sizediff/basic-input.s =================================================================== --- /dev/null +++ llvm/test/tools/sizediff/basic-input.s @@ -0,0 +1,30 @@ +// REQUIRES: x86-registered-target +// RUN: sed s!FILEPATH!%/p/Inputs/basic-a.opt.yaml! %s | llvm-mc -triple x86_64-apple-darwin --filetype=obj -o=%t1 +// RUN: sed s!FILEPATH!%/p/Inputs/basic-b.opt.yaml! %s | llvm-mc -triple x86_64-apple-darwin --filetype=obj -o=%t2 +// RUN: sizediff %t1 %t2 + +// Check that we produce a valid size diff from basic-a.opt.yaml and basic-b.opt.yaml. + +// CHECK: pluto 24 0 -24 +// CHECK-NEXT: bar 26 22 -4 +// CHECK-NEXT: foo 17 16 -1 +// CHECK-NEXT: baz 0 42 42 + + .section __TEXT,__text,regular,pure_instructions + .globl _foo + .p2align 4, 0x90 +_foo: + .cfi_startproc + retq + .cfi_endproc + .section __LLVM,__remarks,regular,debug + .ascii "REMARKS" + .byte 0 + .byte 0x00, 0x00, 0x00, 0x00 + .byte 0x00, 0x00, 0x00, 0x00 + .byte 0x00, 0x00, 0x00, 0x00 + .byte 0x00, 0x00, 0x00, 0x00 + .ascii "FILEPATH" + .byte 0 + +.subsections_via_symbols Index: llvm/test/tools/sizediff/no-remarks.s =================================================================== --- /dev/null +++ llvm/test/tools/sizediff/no-remarks.s @@ -0,0 +1,25 @@ +// REQUIRES: x86-registered-target +// RUN: llvm-mc -triple x86_64-apple-darwin --filetype=obj -o=%t1 +// RUN: llvm-mc -triple x86_64-linux-elf --filetype=obj -o=%t2 +// RUN: echo "heck" > %t3 + +// RUN: not sizediff %t1 %t1 2>&1 | FileCheck %s --check-prefix=NO-REMARKS +// RUN: not sizediff %t1 2>&1 | FileCheck %s --check-prefix=NEED-TWO +// RUN: not sizediff %t2 %t2 2>&1 | FileCheck %s --check-prefix=ELF +// RUN: not sizediff %t3 %t3 2>&1 | FileCheck %s --check-prefix=GARBAGE + +// Check basic failure conditions. +// NO-REMARKS: Could not find remarks section +// NEED-TWO: Not enough positional command line arguments specified! +// ELF: Currently, only Mach-O files are supported +// GARBAGE: The file was not recognized as a valid object file + + .section __TEXT,__text,regular,pure_instructions + .globl _foo + .p2align 4, 0x90 +_foo: + .cfi_startproc + retq + .cfi_endproc + +.subsections_via_symbols Index: llvm/tools/sizediff/CMakeLists.txt =================================================================== --- /dev/null +++ llvm/tools/sizediff/CMakeLists.txt @@ -0,0 +1,13 @@ +set(LLVM_LINK_COMPONENTS + Object + Support + Remarks + ) + +add_llvm_tool(sizediff + sizediff.cpp + ) + +if(LLVM_INSTALL_BINUTILS_SYMLINKS) + add_llvm_tool_symlink(sizeinfo sizediff) +endif() Index: llvm/tools/sizediff/LLVMBuild.txt =================================================================== --- /dev/null +++ llvm/tools/sizediff/LLVMBuild.txt @@ -0,0 +1,5 @@ +[component_0] +type = Tool +name = sizediff +parent = Tools +required_libraries = Object Support Remarks Index: llvm/tools/sizediff/sizediff.cpp =================================================================== --- /dev/null +++ llvm/tools/sizediff/sizediff.cpp @@ -0,0 +1,253 @@ +//===- sizediff.cpp -------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// A size diffing tool for object files based on LLVM optimization remarks. +// +// This tool outputs information about individual functions in two object files +// produced from the same (or very similar) source files. The object files must +// contain LLVM optimization remarks. This tool allows the user to see which +// functions changed the most in size between two compilations of their program. +// +// This can be useful for people experimenting with different optimization +// levels, for example. It can also be used by compiler developers interested in +// diagnosing code size changes. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Object/Binary.h" +#include "llvm/Object/MachO.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Remarks/RemarkParser.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/WithColor.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +using namespace llvm; +using namespace object; +using namespace remarks; + +namespace { +bool HadError = false; +std::string ToolName; +cl::opt OldFile(cl::Positional, cl::desc(""), + cl::Required); +cl::opt NewFile(cl::Positional, cl::desc(""), + cl::Required); + +// Helper struct for instruction counts. +struct FunctionInstrCount { + // First element = count in the old file. Second element = count in the + // new file. + int Count[2] = {0, 0}; + + // Convenience function to get the difference between the old file and the + // new file. + int getDelta() const { return Count[1] - Count[0]; } +}; +} // Anonymous namespace. + +static void error(Twine Message, Twine Path = Twine()) { + HadError = true; + WithColor::error(errs(), ToolName) << Path << ": " << Message << ".\n"; +} + +static bool error(std::error_code EC, Twine Path = Twine()) { + if (EC) { + error(EC.message(), Path); + return true; + } + return false; +} + +static void error(Error E, StringRef FileName) { + HadError = true; + WithColor::error(errs(), ToolName) << FileName << ":"; + std::string Buf; + raw_string_ostream OS(Buf); + logAllUnhandledErrors(std::move(E), OS); + OS.flush(); + errs() << " " << Buf << "\n"; +} + +/// Find the remarks section in \p Obj and return it if it exists. Otherwise, +/// return an error. +static Expected getRemarksSection(ObjectFile &Obj) { + // TODO: This should work with other object file formats. + // For now, let's be conservative and just support Mach-O. + if (!isa(&Obj)) + return make_error( + "Currently, only Mach-O files are supported", + std::make_error_code(std::errc::invalid_argument)); + + // Find the remarks section. + section_iterator RemarksIt = + find_if(Obj.sections(), [](const SectionRef &SecRef) { + StringRef Name; + + // Skip over sections that don't have names. + if (auto EC = SecRef.getName(Name)) + return false; + return Name == "__remarks"; + }); + + // No remarks section, bail. + if (RemarksIt == Obj.section_end()) + return make_error( + "Could not find remarks section", + std::make_error_code(std::errc::invalid_argument)); + return *RemarksIt; +} + +static bool getRemarksFromYAML(StringMap &InstrCounts, + int CurrFile, + std::unique_ptr &YAMLBuf, + StringRef RemarkFile) { + Parser RemarkParser(YAMLBuf->getBuffer()); + Expected RemarkOrErr = RemarkParser.getNext(); + + // Walk over remarks until we either run out of them, or hit an error. + for (; RemarkOrErr; RemarkOrErr = RemarkParser.getNext()) { + const Remark *Rmk = RemarkOrErr.get(); + + // We're at the end of the remark stream, so we're done. + if (!Rmk) + return true; + + // Is this an asm-printer remark? + if (Rmk->PassName != "asm-printer") + continue; + + // Yep. Grab the number of instructions in the function. + auto NumInstrs = find_if(Rmk->Args, [](const Argument &A) { + return A.Key == "NumInstructions"; + }); + assert("Didn't find number of instructions?" && + NumInstrs != Rmk->Args.end()); + + // Convert the string from the YAML file to an integer so we can save it in + // the map, do comparisons on it later, etc. + int InstrCount; + NumInstrs->Val.getAsInteger(0, InstrCount); + + // Did we already add this function to the map? + if (InstrCounts.count(Rmk->FunctionName)) { + // Yes. Just update the existing value. + InstrCounts[Rmk->FunctionName].Count[CurrFile] = InstrCount; + } else { + // No. Add a new value. + FunctionInstrCount FC; + FC.Count[CurrFile] = InstrCount; + InstrCounts[Rmk->FunctionName] = FC; + } + } + + error(RemarkOrErr.takeError(), RemarkFile); + return false; +} + +static bool collectRemarks(std::string &Filename, int CurrFile, + StringMap &InstrCounts) { + + // Get the object file. + ErrorOr> BufferOrErr = + MemoryBuffer::getFile(Filename); + if (error(BufferOrErr.getError(), Filename)) + return false; + + Expected> ObjOrErr( + ObjectFile::createObjectFile(BufferOrErr.get()->getMemBufferRef())); + if (!ObjOrErr) { + error(ObjOrErr.takeError(), Filename); + return false; + } + + // Get the remarks section in the object file if it exists. + ObjectFile &Obj = *ObjOrErr.get(); + Expected RemarksSectionOrErr = getRemarksSection(Obj); + if (!RemarksSectionOrErr) { + error(RemarksSectionOrErr.takeError(), Filename); + return false; + } + + // We have a remarks section. Get whatever is in it so we can process it. + Expected SectionContentsOrErr = + RemarksSectionOrErr.get().getContents(); + if (!SectionContentsOrErr) { + error(SectionContentsOrErr.takeError(), Filename); + return false; + } + + // The remarks section points to a YAML file. Move past the special remarks + // header to get the path to the file. + StringRef RemarkFile = SectionContentsOrErr.get().trim("REMARKS").drop_while( + [](const char &C) { return C == '\0'; }); + ErrorOr> YAMLBufferOrErr = + MemoryBuffer::getFile(RemarkFile.str()); + if (error(BufferOrErr.getError(), RemarkFile.str())) + return false; + + // Collect results from the YAML file. + return getRemarksFromYAML(InstrCounts, CurrFile, YAMLBufferOrErr.get(), + RemarkFile); +} + +/// Output the instruction count differences between the two files. +void outputResults(StringMap &InstrCounts) { + typedef std::pair NameAndSizeInfo; + + // Copy the counts over to a vector so that we can sort them. + std::vector CountVec; + for (const StringMapEntry &FnAndSize : InstrCounts) + CountVec.push_back({FnAndSize.first(), FnAndSize.getValue()}); + + // Sort by delta. + stable_sort(CountVec, + [](const NameAndSizeInfo &LHS, const NameAndSizeInfo &RHS) { + return LHS.second.getDelta() < RHS.second.getDelta(); + }); + + // Output results. + for (const NameAndSizeInfo &FnAndSize : CountVec) { + FunctionInstrCount FIC = FnAndSize.second; + int Delta = FIC.getDelta(); + // Only print things that changed. + if (Delta == 0) + continue; + outs() << FnAndSize.first << " " << FIC.Count[0] << " " << FIC.Count[1] + << " " << Delta << "\n"; + } +} + +int main(int argc, char **argv) { + InitLLVM X(argc, argv); + ToolName = argv[0]; + cl::ParseCommandLineOptions( + argc, argv, + "Remark-based size diffing tool.\nThis tool takes in two object files " + "produced " + "with optimization remarks.\nBoth files must contain a __remarks " + "section.\n"); + StringMap InstrCounts; + + // Read in each file. Bail out if an error is encountered at any point. + std::string Files[2] = {OldFile, NewFile}; + for (unsigned Idx = 0; Idx < 2; ++Idx) { + collectRemarks(Files[Idx], Idx, InstrCounts); + if (HadError) + return 1; + } + + outputResults(InstrCounts); +}