[llvm-objdump] Support --mcpu=help/--mattr=help without -d (#165661)

Currently `--mcpu=help` and `--mattr=help` only produce help out when
disassembling. This patch specialises these cases to always print the
requested help.

If `--triple` is specified, the help text will be derived from the
specified target. Otherwise, it will be derived from the target of the
first input file.

Fixes: #150567

---------

Signed-off-by: Ruoyu Qiu <cabbaken@outlook.com>
Co-authored-by: James Henderson <James.Henderson@sony.com>
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 4406a9d..503cf64 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -246,6 +246,9 @@
   emitted on stdout, to account for spaces or other special characters in path.
   (`#97305 <https://github.com/llvm/llvm-project/pull/97305>`_).
 
+* `llvm-objdump` now supports using `--mcpu=help` and `--mattr=help` with the `--triple` option
+  without requiring an input file or the `-d` (disassemble) flag.
+
 Changes to LLDB
 ---------------------------------
 
diff --git a/llvm/test/tools/llvm-objdump/mattr-mcpu-help.test b/llvm/test/tools/llvm-objdump/mattr-mcpu-help.test
index 65c4260..7aeaf66 100644
--- a/llvm/test/tools/llvm-objdump/mattr-mcpu-help.test
+++ b/llvm/test/tools/llvm-objdump/mattr-mcpu-help.test
@@ -1,12 +1,40 @@
-# RUN: yaml2obj %s -o %t
-# RUN: llvm-objdump -d %t --mattr=help 2>&1 | FileCheck %s
-# RUN: llvm-objdump -d %t --mcpu=help 2>&1 | FileCheck %s
 # REQUIRES: x86-registered-target
 
+# RUN: yaml2obj --docnum=1 %s -o %t
+# RUN: llvm-objdump -d %t --mattr=help 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK,CHECK-DISASSEMBLE
+# RUN: llvm-objdump -d %t --mcpu=help 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK,CHECK-DISASSEMBLE
+
 # CHECK: Available CPUs for this target:
 # CHECK: Available features for this target:
 ## To check we still disassemble the file:
-# CHECK: file format elf64-x86-64
+# CHECK-DISASSEMBLE: file format elf64-x86-64
+
+## The help message can be printed without -d.
+# RUN: llvm-objdump --mattr=help %t 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK,CHECK-NO-DISASSEMBLE
+# RUN: llvm-objdump --mcpu=help %t 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK,CHECK-NO-DISASSEMBLE
+
+# CHECK-NO-DISASSEMBLE-NOT: file format elf64-x86-64
+
+## We still handle other options.
+# RUN: llvm-objdump --mattr=help --section-headers %t 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK,CHECK-SECTION-HEADERS
+# RUN: llvm-objdump --mcpu=help --section-headers %t 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK,CHECK-SECTION-HEADERS
+
+# CHECK-SECTION-HEADERS: Sections:
+# CHECK-SECTION-HEADERS: Idx Name               Size     VMA              Type
+
+## We report an error when we can't infer the triple because we don't have --triple or an input object (including a.out).
+# RUN: not llvm-objdump --mattr=help 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=CHECK-MISSING-ERROR --implicit-check-not=error: -DMSG=%errc_ENOENT
+# RUN: not llvm-objdump --mcpu=help 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=CHECK-MISSING-ERROR --implicit-check-not=error: -DMSG=%errc_ENOENT
+
+# CHECK-MISSING-ERROR: llvm-objdump{{.*}}: error: 'a.out': triple was not specified and could not be inferred from the input file: [[MSG]]
 
 --- !ELF
 FileHeader:
@@ -14,3 +42,68 @@
   Data:            ELFDATA2LSB
   Type:            ET_EXEC
   Machine:         EM_X86_64
+
+# RUN: yaml2obj --docnum=2 %s -o %t.minidump
+## We report an error when the binary file isn't an object file format.
+# RUN: not llvm-objdump --mattr=help %t.minidump 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=CHECK-UNSUPPORTED-ERROR --implicit-check-not=error: -DFILE=%t.minidump
+# RUN: not llvm-objdump --mcpu=help %t.minidump 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=CHECK-UNSUPPORTED-ERROR --implicit-check-not=error: -DFILE=%t.minidump
+
+# CHECK-UNSUPPORTED-ERROR: llvm-objdump{{.*}}: error: '[[FILE]]': target triple could not be derived from input file
+
+--- !minidump
+Streams:
+  - Type:            SystemInfo
+    Processor Arch:  PPC
+    Platform ID:     Linux
+
+# RUN: llvm-ar rc %t.a %t
+# RUN: llvm-objdump --mcpu=help %t.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK
+# RUN: llvm-objdump --mattr=help %t.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK
+
+## We report an error when the triple cannot be inferred from any archive member.
+# RUN: llvm-ar rc %t.empty.a
+# RUN: not llvm-objdump --mcpu=help %t.empty.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK-UNSUPPORTED-ERROR --implicit-check-not=error: -DFILE=%t.empty.a
+# RUN: not llvm-objdump --mattr=help %t.empty.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK-UNSUPPORTED-ERROR --implicit-check-not=error: -DFILE=%t.empty.a
+
+## We are able to handle an archive with unsupported binary files. The target is
+## derived from the first recognised object file.
+# RUN: llvm-ar rc %t.a %t.minidump %t
+# RUN: llvm-objdump --mcpu=help %t.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK
+# RUN: llvm-objdump --mattr=help %t.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK
+
+## We report an error when the archive is malformed.
+# RUN: yaml2obj --docnum=3 %s -o %t.malformed.archive.a
+# RUN: not llvm-objdump --mcpu=help %t.malformed.archive.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK-MALFORMED-ARCHIVE-ERROR --implicit-check-not=error: -DFILE=%t.malformed.archive.a
+# RUN: not llvm-objdump --mattr=help %t.malformed.archive.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK-MALFORMED-ARCHIVE-ERROR --implicit-check-not=error: -DFILE=%t.malformed.archive.a
+
+# CHECK-MALFORMED-ARCHIVE-ERROR: llvm-objdump{{.*}}: error: '[[FILE]]': truncated or malformed archive
+
+--- !Arch
+Members:
+  - Name: 'foo.c'
+    Size: '1'
+
+## We report an error when we encounter an unexpected error while iterating files in an archive.
+# RUN: yaml2obj --docnum=4 %s -o %t.invalid.error.code.archive.a
+# RUN: not llvm-objdump --mcpu=help %t.invalid.error.code.archive.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK-INVALID-ERROR-ARCHIVE-ERROR --implicit-check-not=error: -DFILE=%t.invalid.error.code.archive.a
+# RUN: not llvm-objdump --mattr=help %t.invalid.error.code.archive.a 2>&1 \
+# RUN:   | FileCheck %s --check-prefixes=CHECK-INVALID-ERROR-ARCHIVE-ERROR --implicit-check-not=error: -DFILE=%t.invalid.error.code.archive.a
+
+# CHECK-INVALID-ERROR-ARCHIVE-ERROR: llvm-objdump{{.*}}: error: [[FILE]](<file index: 1>): truncated or malformed archive (long name offset characters after the '/' are not all decimal numbers: '&a25*' for archive member header at offset 68)
+
+--- !Arch
+Members:
+## We need the first member to be a valid member to trigger the right error.
+  - Name: 'hello.c/'
+  - Name: "/&a25*"
diff --git a/llvm/test/tools/llvm-objdump/mattr-mcpu-triple-help.test b/llvm/test/tools/llvm-objdump/mattr-mcpu-triple-help.test
new file mode 100644
index 0000000..1212773
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/mattr-mcpu-triple-help.test
@@ -0,0 +1,21 @@
+# RUN: yaml2obj %s -o %t
+# REQUIRES: x86-registered-target
+
+## When specifying --triple, the help message should be printed for the specified target.
+# RUN: llvm-objdump --mattr=help --triple=x86_64 2>&1 | FileCheck %s
+# RUN: llvm-objdump --mcpu=help --triple=x86_64 2>&1 | FileCheck %s
+
+## The help message will depend on the target specified by --triple even if the input is for a different target.
+# RUN: llvm-objdump --mattr=help --triple=x86_64 %t 2>&1 | FileCheck %s
+# RUN: llvm-objdump --mcpu=help --triple=x86_64 %t 2>&1 | FileCheck %s
+
+# CHECK: Available CPUs for this target:
+# CHECK: x86-64
+# CHECK: Available features for this target:
+
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS32
+  Data:            ELFDATA2LSB
+  Type:            ET_EXEC
+  Machine:         EM_MIPS
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 38c3f31..97032ec 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -3533,6 +3533,57 @@
   return Values;
 }
 
