Skip to content

Commit f1aa7f4

Browse files
authored
[Driver][SYCL] Remove partial-link path when dealing with fat static archives (#2867)
For Linux, we would use partial linking to generate the object that contained the device binaries from a fat static archive. We are moving away from using partial linking to provide a better overall solution that is the same in regards to link behaviors for both Linux and Windows. The change here will allow for the extraction of device binaries from archives occur in the same manner as it does for Windows. We do full extraction from the archives, and do a full llvm-link with those extracted device binaries.
1 parent 7867089 commit f1aa7f4

File tree

8 files changed

+137
-224
lines changed

8 files changed

+137
-224
lines changed

clang/lib/Driver/Driver.cpp

Lines changed: 28 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -4605,41 +4605,27 @@ class OffloadingActionBuilder final {
46054605
!InputArg->getOption().hasFlag(options::LinkerInput) &&
46064606
!types::isSrcFile(HostAction->getType())) {
46074607
std::string InputName = InputArg->getAsString(Args);
4608-
// Do not create an unbundling action for an object when we know a fat
4609-
// static library is being used. A separate unbundling action is created
4610-
// for all objects and the fat static library.
4611-
// But in MSVC environment static offload archives are handled differently
4612-
// due to absence of partial linking support in the linker. Instead of
4613-
// partially linking input objects and static archives and then unbundling
4614-
// result we are unbundling all objects and offload archives to extract
4615-
// device parts. Therefore, in case on MSVC environment unbundling action
4616-
// for objects is still needed.
4617-
if (C.getDefaultToolChain().getTriple().isWindowsMSVCEnvironment() ||
4608+
ActionList HostActionList;
4609+
Action *A(HostAction);
4610+
// Only check for FPGA device information when using fpga SubArch.
4611+
if (Args.hasArg(options::OPT_fintelfpga) &&
46184612
!(HostAction->getType() == types::TY_Object &&
4619-
isObjectFile(InputName) &&
4620-
C.getDriver().getOffloadStaticLibSeen())) {
4621-
ActionList HostActionList;
4622-
Action *A(HostAction);
4623-
// Only check for FPGA device information when using fpga SubArch.
4624-
if (Args.hasArg(options::OPT_fintelfpga) &&
4625-
!(HostAction->getType() == types::TY_Object &&
4626-
isObjectFile(InputName))) {
4627-
// Type FPGA aoco is a special case for -foffload-static-lib.
4628-
if (HostAction->getType() == types::TY_FPGA_AOCO) {
4629-
if (!hasFPGABinary(C, InputName, types::TY_FPGA_AOCO))
4630-
return false;
4631-
A = C.MakeAction<InputAction>(*InputArg, types::TY_FPGA_AOCO);
4632-
} else if (hasFPGABinary(C, InputName, types::TY_FPGA_AOCX))
4633-
A = C.MakeAction<InputAction>(*InputArg, types::TY_FPGA_AOCX);
4634-
else if (hasFPGABinary(C, InputName, types::TY_FPGA_AOCR))
4635-
A = C.MakeAction<InputAction>(*InputArg, types::TY_FPGA_AOCR);
4636-
}
4637-
auto UnbundlingHostAction = C.MakeAction<OffloadUnbundlingJobAction>(A);
4638-
UnbundlingHostAction->registerDependentActionInfo(
4639-
C.getSingleOffloadToolChain<Action::OFK_Host>(),
4640-
/*BoundArch=*/StringRef(), Action::OFK_Host);
4641-
HostAction = UnbundlingHostAction;
4613+
isObjectFile(InputName))) {
4614+
// Type FPGA aoco is a special case for -foffload-static-lib.
4615+
if (HostAction->getType() == types::TY_FPGA_AOCO) {
4616+
if (!hasFPGABinary(C, InputName, types::TY_FPGA_AOCO))
4617+
return false;
4618+
A = C.MakeAction<InputAction>(*InputArg, types::TY_FPGA_AOCO);
4619+
} else if (hasFPGABinary(C, InputName, types::TY_FPGA_AOCX))
4620+
A = C.MakeAction<InputAction>(*InputArg, types::TY_FPGA_AOCX);
4621+
else if (hasFPGABinary(C, InputName, types::TY_FPGA_AOCR))
4622+
A = C.MakeAction<InputAction>(*InputArg, types::TY_FPGA_AOCR);
46424623
}
4624+
auto UnbundlingHostAction = C.MakeAction<OffloadUnbundlingJobAction>(A);
4625+
UnbundlingHostAction->registerDependentActionInfo(
4626+
C.getSingleOffloadToolChain<Action::OFK_Host>(),
4627+
/*BoundArch=*/StringRef(), Action::OFK_Host);
4628+
HostAction = UnbundlingHostAction;
46434629
}
46444630

46454631
assert(HostAction && "Invalid host action!");
@@ -5096,84 +5082,9 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
50965082
OffloadBuilder.appendTopLevelLinkAction(Actions);
50975083

50985084
// Go through all of the args, and create a Linker specific argument list.
5099-
// When dealing with fat static archives, this is fed into the partial link
5100-
// step on Linux or each archive is individually addressed on Windows.
5085+
// When dealing with fat static archives each archive is individually
5086+
// unbundled.
51015087
SmallVector<const char *, 16> LinkArgs(getLinkerArgs(C, Args));
5102-
// When a static fat archive is provided, create a new unbundling step
5103-
// for all of the objects.
5104-
if (!C.getDefaultToolChain().getTriple().isWindowsMSVCEnvironment() &&
5105-
C.getDriver().getOffloadStaticLibSeen()) {
5106-
ActionList UnbundlerInputs;
5107-
for (const auto &LI : LinkerInputs) {
5108-
// Unbundler only handles objects.
5109-
if (auto *IA = dyn_cast<InputAction>(LI)) {
5110-
std::string FileName = IA->getInputArg().getAsString(Args);
5111-
if ((IA->getType() == types::TY_Object && !isObjectFile(FileName)) ||
5112-
IA->getInputArg().getOption().hasFlag(options::LinkerInput))
5113-
// Pass the Input along to linker only.
5114-
continue;
5115-
}
5116-
UnbundlerInputs.push_back(LI);
5117-
}
5118-
const Arg *LastArg = nullptr;
5119-
auto addUnbundlerInput = [&](types::ID T, const StringRef &A) {
5120-
const llvm::opt::OptTable &Opts = getOpts();
5121-
Arg *InputArg = MakeInputArg(Args, Opts, C.getArgs().MakeArgString(A));
5122-
LastArg = InputArg;
5123-
Action *Current = C.MakeAction<InputAction>(*InputArg, T);
5124-
UnbundlerInputs.push_back(Current);
5125-
};
5126-
bool IsWholeArchive = false;
5127-
for (StringRef LA : LinkArgs) {
5128-
if (isStaticArchiveFile(LA)) {
5129-
addUnbundlerInput(
5130-
IsWholeArchive ? types::TY_WholeArchive : types::TY_Archive, LA);
5131-
continue;
5132-
}
5133-
if (optionMatches("-no-whole-archive", LA.str())) {
5134-
IsWholeArchive = false;
5135-
continue;
5136-
}
5137-
if (optionMatches("-whole-archive", LA.str())) {
5138-
IsWholeArchive = true;
5139-
continue;
5140-
}
5141-
if (isObjectFile(LA.str())) {
5142-
// Add any objects to the unbundler step. These objects are passed
5143-
// directly to the linker, so the driver does not know about them.
5144-
// FIXME - Better process objects passed to the linker. We are only
5145-
// adding these objects to the unbundler step, but these objects can
5146-
// potentially be fat objects that should be processed by the driver.
5147-
addUnbundlerInput(types::TY_Object, LA);
5148-
continue;
5149-
}
5150-
}
5151-
5152-
if (!UnbundlerInputs.empty() && !PL.empty()) {
5153-
Action *PartialLink =
5154-
C.MakeAction<PartialLinkJobAction>(UnbundlerInputs, types::TY_Object);
5155-
if (!LastArg) {
5156-
auto *TA = dyn_cast<Action>(UnbundlerInputs.back());
5157-
assert(TA->getKind() == Action::InputClass ||
5158-
TA->getKind() == Action::OffloadClass);
5159-
5160-
// If the Action is of OffloadAction type, use the HostAction of the
5161-
// specific Offload Action to set LastArg
5162-
if (auto *OA = dyn_cast<OffloadAction>(UnbundlerInputs.back()))
5163-
LastArg =
5164-
&(dyn_cast<InputAction>(OA->getHostDependence())->getInputArg());
5165-
else if (auto *IA = dyn_cast<InputAction>(UnbundlerInputs.back()))
5166-
// else set the LastArg based on Last InputAction
5167-
LastArg = &(IA->getInputArg());
5168-
}
5169-
Action *Current = C.MakeAction<InputAction>(*LastArg, types::TY_Object);
5170-
ActionList AL;
5171-
AL.push_back(PartialLink);
5172-
OffloadBuilder.addHostDependenceToUnbundlingAction(Current, AL, LastArg);
5173-
Current = OffloadBuilder.addDeviceDependencesToHostAction(Current,
5174-
LastArg, phases::Link, PL.back(), PL);
5175-
}
5176-
}
51775088
const llvm::opt::OptTable &Opts = getOpts();
51785089
auto unbundleStaticLib = [&](types::ID T, const StringRef &A) {
51795090
Arg *InputArg = MakeInputArg(Args, Opts, Args.MakeArgString(A));
@@ -5191,14 +5102,12 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
51915102
// as they have already been unbundled and processed for linking.
51925103
if (hasFPGABinary(C, LA.str(), types::TY_FPGA_AOCX))
51935104
continue;
5194-
// In MSVC environment offload-static-libs are handled slightly different
5195-
// because of missing support for partial linking in the linker. We add an
5196-
// unbundling action for each static archive which produces list files with
5197-
// extracted objects. Device lists are then added to the appropriate device
5198-
// link actions and host list is ignored since we are adding
5199-
// offload-static-libs as normal libraries to the host link command.
5200-
if (C.getDefaultToolChain().getTriple().isWindowsMSVCEnvironment() &&
5201-
hasOffloadSections(C, LA, Args))
5105+
// For offload-static-libs we add an unbundling action for each static
5106+
// archive which produces list files with extracted objects. Device lists
5107+
// are then added to the appropriate device link actions and host list is
5108+
// ignored since we are adding offload-static-libs as normal libraries to
5109+
// the host link command.
5110+
if (hasOffloadSections(C, LA, Args))
52025111
unbundleStaticLib(types::TY_Archive, LA);
52035112
// Pass along the static libraries to check if we need to add them for
52045113
// unbundling for FPGA AOT static lib usage. Uses FPGA aoco type to
@@ -6160,14 +6069,11 @@ InputInfo Driver::BuildJobsForActionNoCache(
61606069
// unbundling action does not change the type of the output which can
61616070
// cause a overwrite.
61626071
InputInfo CurI;
6163-
bool IsMSVCEnv =
6164-
C.getDefaultToolChain().getTriple().isWindowsMSVCEnvironment();
61656072
bool IsFPGAObjLink = (JA->getType() == types::TY_Object &&
61666073
C.getInputArgs().hasArg(options::OPT_fintelfpga) &&
61676074
C.getInputArgs().hasArg(options::OPT_fsycl_link_EQ));
61686075
if (C.getDriver().getOffloadStaticLibSeen() &&
6169-
(JA->getType() == types::TY_Archive ||
6170-
(JA->getType() == types::TY_Object && !IsMSVCEnv))) {
6076+
JA->getType() == types::TY_Archive) {
61716077
// Host part of the unbundled static archive is not used.
61726078
if (UI.DependentOffloadKind == Action::OFK_Host)
61736079
continue;

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7657,19 +7657,11 @@ void OffloadBundler::ConstructJobMultipleOutputs(
76577657
InputInfo Input = Inputs.front();
76587658
const char *TypeArg = types::getTypeTempSuffix(Input.getType());
76597659
const char *InputFileName = Input.getFilename();
7660-
bool IsMSVCEnv =
7661-
C.getDefaultToolChain().getTriple().isWindowsMSVCEnvironment();
76627660
types::ID InputType(Input.getType());
76637661
bool IsFPGADepUnbundle = JA.getType() == types::TY_FPGA_Dependencies;
76647662
bool IsFPGADepLibUnbundle = JA.getType() == types::TY_FPGA_Dependencies_List;
7665-
bool IsArchiveUnbundle =
7666-
(!IsMSVCEnv && C.getDriver().getOffloadStaticLibSeen() &&
7667-
(types::isArchive(InputType) || InputType == types::TY_Object));
7668-
7669-
if (IsArchiveUnbundle)
7670-
TypeArg = "oo";
7671-
else if (InputType == types::TY_FPGA_AOCX ||
7672-
InputType == types::TY_FPGA_AOCR) {
7663+
7664+
if (InputType == types::TY_FPGA_AOCX || InputType == types::TY_FPGA_AOCR) {
76737665
// Override type with archive object
76747666
if (getToolChain().getTriple().getSubArch() ==
76757667
llvm::Triple::SPIRSubArch_fpga)
@@ -7678,7 +7670,7 @@ void OffloadBundler::ConstructJobMultipleOutputs(
76787670
TypeArg = "aoo";
76797671
}
76807672
if (InputType == types::TY_FPGA_AOCO || IsFPGADepLibUnbundle ||
7681-
(IsMSVCEnv && types::isArchive(InputType)))
7673+
types::isArchive(InputType))
76827674
TypeArg = "aoo";
76837675
if (IsFPGADepUnbundle)
76847676
TypeArg = "o";
@@ -7714,7 +7706,7 @@ void OffloadBundler::ConstructJobMultipleOutputs(
77147706
Triples += Dep.DependentToolChain->getTriple().normalize();
77157707
}
77167708
continue;
7717-
} else if (InputType == types::TY_Archive || IsArchiveUnbundle ||
7709+
} else if (InputType == types::TY_Archive ||
77187710
(TCArgs.hasArg(options::OPT_fintelfpga) &&
77197711
TCArgs.hasArg(options::OPT_fsycl_link_EQ))) {
77207712
// Do not extract host part if we are unbundling archive on Windows

clang/test/Driver/openmp-offload.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -661,12 +661,13 @@
661661
/// ###########################################################################
662662

663663
/// test behaviors of -foffload-static-lib=<lib>
664-
// RUN: touch %t.a
664+
// RUN: echo "void foo(void) {}" > %t1.cpp
665+
// RUN: %clangxx -target x86_64-unknown-linux-gnu -fopenmp -fopenmp-targets=x86_64-pc-linux-gnu %t1.cpp -c -o %t1_bundle.o
666+
// RUN: llvm-ar cr %t.a %t1_bundle.o
665667
// RUN: touch %t.o
666668
// RUN: %clang -fopenmp -fopenmp-targets=x86_64-pc-linux-gnu -foffload-static-lib=%t.a -### %t.o 2>&1 \
667669
// RUN: | FileCheck %s -check-prefix=FOFFLOAD_STATIC_LIB
668-
// FOFFLOAD_STATIC_LIB: ld{{(.exe)?}}" "-r" "-o" {{.*}} "[[INPUT:.+\.o]]"
669-
// FOFFLOAD_STATIC_LIB: clang-offload-bundler{{.*}} "-type=oo"
670+
// FOFFLOAD_STATIC_LIB: clang-offload-bundler{{.*}} "-type=aoo"
670671
// FOFFLOAD_STATIC_LIB: ld{{.*}} "@{{.*}}"
671672

672673
// TODO: SYCL specific fail - analyze and enable

clang/test/Driver/sycl-intelfpga-aoco.cpp

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,17 @@
2828
// CHK-FPGA-AOCO-PHASES: 10: linker, {0, 9}, image, (host-sycl)
2929
// CHK-FPGA-AOCO-PHASES: 11: compiler, {4}, ir, (device-sycl)
3030
// CHK-FPGA-AOCO-PHASES: 12: input, "[[INPUTA]]", archive
31-
// CHK-FPGA-AOCO-PHASES: 13: partial-link, {9, 12}, object
32-
// CHK-FPGA-AOCO-PHASES: 14: clang-offload-unbundler, {13}, object
33-
// CHK-FPGA-AOCO-PHASES: 15: linker, {11, 14}, ir, (device-sycl)
34-
// CHK-FPGA-AOCO-PHASES: 16: sycl-post-link, {15}, ir, (device-sycl)
35-
// CHK-FPGA-AOCO-PHASES: 17: llvm-spirv, {16}, spirv, (device-sycl)
36-
// CHK-FPGA-AOCO-PHASES: 18: input, "[[INPUTA]]", archive
37-
// CHK-FPGA-AOCO-PHASES: 19: clang-offload-unbundler, {18}, fpga_dependencies_list
38-
// CHK-FPGA-AOCO-PHASES: 20: input, "[[INPUTA]]", fpga_aoco
39-
// CHK-FPGA-AOCO-PHASES: 21: clang-offload-unbundler, {20}, fpga_aoco
40-
// CHK-FPGA-AOCO-PHASES: 22: backend-compiler, {17, 19, 21}, fpga_aocx, (device-sycl)
41-
// CHK-FPGA-AOCO-PHASES: 23: clang-offload-wrapper, {22}, object, (device-sycl)
42-
// CHK-FPGA-AOCO-PHASES: 24: offload, "host-sycl (x86_64-unknown-linux-gnu)" {10}, "device-sycl (spir64_fpga-unknown-unknown-sycldevice)" {23}, image
31+
// CHK-FPGA-AOCO-PHASES: 13: clang-offload-unbundler, {12}, archive
32+
// CHK-FPGA-AOCO-PHASES: 14: linker, {11, 13}, ir, (device-sycl)
33+
// CHK-FPGA-AOCO-PHASES: 15: sycl-post-link, {14}, ir, (device-sycl)
34+
// CHK-FPGA-AOCO-PHASES: 16: llvm-spirv, {15}, spirv, (device-sycl)
35+
// CHK-FPGA-AOCO-PHASES: 17: input, "[[INPUTA]]", archive
36+
// CHK-FPGA-AOCO-PHASES: 18: clang-offload-unbundler, {17}, fpga_dependencies_list
37+
// CHK-FPGA-AOCO-PHASES: 19: input, "[[INPUTA]]", fpga_aoco
38+
// CHK-FPGA-AOCO-PHASES: 20: clang-offload-unbundler, {19}, fpga_aoco
39+
// CHK-FPGA-AOCO-PHASES: 21: backend-compiler, {16, 18, 20}, fpga_aocx, (device-sycl)
40+
// CHK-FPGA-AOCO-PHASES: 22: clang-offload-wrapper, {21}, object, (device-sycl)
41+
// CHK-FPGA-AOCO-PHASES: 23: offload, "host-sycl (x86_64-unknown-linux-gnu)" {10}, "device-sycl (spir64_fpga-unknown-unknown-sycldevice)" {22}, image
4342

4443
/// FPGA AOCO Windows phases check
4544
// RUN: %clang_cl -fsycl -fno-sycl-device-lib=all -fintelfpga -foffload-static-lib=%t_aoco_cl.a %s -### -ccc-print-phases 2>&1 \
@@ -78,11 +77,7 @@
7877
// RUN: | FileCheck -check-prefixes=CHK-FPGA-AOCO,CHK-FPGA-AOCO-WIN %s
7978
// RUN: %clang_cl -fsycl -fno-sycl-device-lib=all -fintelfpga %t_aoco_cl.a -### %s 2>&1 \
8079
// RUN: | FileCheck -check-prefixes=CHK-FPGA-AOCO,CHK-FPGA-AOCO-WIN %s
81-
// CHK-FPGA-AOCO-LIN: clang-offload-bundler{{.*}} "-type=ao" "-targets=sycl-fpga_aoco-intel-unknown-sycldevice" "-inputs=[[INPUTLIB:.+\.a]]" "-check-section"
82-
// CHK-FPGA-AOCO-LIN: clang{{.*}} "-emit-obj" {{.*}} "-o" "[[HOSTOBJ:.+\.o]]"
83-
// CHK-FPGA-AOCO-LIN: ld{{.*}} "-r" "-o" "[[PARTLINKOBJ:.+\.o]]" "{{.*}}crt1.o" "{{.*}}crti.o" {{.*}} "[[HOSTOBJ]]" "[[INPUTLIB]]" "{{.*}}crtn.o"
84-
// CHK-FPGA-AOCO-LIN: clang-offload-bundler{{.*}} "-type=oo" "-targets=sycl-spir64_fpga-unknown-unknown-sycldevice" "-inputs=[[PARTLINKOBJ]]" "-outputs={{.*}}" "-unbundle"
85-
// CHK-FPGA-AOCO-WIN: clang-offload-bundler{{.*}} "-type=aoo" "-targets=sycl-spir64_fpga-unknown-unknown-sycldevice" "-inputs=[[INPUTLIB:.+\.a]]" "-outputs={{.*}}" "-unbundle"
80+
// CHK-FPGA-AOCO: clang-offload-bundler{{.*}} "-type=aoo" "-targets=sycl-spir64_fpga-unknown-unknown-sycldevice" "-inputs=[[INPUTLIB:.+\.a]]" "-outputs={{.*}}" "-unbundle"
8681
// CHK-FPGA-AOCO: llvm-link{{.*}} "@{{.*}}" "-o" "[[LINKEDBC:.+\.bc]]"
8782
// CHK-FPGA-AOCO: sycl-post-link{{.*}} "-ir-output-only" "-spec-const=default" "-o" "[[PLINKEDBC:.+\.bc]]" "[[LINKEDBC]]"
8883
// CHK-FPGA-AOCO: llvm-spirv{{.*}} "-o" "[[TARGSPV:.+\.spv]]" {{.*}} "[[PLINKEDBC]]"

clang/test/Driver/sycl-intelfpga-static-lib.cpp

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,15 @@
1515
// CHECK_PHASES: 0: input, "[[INPUT:.+\.a]]", object, (host-sycl)
1616
// CHECK_PHASES: 1: linker, {0}, image, (host-sycl)
1717
// CHECK_PHASES: 2: input, "[[INPUT]]", archive
18-
// CHECK_PHASES: 3: partial-link, {2}, object
19-
// CHECK_PHASES: 4: clang-offload-unbundler, {3}, object
20-
// CHECK_PHASES: 5: linker, {4}, ir, (device-sycl)
21-
// CHECK_PHASES: 6: sycl-post-link, {5}, ir, (device-sycl)
22-
// CHECK_PHASES: 7: llvm-spirv, {6}, spirv, (device-sycl)
23-
// CHECK_PHASES: 8: input, "[[INPUT]]", archive
24-
// CHECK_PHASES: 9: clang-offload-unbundler, {8}, fpga_dependencies_list
25-
// CHECK_PHASES: 10: backend-compiler, {7, 9}, fpga_aocx, (device-sycl)
26-
// CHECK_PHASES: 11: clang-offload-wrapper, {10}, object, (device-sycl)
27-
// CHECK_PHASES: 12: offload, "host-sycl (x86_64-unknown-linux-gnu)" {1}, "device-sycl (spir64_fpga-unknown-unknown-sycldevice)" {11}, image
18+
// CHECK_PHASES: 3: clang-offload-unbundler, {2}, archive
19+
// CHECK_PHASES: 4: linker, {3}, ir, (device-sycl)
20+
// CHECK_PHASES: 5: sycl-post-link, {4}, ir, (device-sycl)
21+
// CHECK_PHASES: 6: llvm-spirv, {5}, spirv, (device-sycl)
22+
// CHECK_PHASES: 7: input, "[[INPUT]]", archive
23+
// CHECK_PHASES: 8: clang-offload-unbundler, {7}, fpga_dependencies_list
24+
// CHECK_PHASES: 9: backend-compiler, {6, 8}, fpga_aocx, (device-sycl)
25+
// CHECK_PHASES: 10: clang-offload-wrapper, {9}, object, (device-sycl)
26+
// CHECK_PHASES: 11: offload, "host-sycl (x86_64-unknown-linux-gnu)" {1}, "device-sycl (spir64_fpga-unknown-unknown-sycldevice)" {10}, image
2827

2928
/// Check for unbundle and use of deps in static lib
3029
// RUN: %clangxx -target x86_64-unknown-linux-gnu -fsycl -fno-sycl-device-lib=all -fintelfpga %t.a -### 2>&1 \

0 commit comments

Comments
 (0)