diff --git a/clang/test/Driver/clang-offload-bundler.c b/clang/test/Driver/clang-offload-bundler.c index b4bab6bbd1e8..3e1fab25d754 100644 --- a/clang/test/Driver/clang-offload-bundler.c +++ b/clang/test/Driver/clang-offload-bundler.c @@ -1,326 +1,367 @@ // REQUIRES: x86-registered-target // REQUIRES: powerpc-registered-target // UNSUPPORTED: darwin // // Generate all the types of files we can bundle. // // RUN: %clang -O0 -target %itanium_abi_triple %s -E -o %t.i // RUN: %clangxx -O0 -target %itanium_abi_triple -x c++ %s -E -o %t.ii // RUN: %clang -O0 -target %itanium_abi_triple %s -S -emit-llvm -o %t.ll // RUN: %clang -O0 -target %itanium_abi_triple %s -c -emit-llvm -o %t.bc // RUN: %clang -O0 -target %itanium_abi_triple %s -S -o %t.s // RUN: %clang -O0 -target %itanium_abi_triple %s -c -o %t.o // RUN: %clang -O0 -target %itanium_abi_triple %s -emit-ast -o %t.ast // // Generate an empty file to help with the checks of empty files. // // RUN: touch %t.empty // // Generate a couple of files to bundle with. // // RUN: echo 'Content of device file 1' > %t.tgt1 // RUN: echo 'Content of device file 2' > %t.tgt2 // // Check help message. // // RUN: clang-offload-bundler --help | FileCheck %s --check-prefix CK-HELP // CK-HELP: {{.*}}OVERVIEW: A tool to bundle several input files of the specified type // CK-HELP: {{.*}}referring to the same source file but different targets into a single // CK-HELP: {{.*}}one. The resulting file can also be unbundled into different files by // CK-HELP: {{.*}}this tool if -unbundle is provided. // CK-HELP: {{.*}}USAGE: clang-offload-bundler [options] // CK-HELP: {{.*}}-allow-missing-bundles {{.*}}- Create empty files if bundles are missing when unbundling // CK-HELP: {{.*}}-inputs= - [,...] +// CK-HELP: {{.*}}-list {{.*}}- List bundle IDs in the bundled file. // CK-HELP: {{.*}}-outputs= - [,...] // CK-HELP: {{.*}}-targets= - [-,...] // CK-HELP: {{.*}}-type= - Type of the files to be bundled/unbundled. // CK-HELP: {{.*}}Current supported types are: // CK-HELP: {{.*}}i {{.*}}- cpp-output // CK-HELP: {{.*}}ii {{.*}}- c++-cpp-output // CK-HELP: {{.*}}ll {{.*}}- llvm // CK-HELP: {{.*}}bc {{.*}}- llvm-bc // CK-HELP: {{.*}}s {{.*}}- assembler // CK-HELP: {{.*}}o {{.*}}- object // CK-HELP: {{.*}}gch {{.*}}- precompiled-header // CK-HELP: {{.*}}ast {{.*}}- clang AST file // CK-HELP: {{.*}}-unbundle {{.*}}- Unbundle bundled file into several output files. // // Check errors. // // RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i -unbundle 2>&1 | FileCheck %s --check-prefix CK-ERR1 // CK-ERR1: error: only one input file supported in unbundling mode -// CK-ERR1: error: number of output files and targets should match in unbundling mode + +// RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.i -outputs=%t.bundle.i -unbundle 2>&1 | FileCheck %s --check-prefix CK-ERR1A +// CK-ERR1A: error: number of output files and targets should match in unbundling mode // RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i 2>&1 | FileCheck %s --check-prefix CK-ERR2 // RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.i,%t.tgt1 -outputs=%t.bundle.i 2>&1 | FileCheck %s --check-prefix CK-ERR2 // CK-ERR2: error: number of input files and targets should match in bundling mode // RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.i,%t.tgt1,%t.tgt2 -inputs=%t.bundle.i 2>&1 | FileCheck %s --check-prefix CK-ERR3 // CK-ERR3: error: only one output file supported in bundling mode -// CK-ERR3: error: number of input files and targets should match in bundling mode // RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu -outputs=%t.i,%t.tgt1,%t.tgt2 -inputs=%t.bundle.i -unbundle 2>&1 | FileCheck %s --check-prefix CK-ERR4 // RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.i,%t.tgt1 -inputs=%t.bundle.i -unbundle 2>&1 | FileCheck %s --check-prefix CK-ERR4 // CK-ERR4: error: number of output files and targets should match in unbundling mode // RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2.notexist -outputs=%t.bundle.i 2>&1 | FileCheck %s -DFILE=%t.tgt2.notexist --check-prefix CK-ERR5 // RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.i,%t.tgt1,%t.tgt2 -inputs=%t.bundle.i.notexist -unbundle 2>&1 | FileCheck %s -DFILE=%t.bundle.i.notexist --check-prefix CK-ERR5 // CK-ERR5: error: '[[FILE]]': {{N|n}}o such file or directory // RUN: not clang-offload-bundler -type=invalid -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i 2>&1 | FileCheck %s -DTYPE=invalid --check-prefix CK-ERR6 // CK-ERR6: error: '[[TYPE]]': invalid file type specified // RUN: not clang-offload-bundler 2>&1 | FileCheck %s --check-prefix CK-ERR7 -// CK-ERR7-DAG: clang-offload-bundler: for the --type option: must be specified at least once! -// CK-ERR7-DAG: clang-offload-bundler: for the --inputs option: must be specified at least once! -// CK-ERR7-DAG: clang-offload-bundler: for the --outputs option: must be specified at least once! -// CK-ERR7-DAG: clang-offload-bundler: for the --targets option: must be specified at least once! +// CK-ERR7: clang-offload-bundler: for the --type option: must be specified at least once! + +// RUN: not clang-offload-bundler -type=i -inputs=%t.i,%t.tgt1,%t.tgt2 2>&1 | FileCheck %s -check-prefix=CK-ERR7A +// CK-ERR7A: error: for the --outputs option: must be specified at least once! + +// RUN: not clang-offload-bundler -type=i -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i 2>&1 | FileCheck %s -check-prefix=CK-ERR7B +// CK-ERR7B: error: for the --targets option: must be specified at least once! // RUN: not clang-offload-bundler -type=i -targets=hxst-powerpcxxle-ibm-linux-gnu,openxp-pxxerpc64le-ibm-linux-gnu,xpenmp-x86_xx-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i 2>&1 | FileCheck %s --check-prefix CK-ERR8 // CK-ERR8: error: invalid target 'hxst-powerpcxxle-ibm-linux-gnu', unknown offloading kind 'hxst', unknown target triple 'powerpcxxle-ibm-linux-gnu' -// CK-ERR8: error: invalid target 'openxp-pxxerpc64le-ibm-linux-gnu', unknown offloading kind 'openxp', unknown target triple 'pxxerpc64le-ibm-linux-gnu' -// CK-ERR8: error: invalid target 'xpenmp-x86_xx-pc-linux-gnu', unknown offloading kind 'xpenmp', unknown target triple 'x86_xx-pc-linux-gnu' + +// RUN: not clang-offload-bundler -type=i -targets=host-powerpc64le-ibm-linux-gnu,openxp-pxxerpc64le-ibm-linux-gnu,xpenmp-x86_xx-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i 2>&1 | FileCheck %s --check-prefix CK-ERR8A +// CK-ERR8A: error: invalid target 'openxp-pxxerpc64le-ibm-linux-gnu', unknown offloading kind 'openxp', unknown target triple 'pxxerpc64le-ibm-linux-gnu' + +// RUN: not clang-offload-bundler -type=i -targets=host-powerpc64le-ibm-linux-gnu,openmp-powerpc64le-ibm-linux-gnu,xpenmp-x86_xx-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i 2>&1 | FileCheck %s --check-prefix CK-ERR8B +// CK-ERR8B: error: invalid target 'xpenmp-x86_xx-pc-linux-gnu', unknown offloading kind 'xpenmp', unknown target triple 'x86_xx-pc-linux-gnu' // RUN: not clang-offload-bundler -type=i -targets=openmp-powerpc64le-linux,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i 2>&1 | FileCheck %s --check-prefix CK-ERR9A -// RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i 2>&1 | FileCheck %s --check-prefix CK-ERR9B // CK-ERR9A: error: expecting exactly one host target but got 0 + +// RUN: not clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle.i 2>&1 | FileCheck %s --check-prefix CK-ERR9B // CK-ERR9B: error: Duplicate targets are not allowed // // Check text bundle. This is a readable format, so we check for the format we expect to find. // // RUN: clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.i,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.i // RUN: clang-offload-bundler -type=ii -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.ii,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.ii // RUN: clang-offload-bundler -type=ll -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.ll,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.ll // RUN: clang-offload-bundler -type=s -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.s,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.s // RUN: clang-offload-bundler -type=s -targets=openmp-powerpc64le-ibm-linux-gnu,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -inputs=%t.tgt1,%t.s,%t.tgt2 -outputs=%t.bundle3.unordered.s // RUN: FileCheck %s --input-file %t.bundle3.i --check-prefix CK-TEXTI // RUN: FileCheck %s --input-file %t.bundle3.ii --check-prefix CK-TEXTI // RUN: FileCheck %s --input-file %t.bundle3.ll --check-prefix CK-TEXTLL // RUN: FileCheck %s --input-file %t.bundle3.s --check-prefix CK-TEXTS // RUN: FileCheck %s --input-file %t.bundle3.unordered.s --check-prefix CK-TEXTS-UNORDERED // CK-TEXTI: // __CLANG_OFFLOAD_BUNDLE____START__ host-[[HOST:.+]] // CK-TEXTI: int A = 0; // CK-TEXTI: test_func(void) // CK-TEXTI: // __CLANG_OFFLOAD_BUNDLE____END__ host-[[HOST]] // CK-TEXTI: // __CLANG_OFFLOAD_BUNDLE____START__ openmp-powerpc64le-ibm-linux-gnu // CK-TEXTI: Content of device file 1 // CK-TEXTI: // __CLANG_OFFLOAD_BUNDLE____END__ openmp-powerpc64le-ibm-linux-gnu // CK-TEXTI: // __CLANG_OFFLOAD_BUNDLE____START__ openmp-x86_64-pc-linux-gnu // CK-TEXTI: Content of device file 2 // CK-TEXTI: // __CLANG_OFFLOAD_BUNDLE____END__ openmp-x86_64-pc-linux-gnu // CK-TEXTLL: ; __CLANG_OFFLOAD_BUNDLE____START__ host-[[HOST:.+]] // CK-TEXTLL: @A = {{.*}}global i32 0 // CK-TEXTLL: define {{.*}}@test_func() // CK-TEXTLL: ; __CLANG_OFFLOAD_BUNDLE____END__ host-[[HOST]] // CK-TEXTLL: ; __CLANG_OFFLOAD_BUNDLE____START__ openmp-powerpc64le-ibm-linux-gnu // CK-TEXTLL: Content of device file 1 // CK-TEXTLL: ; __CLANG_OFFLOAD_BUNDLE____END__ openmp-powerpc64le-ibm-linux-gnu // CK-TEXTLL: ; __CLANG_OFFLOAD_BUNDLE____START__ openmp-x86_64-pc-linux-gnu // CK-TEXTLL: Content of device file 2 // CK-TEXTLL: ; __CLANG_OFFLOAD_BUNDLE____END__ openmp-x86_64-pc-linux-gnu // CK-TEXTS: # __CLANG_OFFLOAD_BUNDLE____START__ host-[[HOST:.+]] // CK-TEXTS: .globl {{.*}}test_func // CK-TEXTS: .globl {{.*}}A // CK-TEXTS: # __CLANG_OFFLOAD_BUNDLE____END__ host-[[HOST]] // CK-TEXTS: # __CLANG_OFFLOAD_BUNDLE____START__ openmp-powerpc64le-ibm-linux-gnu // CK-TEXTS: Content of device file 1 // CK-TEXTS: # __CLANG_OFFLOAD_BUNDLE____END__ openmp-powerpc64le-ibm-linux-gnu // CK-TEXTS: # __CLANG_OFFLOAD_BUNDLE____START__ openmp-x86_64-pc-linux-gnu // CK-TEXTS: Content of device file 2 // CK-TEXTS: # __CLANG_OFFLOAD_BUNDLE____END__ openmp-x86_64-pc-linux-gnu // CK-TEXTS-UNORDERED: # __CLANG_OFFLOAD_BUNDLE____START__ openmp-powerpc64le-ibm-linux-gnu // CK-TEXTS-UNORDERED: Content of device file 1 // CK-TEXTS-UNORDERED: # __CLANG_OFFLOAD_BUNDLE____END__ openmp-powerpc64le-ibm-linux-gnu // CK-TEXTS-UNORDERED: # __CLANG_OFFLOAD_BUNDLE____START__ host-[[HOST:.+]] // CK-TEXTS-UNORDERED: .globl {{.*}}test_func // CK-TEXTS-UNORDERED: .globl {{.*}}A // CK-TEXTS-UNORDERED: # __CLANG_OFFLOAD_BUNDLE____END__ host-[[HOST]] // CK-TEXTS-UNORDERED: # __CLANG_OFFLOAD_BUNDLE____START__ openmp-x86_64-pc-linux-gnu // CK-TEXTS-UNORDERED: Content of device file 2 // CK-TEXTS-UNORDERED: # __CLANG_OFFLOAD_BUNDLE____END__ openmp-x86_64-pc-linux-gnu // // Check text unbundle. Check if we get the exact same content that we bundled before for each file. // +// RUN: clang-offload-bundler -type=i -inputs=%t.bundle3.i -list | FileCheck -check-prefix=CKLST %s // RUN: clang-offload-bundler -type=i -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.i,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.i -unbundle // RUN: diff %t.i %t.res.i // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=i -targets=openmp-powerpc64le-ibm-linux-gnu -outputs=%t.res.tgt1 -inputs=%t.bundle3.i -unbundle // RUN: diff %t.tgt1 %t.res.tgt1 +// RUN: clang-offload-bundler -type=ii -inputs=%t.bundle3.ii -list | FileCheck -check-prefix=CKLST %s // RUN: clang-offload-bundler -type=ii -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.ii,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.ii -unbundle // RUN: diff %t.ii %t.res.ii // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=ii -targets=openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt2 -inputs=%t.bundle3.ii -unbundle // RUN: diff %t.tgt2 %t.res.tgt2 +// RUN: clang-offload-bundler -type=ll -inputs=%t.bundle3.ll -list | FileCheck -check-prefix=CKLST %s // RUN: clang-offload-bundler -type=ll -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.ll,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.ll -unbundle // RUN: diff %t.ll %t.res.ll // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=ll -targets=openmp-powerpc64le-ibm-linux-gnu -outputs=%t.res.tgt1 -inputs=%t.bundle3.ll -unbundle // RUN: diff %t.tgt1 %t.res.tgt1 +// RUN: clang-offload-bundler -type=s -inputs=%t.bundle3.s -list | FileCheck -check-prefix=CKLST %s // RUN: clang-offload-bundler -type=s -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.s,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.s -unbundle // RUN: diff %t.s %t.res.s // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=s -targets=openmp-powerpc64le-ibm-linux-gnu,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt1,%t.res.s,%t.res.tgt2 -inputs=%t.bundle3.s -unbundle // RUN: diff %t.s %t.res.s // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=s -targets=openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt2 -inputs=%t.bundle3.s -unbundle // RUN: diff %t.tgt2 %t.res.tgt2 // Check if we can unbundle a file with no magic strings. +// RUN: clang-offload-bundler -type=s -inputs=%t.s -list | FileCheck -check-prefix=CKLST2 --allow-empty %s // RUN: clang-offload-bundler -type=s -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.s,%t.res.tgt1,%t.res.tgt2 -inputs=%t.s -unbundle -allow-missing-bundles // RUN: diff %t.s %t.res.s // RUN: diff %t.empty %t.res.tgt1 // RUN: diff %t.empty %t.res.tgt2 // RUN: clang-offload-bundler -type=s -targets=openmp-powerpc64le-ibm-linux-gnu,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt1,%t.res.s,%t.res.tgt2 -inputs=%t.s -unbundle -allow-missing-bundles // RUN: diff %t.s %t.res.s // RUN: diff %t.empty %t.res.tgt1 // RUN: diff %t.empty %t.res.tgt2 // Check that bindler prints an error if given host bundle does not exist in the fat binary. // RUN: not clang-offload-bundler -type=s -targets=host-x86_64-xxx-linux-gnu,openmp-powerpc64le-ibm-linux-gnu -outputs=%t.res.s,%t.res.tgt1 -inputs=%t.bundle3.s -unbundle -allow-missing-bundles 2>&1 | FileCheck %s --check-prefix CK-NO-HOST-BUNDLE // CK-NO-HOST-BUNDLE: error: Can't find bundle for the host target // // Check binary bundle/unbundle. The content that we have before bundling must be the same we have after unbundling. // // RUN: clang-offload-bundler -type=bc -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.bc,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.bc // RUN: clang-offload-bundler -type=gch -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.ast,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.gch // RUN: clang-offload-bundler -type=ast -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.ast,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.ast // RUN: clang-offload-bundler -type=ast -targets=openmp-powerpc64le-ibm-linux-gnu,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -inputs=%t.tgt1,%t.ast,%t.tgt2 -outputs=%t.bundle3.unordered.ast +// RUN: clang-offload-bundler -type=bc -inputs=%t.bundle3.bc -list | FileCheck -check-prefix=CKLST %s // RUN: clang-offload-bundler -type=bc -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.bc,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.bc -unbundle // RUN: diff %t.bc %t.res.bc // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=bc -targets=openmp-powerpc64le-ibm-linux-gnu -outputs=%t.res.tgt1 -inputs=%t.bundle3.bc -unbundle // RUN: diff %t.tgt1 %t.res.tgt1 +// RUN: clang-offload-bundler -type=gch -inputs=%t.bundle3.gch -list | FileCheck -check-prefix=CKLST %s // RUN: clang-offload-bundler -type=gch -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.gch,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.gch -unbundle // RUN: diff %t.ast %t.res.gch // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=gch -targets=openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt2 -inputs=%t.bundle3.gch -unbundle // RUN: diff %t.tgt2 %t.res.tgt2 +// RUN: clang-offload-bundler -type=ast -inputs=%t.bundle3.ast -list | FileCheck -check-prefix=CKLST %s // RUN: clang-offload-bundler -type=ast -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.ast,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.ast -unbundle // RUN: diff %t.ast %t.res.ast // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=ast -targets=openmp-powerpc64le-ibm-linux-gnu,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt1,%t.res.ast,%t.res.tgt2 -inputs=%t.bundle3.ast -unbundle // RUN: diff %t.ast %t.res.ast // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=ast -targets=openmp-powerpc64le-ibm-linux-gnu,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt1,%t.res.ast,%t.res.tgt2 -inputs=%t.bundle3.unordered.ast -unbundle // RUN: diff %t.ast %t.res.ast // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=ast -targets=openmp-powerpc64le-ibm-linux-gnu -outputs=%t.res.tgt1 -inputs=%t.bundle3.ast -unbundle // RUN: diff %t.tgt1 %t.res.tgt1 // Check if we can unbundle a file with no magic strings. // RUN: clang-offload-bundler -type=bc -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.bc,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bc -unbundle -allow-missing-bundles // RUN: diff %t.bc %t.res.bc // RUN: diff %t.empty %t.res.tgt1 // RUN: diff %t.empty %t.res.tgt2 // RUN: clang-offload-bundler -type=bc -targets=openmp-powerpc64le-ibm-linux-gnu,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt1,%t.res.bc,%t.res.tgt2 -inputs=%t.bc -unbundle -allow-missing-bundles // RUN: diff %t.bc %t.res.bc // RUN: diff %t.empty %t.res.tgt1 // RUN: diff %t.empty %t.res.tgt2 // Check that we do not have to unbundle all available bundles from the fat binary. // RUN: clang-offload-bundler -type=ast -targets=host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -outputs=%t.res.ast,%t.res.tgt2 -inputs=%t.bundle3.unordered.ast -unbundle // RUN: diff %t.ast %t.res.ast // RUN: diff %t.tgt2 %t.res.tgt2 // // Check object bundle/unbundle. The content should be bundled into an ELF // section (we are using a PowerPC little-endian host which uses ELF). We // have an already bundled file to check the unbundle and do a dry run on the // bundling as it cannot be tested in all host platforms that will run these // tests. // // RUN: clang-offload-bundler -type=o -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.o,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.o -### 2>&1 \ // RUN: | FileCheck %s -DHOST=%itanium_abi_triple -DINOBJ1=%t.o -DINOBJ2=%t.tgt1 -DINOBJ3=%t.tgt2 -DOUTOBJ=%t.bundle3.o --check-prefix CK-OBJ-CMD // CK-OBJ-CMD: llvm-objcopy{{(.exe)?}}" "--add-section=__CLANG_OFFLOAD_BUNDLE__host-[[HOST]]={{.*}}" "--add-section=__CLANG_OFFLOAD_BUNDLE__openmp-powerpc64le-ibm-linux-gnu=[[INOBJ2]]" "--add-section=__CLANG_OFFLOAD_BUNDLE__openmp-x86_64-pc-linux-gnu=[[INOBJ3]]" "[[INOBJ1]]" "[[TEMPOBJ:.*]]" // CK-OBJ-CMD: llvm-objcopy{{(.exe)?}}" "--set-section-flags=__CLANG_OFFLOAD_BUNDLE__host-[[HOST]]=readonly,exclude" "--set-section-flags=__CLANG_OFFLOAD_BUNDLE__openmp-powerpc64le-ibm-linux-gnu=readonly,exclude" "--set-section-flags=__CLANG_OFFLOAD_BUNDLE__openmp-x86_64-pc-linux-gnu=readonly,exclude" "[[TEMPOBJ]]" "[[OUTOBJ]]" // RUN: clang-offload-bundler -type=o -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.o,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.o +// RUN: clang-offload-bundler -type=o -inputs=%t.bundle3.o -list | FileCheck -check-prefix=CKLST %s // RUN: clang-offload-bundler -type=o -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.o,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.o -unbundle // RUN: diff %t.bundle3.o %t.res.o // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=o -targets=openmp-powerpc64le-ibm-linux-gnu,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt1,%t.res.o,%t.res.tgt2 -inputs=%t.bundle3.o -unbundle // RUN: diff %t.bundle3.o %t.res.o // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // RUN: clang-offload-bundler -type=o -targets=openmp-powerpc64le-ibm-linux-gnu -outputs=%t.res.tgt1 -inputs=%t.bundle3.o -unbundle // RUN: diff %t.tgt1 %t.res.tgt1 // Check if we can unbundle a file with no magic strings. +// RUN: clang-offload-bundler -type=o -inputs=%t.o -list | FileCheck -check-prefix=CKLST2 --allow-empty %s // RUN: clang-offload-bundler -type=o -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.o,%t.res.tgt1,%t.res.tgt2 -inputs=%t.o -unbundle -allow-missing-bundles // RUN: diff %t.o %t.res.o // RUN: diff %t.empty %t.res.tgt1 // RUN: diff %t.empty %t.res.tgt2 // RUN: clang-offload-bundler -type=o -targets=openmp-powerpc64le-ibm-linux-gnu,host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu -outputs=%t.res.tgt1,%t.res.o,%t.res.tgt2 -inputs=%t.o -unbundle -allow-missing-bundles // RUN: diff %t.o %t.res.o // RUN: diff %t.empty %t.res.tgt1 // RUN: diff %t.empty %t.res.tgt2 // // Check -bundle-align option // // RUN: clang-offload-bundler -bundle-align=4096 -type=bc -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.bc,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.bc // RUN: clang-offload-bundler -type=bc -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.bc,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.bc -unbundle // RUN: diff %t.bc %t.res.bc // RUN: diff %t.tgt1 %t.res.tgt1 // RUN: diff %t.tgt2 %t.res.tgt2 // // Check error due to missing bundles // // RUN: clang-offload-bundler -type=bc -targets=host-%itanium_abi_triple,hip-amdgcn-amd-amdhsa-gfx900 -inputs=%t.bc,%t.tgt1 -outputs=%t.hip.bundle.bc // RUN: not clang-offload-bundler -type=bc -inputs=%t.hip.bundle.bc -outputs=%t.tmp.bc -unbundle \ // RUN: -targets=hip-amdgcn-amd-amdhsa-gfx906 \ // RUN: 2>&1 | FileCheck -check-prefix=MISS1 %s // RUN: not clang-offload-bundler -type=bc -inputs=%t.hip.bundle.bc -outputs=%t.tmp.bc,%t.tmp2.bc -unbundle \ // RUN: -targets=hip-amdgcn-amd-amdhsa-gfx906,hip-amdgcn-amd-amdhsa-gfx900 \ // RUN: 2>&1 | FileCheck -check-prefix=MISS1 %s // MISS1: error: Can't find bundles for hip-amdgcn-amd-amdhsa-gfx906 // RUN: not clang-offload-bundler -type=bc -inputs=%t.hip.bundle.bc -outputs=%t.tmp.bc,%t.tmp2.bc -unbundle \ // RUN: -targets=hip-amdgcn-amd-amdhsa-gfx906,hip-amdgcn-amd-amdhsa-gfx803 \ // RUN: 2>&1 | FileCheck -check-prefix=MISS2 %s // MISS2: error: Can't find bundles for hip-amdgcn-amd-amdhsa-gfx803 and hip-amdgcn-amd-amdhsa-gfx906 // RUN: not clang-offload-bundler -type=bc -inputs=%t.hip.bundle.bc -outputs=%t.tmp.bc,%t.tmp2.bc,%t.tmp3.bc -unbundle \ // RUN: -targets=hip-amdgcn-amd-amdhsa-gfx906,hip-amdgcn-amd-amdhsa-gfx803,hip-amdgcn-amd-amdhsa-gfx1010 \ // RUN: 2>&1 | FileCheck -check-prefix=MISS3 %s // MISS3: error: Can't find bundles for hip-amdgcn-amd-amdhsa-gfx1010, hip-amdgcn-amd-amdhsa-gfx803, and hip-amdgcn-amd-amdhsa-gfx906 // // Check error due to duplicate targets // // RUN: not clang-offload-bundler -type=bc -targets=host-%itanium_abi_triple,hip-amdgcn-amd-amdhsa-gfx900,hip-amdgcn-amd-amdhsa-gfx900 \ // RUN: -inputs=%t.bc,%t.tgt1,%t.tgt1 -outputs=%t.hip.bundle.bc 2>&1 | FileCheck -check-prefix=DUP %s // RUN: not clang-offload-bundler -type=bc -inputs=%t.hip.bundle.bc -outputs=%t.tmp.bc,%t.tmp2.bc -unbundle \ // RUN: -targets=hip-amdgcn-amd-amdhsa-gfx906,hip-amdgcn-amd-amdhsa-gfx906 \ // RUN: 2>&1 | FileCheck -check-prefix=DUP %s // DUP: error: Duplicate targets are not allowed +// +// Check -list option +// + +// RUN: clang-offload-bundler -bundle-align=4096 -type=bc -targets=host-%itanium_abi_triple,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.bc,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.bc +// RUN: not clang-offload-bundler -type=bc -inputs=%t.bundle3.bc -unbundle -list 2>&1 | FileCheck -check-prefix=CKLST-ERR %s +// CKLST-ERR: error: -unbundle and -list cannot be used together +// RUN: not clang-offload-bundler -type=bc -inputs=%t.bundle3.bc -targets=host-%itanium_abi_triple -list 2>&1 | FileCheck -check-prefix=CKLST-ERR2 %s +// CKLST-ERR2: error: -targets option is invalid for -list +// RUN: not clang-offload-bundler -type=bc -inputs=%t.bundle3.bc -outputs=out.txt -list 2>&1 | FileCheck -check-prefix=CKLST-ERR3 %s +// CKLST-ERR3: error: -outputs option is invalid for -list +// RUN: not clang-offload-bundler -type=bc -inputs=%t.bundle3.bc,%t.bc -list 2>&1 | FileCheck -check-prefix=CKLST-ERR4 %s +// CKLST-ERR4: error: only one input file supported for -list + +// CKLST-DAG: host- +// CKLST-DAG: openmp-powerpc64le-ibm-linux-gnu +// CKLST-DAG: openmp-x86_64-pc-linux-gnu + +// CKLST2-NOT: host- +// CKLST2-NOT: openmp-powerpc64le-ibm-linux-gnu +// CKLST2-NOT: openmp-x86_64-pc-linux-gnu // Some code so that we can create a binary out of this file. int A = 0; void test_func(void) { ++A; } diff --git a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp b/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp index 1e2a2a67bcce..e8df0f151ef0 100644 --- a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp +++ b/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp @@ -1,1072 +1,1182 @@ //===-- clang-offload-bundler/ClangOffloadBundler.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 // //===----------------------------------------------------------------------===// /// /// \file /// This file implements a clang-offload-bundler that bundles different /// files that relate with the same source code but different targets into a /// single one. Also the implements the opposite functionality, i.e. unbundle /// files previous created by this tool. /// //===----------------------------------------------------------------------===// #include "clang/Basic/Version.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Triple.h" #include "llvm/Object/Binary.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include "llvm/Support/Signals.h" #include "llvm/Support/StringSaver.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include #include #include #include #include #include using namespace llvm; using namespace llvm::object; static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden); // Mark all our options with this category, everything else (except for -version // and -help) will be hidden. static cl::OptionCategory ClangOffloadBundlerCategory("clang-offload-bundler options"); static cl::list InputFileNames("inputs", cl::CommaSeparated, cl::OneOrMore, cl::desc("[,...]"), cl::cat(ClangOffloadBundlerCategory)); static cl::list - OutputFileNames("outputs", cl::CommaSeparated, cl::OneOrMore, + OutputFileNames("outputs", cl::CommaSeparated, cl::desc("[,...]"), cl::cat(ClangOffloadBundlerCategory)); static cl::list - TargetNames("targets", cl::CommaSeparated, cl::OneOrMore, + TargetNames("targets", cl::CommaSeparated, cl::desc("[-,...]"), cl::cat(ClangOffloadBundlerCategory)); static cl::opt FilesType("type", cl::Required, cl::desc("Type of the files to be bundled/unbundled.\n" "Current supported types are:\n" " i - cpp-output\n" " ii - c++-cpp-output\n" " cui - cuda/hip-output\n" " d - dependency\n" " ll - llvm\n" " bc - llvm-bc\n" " s - assembler\n" " o - object\n" " gch - precompiled-header\n" " ast - clang AST file"), cl::cat(ClangOffloadBundlerCategory)); static cl::opt Unbundle("unbundle", cl::desc("Unbundle bundled file into several output files.\n"), cl::init(false), cl::cat(ClangOffloadBundlerCategory)); +static cl::opt + ListBundleIDs("list", cl::desc("List bundle IDs in the bundled file.\n"), + cl::init(false), cl::cat(ClangOffloadBundlerCategory)); + static cl::opt PrintExternalCommands( "###", cl::desc("Print any external commands that are to be executed " "instead of actually executing them - for testing purposes.\n"), cl::init(false), cl::cat(ClangOffloadBundlerCategory)); static cl::opt AllowMissingBundles("allow-missing-bundles", cl::desc("Create empty files if bundles are missing " "when unbundling.\n"), cl::init(false), cl::cat(ClangOffloadBundlerCategory)); static cl::opt BundleAlignment("bundle-align", cl::desc("Alignment of bundle for binary files"), cl::init(1), cl::cat(ClangOffloadBundlerCategory)); /// Magic string that marks the existence of offloading data. #define OFFLOAD_BUNDLER_MAGIC_STR "__CLANG_OFFLOAD_BUNDLE__" /// The index of the host input in the list of inputs. static unsigned HostInputIndex = ~0u; /// Path to the current binary. static std::string BundlerExecutable; /// Obtain the offload kind and real machine triple out of the target /// information specified by the user. static void getOffloadKindAndTriple(StringRef Target, StringRef &OffloadKind, StringRef &Triple) { auto KindTriplePair = Target.split('-'); OffloadKind = KindTriplePair.first; Triple = KindTriplePair.second; } static bool hasHostKind(StringRef Target) { StringRef OffloadKind; StringRef Triple; getOffloadKindAndTriple(Target, OffloadKind, Triple); return OffloadKind == "host"; } /// Generic file handler interface. class FileHandler { public: + struct BundleInfo { + StringRef BundleID; + }; + FileHandler() {} virtual ~FileHandler() {} /// Update the file handler with information from the header of the bundled /// file. virtual Error ReadHeader(MemoryBuffer &Input) = 0; /// Read the marker of the next bundled to be read in the file. The bundle /// name is returned if there is one in the file, or `None` if there are no /// more bundles to be read. virtual Expected> ReadBundleStart(MemoryBuffer &Input) = 0; /// Read the marker that closes the current bundle. virtual Error ReadBundleEnd(MemoryBuffer &Input) = 0; /// Read the current bundle and write the result into the stream \a OS. virtual Error ReadBundle(raw_fd_ostream &OS, MemoryBuffer &Input) = 0; /// Write the header of the bundled file to \a OS based on the information /// gathered from \a Inputs. virtual Error WriteHeader(raw_fd_ostream &OS, ArrayRef> Inputs) = 0; /// Write the marker that initiates a bundle for the triple \a TargetTriple to /// \a OS. virtual Error WriteBundleStart(raw_fd_ostream &OS, StringRef TargetTriple) = 0; /// Write the marker that closes a bundle for the triple \a TargetTriple to \a /// OS. virtual Error WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) = 0; /// Write the bundle from \a Input into \a OS. virtual Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) = 0; + + /// List bundle IDs in \a Input. + virtual Error listBundleIDs(MemoryBuffer &Input) { + if (Error Err = ReadHeader(Input)) + return Err; + + return forEachBundle(Input, [&](const BundleInfo &Info) -> Error { + llvm::outs() << Info.BundleID << '\n'; + Error Err = listBundleIDsCallback(Input, Info); + if (Err) + return Err; + return Error::success(); + }); + } + + /// For each bundle in \a Input, do \a Func. + Error forEachBundle(MemoryBuffer &Input, + std::function Func) { + while (true) { + Expected> CurTripleOrErr = ReadBundleStart(Input); + if (!CurTripleOrErr) + return CurTripleOrErr.takeError(); + + // No more bundles. + if (!*CurTripleOrErr) + break; + + StringRef CurTriple = **CurTripleOrErr; + assert(!CurTriple.empty()); + + BundleInfo Info{CurTriple}; + if (Error Err = Func(Info)) + return Err; + } + return Error::success(); + } + +protected: + virtual Error listBundleIDsCallback(MemoryBuffer &Input, + const BundleInfo &Info) { + return Error::success(); + } }; /// Handler for binary files. The bundled file will have the following format /// (all integers are stored in little-endian format): /// /// "OFFLOAD_BUNDLER_MAGIC_STR" (ASCII encoding of the string) /// /// NumberOfOffloadBundles (8-byte integer) /// /// OffsetOfBundle1 (8-byte integer) /// SizeOfBundle1 (8-byte integer) /// NumberOfBytesInTripleOfBundle1 (8-byte integer) /// TripleOfBundle1 (byte length defined before) /// /// ... /// /// OffsetOfBundleN (8-byte integer) /// SizeOfBundleN (8-byte integer) /// NumberOfBytesInTripleOfBundleN (8-byte integer) /// TripleOfBundleN (byte length defined before) /// /// Bundle1 /// ... /// BundleN /// Read 8-byte integers from a buffer in little-endian format. static uint64_t Read8byteIntegerFromBuffer(StringRef Buffer, size_t pos) { uint64_t Res = 0; const char *Data = Buffer.data(); for (unsigned i = 0; i < 8; ++i) { Res <<= 8; uint64_t Char = (uint64_t)Data[pos + 7 - i]; Res |= 0xffu & Char; } return Res; } /// Write 8-byte integers to a buffer in little-endian format. static void Write8byteIntegerToBuffer(raw_fd_ostream &OS, uint64_t Val) { for (unsigned i = 0; i < 8; ++i) { char Char = (char)(Val & 0xffu); OS.write(&Char, 1); Val >>= 8; } } class BinaryFileHandler final : public FileHandler { /// Information about the bundles extracted from the header. - struct BundleInfo final { + struct BinaryBundleInfo final : public BundleInfo { /// Size of the bundle. uint64_t Size = 0u; /// Offset at which the bundle starts in the bundled file. uint64_t Offset = 0u; - BundleInfo() {} - BundleInfo(uint64_t Size, uint64_t Offset) : Size(Size), Offset(Offset) {} + BinaryBundleInfo() {} + BinaryBundleInfo(uint64_t Size, uint64_t Offset) + : Size(Size), Offset(Offset) {} }; /// Map between a triple and the corresponding bundle information. - StringMap BundlesInfo; + StringMap BundlesInfo; /// Iterator for the bundle information that is being read. - StringMap::iterator CurBundleInfo; - StringMap::iterator NextBundleInfo; + StringMap::iterator CurBundleInfo; + StringMap::iterator NextBundleInfo; /// Current bundle target to be written. std::string CurWriteBundleTarget; public: BinaryFileHandler() : FileHandler() {} ~BinaryFileHandler() final {} Error ReadHeader(MemoryBuffer &Input) final { StringRef FC = Input.getBuffer(); // Initialize the current bundle with the end of the container. CurBundleInfo = BundlesInfo.end(); // Check if buffer is smaller than magic string. size_t ReadChars = sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1; if (ReadChars > FC.size()) return Error::success(); // Check if no magic was found. StringRef Magic(FC.data(), sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1); if (!Magic.equals(OFFLOAD_BUNDLER_MAGIC_STR)) return Error::success(); // Read number of bundles. if (ReadChars + 8 > FC.size()) return Error::success(); uint64_t NumberOfBundles = Read8byteIntegerFromBuffer(FC, ReadChars); ReadChars += 8; // Read bundle offsets, sizes and triples. for (uint64_t i = 0; i < NumberOfBundles; ++i) { // Read offset. if (ReadChars + 8 > FC.size()) return Error::success(); uint64_t Offset = Read8byteIntegerFromBuffer(FC, ReadChars); ReadChars += 8; // Read size. if (ReadChars + 8 > FC.size()) return Error::success(); uint64_t Size = Read8byteIntegerFromBuffer(FC, ReadChars); ReadChars += 8; // Read triple size. if (ReadChars + 8 > FC.size()) return Error::success(); uint64_t TripleSize = Read8byteIntegerFromBuffer(FC, ReadChars); ReadChars += 8; // Read triple. if (ReadChars + TripleSize > FC.size()) return Error::success(); StringRef Triple(&FC.data()[ReadChars], TripleSize); ReadChars += TripleSize; // Check if the offset and size make sense. if (!Offset || Offset + Size > FC.size()) return Error::success(); assert(BundlesInfo.find(Triple) == BundlesInfo.end() && "Triple is duplicated??"); - BundlesInfo[Triple] = BundleInfo(Size, Offset); + BundlesInfo[Triple] = BinaryBundleInfo(Size, Offset); } // Set the iterator to where we will start to read. CurBundleInfo = BundlesInfo.end(); NextBundleInfo = BundlesInfo.begin(); return Error::success(); } Expected> ReadBundleStart(MemoryBuffer &Input) final { if (NextBundleInfo == BundlesInfo.end()) return None; CurBundleInfo = NextBundleInfo++; return CurBundleInfo->first(); } Error ReadBundleEnd(MemoryBuffer &Input) final { assert(CurBundleInfo != BundlesInfo.end() && "Invalid reader info!"); return Error::success(); } Error ReadBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { assert(CurBundleInfo != BundlesInfo.end() && "Invalid reader info!"); StringRef FC = Input.getBuffer(); OS.write(FC.data() + CurBundleInfo->second.Offset, CurBundleInfo->second.Size); return Error::success(); } Error WriteHeader(raw_fd_ostream &OS, ArrayRef> Inputs) final { // Compute size of the header. uint64_t HeaderSize = 0; HeaderSize += sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1; HeaderSize += 8; // Number of Bundles for (auto &T : TargetNames) { HeaderSize += 3 * 8; // Bundle offset, Size of bundle and size of triple. HeaderSize += T.size(); // The triple. } // Write to the buffer the header. OS << OFFLOAD_BUNDLER_MAGIC_STR; Write8byteIntegerToBuffer(OS, TargetNames.size()); unsigned Idx = 0; for (auto &T : TargetNames) { MemoryBuffer &MB = *Inputs[Idx++]; HeaderSize = alignTo(HeaderSize, BundleAlignment); // Bundle offset. Write8byteIntegerToBuffer(OS, HeaderSize); // Size of the bundle (adds to the next bundle's offset) Write8byteIntegerToBuffer(OS, MB.getBufferSize()); - BundlesInfo[T] = BundleInfo(MB.getBufferSize(), HeaderSize); + BundlesInfo[T] = BinaryBundleInfo(MB.getBufferSize(), HeaderSize); HeaderSize += MB.getBufferSize(); // Size of the triple Write8byteIntegerToBuffer(OS, T.size()); // Triple OS << T; } return Error::success(); } Error WriteBundleStart(raw_fd_ostream &OS, StringRef TargetTriple) final { CurWriteBundleTarget = TargetTriple.str(); return Error::success(); } Error WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) final { return Error::success(); } Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { auto BI = BundlesInfo[CurWriteBundleTarget]; OS.seek(BI.Offset); OS.write(Input.getBufferStart(), Input.getBufferSize()); return Error::success(); } }; namespace { // This class implements a list of temporary files that are removed upon // object destruction. class TempFileHandlerRAII { public: ~TempFileHandlerRAII() { for (const auto &File : Files) sys::fs::remove(File); } // Creates temporary file with given contents. Expected Create(Optional> Contents) { SmallString<128u> File; if (std::error_code EC = sys::fs::createTemporaryFile("clang-offload-bundler", "tmp", File)) return createFileError(File, EC); Files.push_front(File); if (Contents) { std::error_code EC; raw_fd_ostream OS(File, EC); if (EC) return createFileError(File, EC); OS.write(Contents->data(), Contents->size()); } return Files.front(); } private: std::forward_list> Files; }; } // end anonymous namespace /// Handler for object files. The bundles are organized by sections with a /// designated name. /// /// To unbundle, we just copy the contents of the designated section. class ObjectFileHandler final : public FileHandler { /// The object file we are currently dealing with. std::unique_ptr Obj; /// Return the input file contents. StringRef getInputFileContents() const { return Obj->getData(); } /// Return bundle name (-) if the provided section is an offload /// section. static Expected> IsOffloadSection(SectionRef CurSection) { Expected NameOrErr = CurSection.getName(); if (!NameOrErr) return NameOrErr.takeError(); // If it does not start with the reserved suffix, just skip this section. if (!NameOrErr->startswith(OFFLOAD_BUNDLER_MAGIC_STR)) return None; // Return the triple that is right after the reserved prefix. return NameOrErr->substr(sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1); } /// Total number of inputs. unsigned NumberOfInputs = 0; /// Total number of processed inputs, i.e, inputs that were already /// read from the buffers. unsigned NumberOfProcessedInputs = 0; /// Iterator of the current and next section. section_iterator CurrentSection; section_iterator NextSection; public: ObjectFileHandler(std::unique_ptr ObjIn) : FileHandler(), Obj(std::move(ObjIn)), CurrentSection(Obj->section_begin()), NextSection(Obj->section_begin()) {} ~ObjectFileHandler() final {} Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); } Expected> ReadBundleStart(MemoryBuffer &Input) final { while (NextSection != Obj->section_end()) { CurrentSection = NextSection; ++NextSection; // Check if the current section name starts with the reserved prefix. If // so, return the triple. Expected> TripleOrErr = IsOffloadSection(*CurrentSection); if (!TripleOrErr) return TripleOrErr.takeError(); if (*TripleOrErr) return **TripleOrErr; } return None; } Error ReadBundleEnd(MemoryBuffer &Input) final { return Error::success(); } Error ReadBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { Expected ContentOrErr = CurrentSection->getContents(); if (!ContentOrErr) return ContentOrErr.takeError(); StringRef Content = *ContentOrErr; // Copy fat object contents to the output when extracting host bundle. if (Content.size() == 1u && Content.front() == 0) Content = StringRef(Input.getBufferStart(), Input.getBufferSize()); OS.write(Content.data(), Content.size()); return Error::success(); } Error WriteHeader(raw_fd_ostream &OS, ArrayRef> Inputs) final { assert(HostInputIndex != ~0u && "Host input index not defined."); // Record number of inputs. NumberOfInputs = Inputs.size(); return Error::success(); } Error WriteBundleStart(raw_fd_ostream &OS, StringRef TargetTriple) final { ++NumberOfProcessedInputs; return Error::success(); } Error WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) final { assert(NumberOfProcessedInputs <= NumberOfInputs && "Processing more inputs that actually exist!"); assert(HostInputIndex != ~0u && "Host input index not defined."); // If this is not the last output, we don't have to do anything. if (NumberOfProcessedInputs != NumberOfInputs) return Error::success(); // We will use llvm-objcopy to add target objects sections to the output // fat object. These sections should have 'exclude' flag set which tells // link editor to remove them from linker inputs when linking executable or // shared library. llvm-objcopy currently does not support adding new // section and changing flags for the added section in one invocation, and // because of that we have to run it two times. First run adds sections and // the second changes flags. // TODO: change it to one run once llvm-objcopy starts supporting that. // Find llvm-objcopy in order to create the bundle binary. ErrorOr Objcopy = sys::findProgramByName( "llvm-objcopy", sys::path::parent_path(BundlerExecutable)); if (!Objcopy) Objcopy = sys::findProgramByName("llvm-objcopy"); if (!Objcopy) return createStringError(Objcopy.getError(), "unable to find 'llvm-objcopy' in path"); // We write to the output file directly. So, we close it and use the name // to pass down to llvm-objcopy. OS.close(); // Temporary files that need to be removed. TempFileHandlerRAII TempFiles; // Create an intermediate temporary file to save object after the first // llvm-objcopy run. Expected IntermediateObjOrErr = TempFiles.Create(None); if (!IntermediateObjOrErr) return IntermediateObjOrErr.takeError(); StringRef IntermediateObj = *IntermediateObjOrErr; // Compose llvm-objcopy command line for add target objects' sections. BumpPtrAllocator Alloc; StringSaver SS{Alloc}; SmallVector ObjcopyArgs{"llvm-objcopy"}; for (unsigned I = 0; I < NumberOfInputs; ++I) { StringRef InputFile = InputFileNames[I]; if (I == HostInputIndex) { // Special handling for the host bundle. We do not need to add a // standard bundle for the host object since we are going to use fat // object as a host object. Therefore use dummy contents (one zero byte) // when creating section for the host bundle. Expected TempFileOrErr = TempFiles.Create(ArrayRef(0)); if (!TempFileOrErr) return TempFileOrErr.takeError(); InputFile = *TempFileOrErr; } ObjcopyArgs.push_back(SS.save(Twine("--add-section=") + OFFLOAD_BUNDLER_MAGIC_STR + TargetNames[I] + "=" + InputFile)); } ObjcopyArgs.push_back(InputFileNames[HostInputIndex]); ObjcopyArgs.push_back(IntermediateObj); if (Error Err = executeObjcopy(*Objcopy, ObjcopyArgs)) return Err; // And run llvm-objcopy for the second time to update section flags. ObjcopyArgs.resize(1); for (unsigned I = 0; I < NumberOfInputs; ++I) ObjcopyArgs.push_back(SS.save(Twine("--set-section-flags=") + OFFLOAD_BUNDLER_MAGIC_STR + TargetNames[I] + "=readonly,exclude")); ObjcopyArgs.push_back(IntermediateObj); ObjcopyArgs.push_back(OutputFileNames.front()); if (Error Err = executeObjcopy(*Objcopy, ObjcopyArgs)) return Err; return Error::success(); } Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { return Error::success(); } private: static Error executeObjcopy(StringRef Objcopy, ArrayRef Args) { // If the user asked for the commands to be printed out, we do that // instead of executing it. if (PrintExternalCommands) { errs() << "\"" << Objcopy << "\""; for (StringRef Arg : drop_begin(Args, 1)) errs() << " \"" << Arg << "\""; errs() << "\n"; } else { if (sys::ExecuteAndWait(Objcopy, Args)) return createStringError(inconvertibleErrorCode(), "'llvm-objcopy' tool failed"); } return Error::success(); } }; /// Handler for text files. The bundled file will have the following format. /// /// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple" /// Bundle 1 /// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple" /// ... /// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple" /// Bundle N /// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple" class TextFileHandler final : public FileHandler { /// String that begins a line comment. StringRef Comment; /// String that initiates a bundle. std::string BundleStartString; /// String that closes a bundle. std::string BundleEndString; /// Number of chars read from input. size_t ReadChars = 0u; protected: Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); } Expected> ReadBundleStart(MemoryBuffer &Input) final { StringRef FC = Input.getBuffer(); // Find start of the bundle. ReadChars = FC.find(BundleStartString, ReadChars); if (ReadChars == FC.npos) return None; // Get position of the triple. size_t TripleStart = ReadChars = ReadChars + BundleStartString.size(); // Get position that closes the triple. size_t TripleEnd = ReadChars = FC.find("\n", ReadChars); if (TripleEnd == FC.npos) return None; // Next time we read after the new line. ++ReadChars; return StringRef(&FC.data()[TripleStart], TripleEnd - TripleStart); } Error ReadBundleEnd(MemoryBuffer &Input) final { StringRef FC = Input.getBuffer(); // Read up to the next new line. assert(FC[ReadChars] == '\n' && "The bundle should end with a new line."); size_t TripleEnd = ReadChars = FC.find("\n", ReadChars + 1); if (TripleEnd != FC.npos) // Next time we read after the new line. ++ReadChars; return Error::success(); } Error ReadBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { StringRef FC = Input.getBuffer(); size_t BundleStart = ReadChars; // Find end of the bundle. size_t BundleEnd = ReadChars = FC.find(BundleEndString, ReadChars); StringRef Bundle(&FC.data()[BundleStart], BundleEnd - BundleStart); OS << Bundle; return Error::success(); } Error WriteHeader(raw_fd_ostream &OS, ArrayRef> Inputs) final { return Error::success(); } Error WriteBundleStart(raw_fd_ostream &OS, StringRef TargetTriple) final { OS << BundleStartString << TargetTriple << "\n"; return Error::success(); } Error WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) final { OS << BundleEndString << TargetTriple << "\n"; return Error::success(); } Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { OS << Input.getBuffer(); return Error::success(); } public: TextFileHandler(StringRef Comment) : FileHandler(), Comment(Comment), ReadChars(0) { BundleStartString = "\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__START__ "; BundleEndString = "\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__END__ "; } + + Error listBundleIDsCallback(MemoryBuffer &Input, + const BundleInfo &Info) final { + // TODO: To list bundle IDs in a bundled text file we need to go through + // all bundles. The format of bundled text file may need to include a + // header if the performance of listing bundle IDs of bundled text file is + // important. + ReadChars = Input.getBuffer().find(BundleEndString, ReadChars); + if (Error Err = ReadBundleEnd(Input)) + return Err; + return Error::success(); + } }; /// Return an appropriate object file handler. We use the specific object /// handler if we know how to deal with that format, otherwise we use a default /// binary file handler. static std::unique_ptr CreateObjectFileHandler(MemoryBuffer &FirstInput) { // Check if the input file format is one that we know how to deal with. Expected> BinaryOrErr = createBinary(FirstInput); // We only support regular object files. If failed to open the input as a // known binary or this is not an object file use the default binary handler. if (errorToBool(BinaryOrErr.takeError()) || !isa(*BinaryOrErr)) return std::make_unique(); // Otherwise create an object file handler. The handler will be owned by the // client of this function. return std::make_unique( std::unique_ptr(cast(BinaryOrErr->release()))); } /// Return an appropriate handler given the input files and options. static Expected> CreateFileHandler(MemoryBuffer &FirstInput) { if (FilesType == "i") return std::make_unique(/*Comment=*/"//"); if (FilesType == "ii") return std::make_unique(/*Comment=*/"//"); if (FilesType == "cui") return std::make_unique(/*Comment=*/"//"); // TODO: `.d` should be eventually removed once `-M` and its variants are // handled properly in offload compilation. if (FilesType == "d") return std::make_unique(/*Comment=*/"#"); if (FilesType == "ll") return std::make_unique(/*Comment=*/";"); if (FilesType == "bc") return std::make_unique(); if (FilesType == "s") return std::make_unique(/*Comment=*/"#"); if (FilesType == "o") return CreateObjectFileHandler(FirstInput); if (FilesType == "gch") return std::make_unique(); if (FilesType == "ast") return std::make_unique(); return createStringError(errc::invalid_argument, "'" + FilesType + "': invalid file type specified"); } /// Bundle the files. Return true if an error was found. static Error BundleFiles() { std::error_code EC; // Create output file. raw_fd_ostream OutputFile(OutputFileNames.front(), EC, sys::fs::OF_None); if (EC) return createFileError(OutputFileNames.front(), EC); // Open input files. SmallVector, 8u> InputBuffers; InputBuffers.reserve(InputFileNames.size()); for (auto &I : InputFileNames) { ErrorOr> CodeOrErr = MemoryBuffer::getFileOrSTDIN(I); if (std::error_code EC = CodeOrErr.getError()) return createFileError(I, EC); InputBuffers.emplace_back(std::move(*CodeOrErr)); } // Get the file handler. We use the host buffer as reference. assert(HostInputIndex != ~0u && "Host input index undefined??"); Expected> FileHandlerOrErr = CreateFileHandler(*InputBuffers[HostInputIndex]); if (!FileHandlerOrErr) return FileHandlerOrErr.takeError(); std::unique_ptr &FH = *FileHandlerOrErr; assert(FH); // Write header. if (Error Err = FH->WriteHeader(OutputFile, InputBuffers)) return Err; // Write all bundles along with the start/end markers. If an error was found // writing the end of the bundle component, abort the bundle writing. auto Input = InputBuffers.begin(); for (auto &Triple : TargetNames) { if (Error Err = FH->WriteBundleStart(OutputFile, Triple)) return Err; if (Error Err = FH->WriteBundle(OutputFile, **Input)) return Err; if (Error Err = FH->WriteBundleEnd(OutputFile, Triple)) return Err; ++Input; } return Error::success(); } +// List bundle IDs. Return true if an error was found. +static Error ListBundleIDsInFile(StringRef InputFileName) { + // Open Input file. + ErrorOr> CodeOrErr = + MemoryBuffer::getFileOrSTDIN(InputFileName); + if (std::error_code EC = CodeOrErr.getError()) + return createFileError(InputFileName, EC); + + MemoryBuffer &Input = **CodeOrErr; + + // Select the right files handler. + Expected> FileHandlerOrErr = + CreateFileHandler(Input); + if (!FileHandlerOrErr) + return FileHandlerOrErr.takeError(); + + std::unique_ptr &FH = *FileHandlerOrErr; + assert(FH); + return FH->listBundleIDs(Input); +} + // Unbundle the files. Return true if an error was found. static Error UnbundleFiles() { // Open Input file. ErrorOr> CodeOrErr = MemoryBuffer::getFileOrSTDIN(InputFileNames.front()); if (std::error_code EC = CodeOrErr.getError()) return createFileError(InputFileNames.front(), EC); MemoryBuffer &Input = **CodeOrErr; // Select the right files handler. Expected> FileHandlerOrErr = CreateFileHandler(Input); if (!FileHandlerOrErr) return FileHandlerOrErr.takeError(); std::unique_ptr &FH = *FileHandlerOrErr; assert(FH); // Read the header of the bundled file. if (Error Err = FH->ReadHeader(Input)) return Err; // Create a work list that consist of the map triple/output file. StringMap Worklist; auto Output = OutputFileNames.begin(); for (auto &Triple : TargetNames) { Worklist[Triple] = *Output; ++Output; } // Read all the bundles that are in the work list. If we find no bundles we // assume the file is meant for the host target. bool FoundHostBundle = false; while (!Worklist.empty()) { Expected> CurTripleOrErr = FH->ReadBundleStart(Input); if (!CurTripleOrErr) return CurTripleOrErr.takeError(); // We don't have more bundles. if (!*CurTripleOrErr) break; StringRef CurTriple = **CurTripleOrErr; assert(!CurTriple.empty()); auto Output = Worklist.find(CurTriple); // The file may have more bundles for other targets, that we don't care // about. Therefore, move on to the next triple if (Output == Worklist.end()) continue; // Check if the output file can be opened and copy the bundle to it. std::error_code EC; raw_fd_ostream OutputFile(Output->second, EC, sys::fs::OF_None); if (EC) return createFileError(Output->second, EC); if (Error Err = FH->ReadBundle(OutputFile, Input)) return Err; if (Error Err = FH->ReadBundleEnd(Input)) return Err; Worklist.erase(Output); // Record if we found the host bundle. if (hasHostKind(CurTriple)) FoundHostBundle = true; } if (!AllowMissingBundles && !Worklist.empty()) { std::string ErrMsg = "Can't find bundles for"; std::set Sorted; for (auto &E : Worklist) Sorted.insert(E.first()); unsigned I = 0; unsigned Last = Sorted.size() - 1; for (auto &E : Sorted) { if (I != 0 && Last > 1) ErrMsg += ","; ErrMsg += " "; if (I == Last && I != 0) ErrMsg += "and "; ErrMsg += E.str(); ++I; } return createStringError(inconvertibleErrorCode(), ErrMsg); } // If no bundles were found, assume the input file is the host bundle and // create empty files for the remaining targets. if (Worklist.size() == TargetNames.size()) { for (auto &E : Worklist) { std::error_code EC; raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None); if (EC) return createFileError(E.second, EC); // If this entry has a host kind, copy the input file to the output file. if (hasHostKind(E.first())) OutputFile.write(Input.getBufferStart(), Input.getBufferSize()); } return Error::success(); } // If we found elements, we emit an error if none of those were for the host // in case host bundle name was provided in command line. if (!FoundHostBundle && HostInputIndex != ~0u) return createStringError(inconvertibleErrorCode(), "Can't find bundle for the host target"); // If we still have any elements in the worklist, create empty files for them. for (auto &E : Worklist) { std::error_code EC; raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None); if (EC) return createFileError(E.second, EC); } return Error::success(); } static void PrintVersion(raw_ostream &OS) { OS << clang::getClangToolFullVersion("clang-offload-bundler") << '\n'; } int main(int argc, const char **argv) { sys::PrintStackTraceOnErrorSignal(argv[0]); cl::HideUnrelatedOptions(ClangOffloadBundlerCategory); cl::SetVersionPrinter(PrintVersion); cl::ParseCommandLineOptions( argc, argv, "A tool to bundle several input files of the specified type \n" "referring to the same source file but different targets into a single \n" "one. The resulting file can also be unbundled into different files by \n" "this tool if -unbundle is provided.\n"); if (Help) { cl::PrintHelpMessage(); return 0; } auto reportError = [argv](Error E) { logAllUnhandledErrors(std::move(E), WithColor::error(errs(), argv[0])); + exit(1); + }; + + auto doWork = [&](std::function Work) { + // Save the current executable directory as it will be useful to find other + // tools. + BundlerExecutable = argv[0]; + if (!llvm::sys::fs::exists(BundlerExecutable)) + BundlerExecutable = + sys::fs::getMainExecutable(argv[0], &BundlerExecutable); + + if (llvm::Error Err = Work()) { + reportError(std::move(Err)); + } }; - bool Error = false; + if (ListBundleIDs) { + if (Unbundle) { + reportError( + createStringError(errc::invalid_argument, + "-unbundle and -list cannot be used together")); + } + if (InputFileNames.size() != 1) { + reportError(createStringError(errc::invalid_argument, + "only one input file supported for -list")); + } + if (OutputFileNames.size()) { + reportError(createStringError(errc::invalid_argument, + "-outputs option is invalid for -list")); + } + if (TargetNames.size()) { + reportError(createStringError(errc::invalid_argument, + "-targets option is invalid for -list")); + } + + doWork([]() { return ListBundleIDsInFile(InputFileNames.front()); }); + return 0; + } + + if (OutputFileNames.getNumOccurrences() == 0) { + reportError(createStringError( + errc::invalid_argument, + "for the --outputs option: must be specified at least once!")); + } + if (TargetNames.getNumOccurrences() == 0) { + reportError(createStringError( + errc::invalid_argument, + "for the --targets option: must be specified at least once!")); + } if (Unbundle) { if (InputFileNames.size() != 1) { - Error = true; reportError(createStringError( errc::invalid_argument, "only one input file supported in unbundling mode")); } if (OutputFileNames.size() != TargetNames.size()) { - Error = true; reportError(createStringError(errc::invalid_argument, "number of output files and targets should " "match in unbundling mode")); } } else { if (OutputFileNames.size() != 1) { - Error = true; reportError(createStringError( errc::invalid_argument, "only one output file supported in bundling mode")); } if (InputFileNames.size() != TargetNames.size()) { - Error = true; reportError(createStringError( errc::invalid_argument, "number of input files and targets should match in bundling mode")); } } // Verify that the offload kinds and triples are known. We also check that we // have exactly one host target. unsigned Index = 0u; unsigned HostTargetNum = 0u; llvm::DenseSet ParsedTargets; for (StringRef Target : TargetNames) { if (ParsedTargets.contains(Target)) { reportError(createStringError(errc::invalid_argument, "Duplicate targets are not allowed")); - return 1; } ParsedTargets.insert(Target); StringRef Kind; StringRef Triple; getOffloadKindAndTriple(Target, Kind, Triple); bool KindIsValid = !Kind.empty(); KindIsValid = KindIsValid && StringSwitch(Kind) .Case("host", true) .Case("openmp", true) .Case("hip", true) .Default(false); bool TripleIsValid = !Triple.empty(); llvm::Triple T(Triple); TripleIsValid &= T.getArch() != Triple::UnknownArch; if (!KindIsValid || !TripleIsValid) { - Error = true; - SmallVector Buf; raw_svector_ostream Msg(Buf); Msg << "invalid target '" << Target << "'"; if (!KindIsValid) Msg << ", unknown offloading kind '" << Kind << "'"; if (!TripleIsValid) Msg << ", unknown target triple '" << Triple << "'"; reportError(createStringError(errc::invalid_argument, Msg.str())); } if (KindIsValid && Kind == "host") { ++HostTargetNum; // Save the index of the input that refers to the host. HostInputIndex = Index; } ++Index; } // Host triple is not really needed for unbundling operation, so do not // treat missing host triple as error if we do unbundling. if ((Unbundle && HostTargetNum > 1) || (!Unbundle && HostTargetNum != 1)) { - Error = true; reportError(createStringError(errc::invalid_argument, "expecting exactly one host target but got " + Twine(HostTargetNum))); } - if (Error) - return 1; - - // Save the current executable directory as it will be useful to find other - // tools. - BundlerExecutable = argv[0]; - if (!llvm::sys::fs::exists(BundlerExecutable)) - BundlerExecutable = sys::fs::getMainExecutable(argv[0], &BundlerExecutable); - - if (llvm::Error Err = Unbundle ? UnbundleFiles() : BundleFiles()) { - reportError(std::move(Err)); - return 1; - } + doWork([]() { return Unbundle ? UnbundleFiles() : BundleFiles(); }); return 0; }