+static void mcpuHelp() {
+  Triple TheTriple;
+
+  if (!TripleName.empty()) {
+    TheTriple.setTriple(TripleName);
+  } else {
+    assert(!InputFilenames.empty());
+    Expected<OwningBinary<Binary>> OBinary = createBinary(InputFilenames[0]);
+    if (Error E = OBinary.takeError()) {
+      reportError(InputFilenames[0], "triple was not specified and could not "
+                                     "be inferred from the input file: " +
+                                         toString(std::move(E)));
+    }
+
+    Binary *Bin = OBinary->getBinary();
+    if (ObjectFile *Obj = dyn_cast<ObjectFile>(Bin)) {
+      TheTriple = Obj->makeTriple();
+    } else if (Archive *A = dyn_cast<Archive>(Bin)) {
+      Error Err = Error::success();
+      unsigned I = -1;
+      for (auto &C : A->children(Err)) {
+        ++I;
+        Expected<std::unique_ptr<Binary>> ChildOrErr = C.getAsBinary();
+        if (!ChildOrErr) {
+          if (auto E = isNotObjectErrorInvalidFileType(ChildOrErr.takeError()))
+            reportError(std::move(E), getFileNameForError(C, I),
+                        A->getFileName());
+          continue;
+        }
+        if (ObjectFile *Obj = dyn_cast<ObjectFile>(&*ChildOrErr.get())) {
+          TheTriple = Obj->makeTriple();
+          break;
+        }
+      }
+      if (Err)
+        reportError(std::move(Err), A->getFileName());
+    }
+    if (TheTriple.empty())
+      reportError(InputFilenames[0],
+                  "target triple could not be derived from input file");
+  }
+
+  std::string ErrMessage;
+  const Target *DummyTarget =
+      TargetRegistry::lookupTarget(TheTriple, ErrMessage);
+  if (!DummyTarget)
+    reportCmdLineError(ErrMessage);
+  // We need to access the Help() through the corresponding MCSubtargetInfo.
+  DummyTarget->createMCSubtargetInfo(TheTriple, "help", "");
+}
+
 static void parseOtoolOptions(const llvm::opt::InputArgList &InputArgs) {
   MachOOpt = true;
   FullLeadingAddr = true;
@@ -3826,20 +3877,31 @@
       !DisassembleSymbols.empty())
     Disassemble = true;
 
-  if (!ArchiveHeaders && !Disassemble && DwarfDumpType == DIDT_Null &&
-      !DynamicRelocations && !FileHeaders && !PrivateHeaders && !RawClangAST &&
-      !Relocations && !SectionHeaders && !SectionContents && !SymbolTable &&
-      !DynamicSymbolTable && !UnwindInfo && !FaultMapSection && !Offloading &&
-      !(MachOOpt &&
-        (Bind || DataInCode || ChainedFixups || DyldInfo || DylibId ||
-         DylibsUsed || ExportsTrie || FirstPrivateHeader ||
-         FunctionStartsType != FunctionStartsMode::None || IndirectSymbols ||
-         InfoPlist || LazyBind || LinkOptHints || ObjcMetaData || Rebase ||
-         Rpaths || UniversalHeaders || WeakBind || !FilterSections.empty()))) {
+  const bool PrintCpuHelp = (MCPU == "help" || is_contained(MAttrs, "help"));
+
+  const bool ShouldDump =
+      ArchiveHeaders || Disassemble || DwarfDumpType != DIDT_Null ||
+      DynamicRelocations || FileHeaders || PrivateHeaders || RawClangAST ||
+      Relocations || SectionHeaders || SectionContents || SymbolTable ||
+      DynamicSymbolTable || UnwindInfo || FaultMapSection || Offloading ||
+      (MachOOpt &&
+       (Bind || DataInCode || ChainedFixups || DyldInfo || DylibId ||
+        DylibsUsed || ExportsTrie || FirstPrivateHeader ||
+        FunctionStartsType != FunctionStartsMode::None || IndirectSymbols ||
+        InfoPlist || LazyBind || LinkOptHints || ObjcMetaData || Rebase ||
+        Rpaths || UniversalHeaders || WeakBind || !FilterSections.empty()));
+
+  if (!ShouldDump && !PrintCpuHelp) {
     T->printHelp(ToolName);
     return 2;
   }
 
+  if (PrintCpuHelp) {
+    mcpuHelp();
+    if (!ShouldDump)
+      return EXIT_SUCCESS;
+  }
+
   DisasmSymbolSet.insert_range(DisassembleSymbols);
 
   llvm::for_each(InputFilenames, dumpInput);