106f32e7eSjoerg //===-- BareMetal.cpp - Bare Metal ToolChain --------------------*- C++ -*-===//
206f32e7eSjoerg //
306f32e7eSjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
406f32e7eSjoerg // See https://llvm.org/LICENSE.txt for license information.
506f32e7eSjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
606f32e7eSjoerg //
706f32e7eSjoerg //===----------------------------------------------------------------------===//
806f32e7eSjoerg 
906f32e7eSjoerg #include "BareMetal.h"
1006f32e7eSjoerg 
1106f32e7eSjoerg #include "CommonArgs.h"
1206f32e7eSjoerg #include "InputInfo.h"
1306f32e7eSjoerg #include "Gnu.h"
1406f32e7eSjoerg 
15*13fbcb42Sjoerg #include "Arch/RISCV.h"
1606f32e7eSjoerg #include "clang/Driver/Compilation.h"
1706f32e7eSjoerg #include "clang/Driver/Driver.h"
1806f32e7eSjoerg #include "clang/Driver/DriverDiagnostic.h"
1906f32e7eSjoerg #include "clang/Driver/Options.h"
2006f32e7eSjoerg #include "llvm/Option/ArgList.h"
2106f32e7eSjoerg #include "llvm/Support/Path.h"
2206f32e7eSjoerg #include "llvm/Support/VirtualFileSystem.h"
2306f32e7eSjoerg #include "llvm/Support/raw_ostream.h"
2406f32e7eSjoerg 
2506f32e7eSjoerg using namespace llvm::opt;
2606f32e7eSjoerg using namespace clang;
2706f32e7eSjoerg using namespace clang::driver;
2806f32e7eSjoerg using namespace clang::driver::tools;
2906f32e7eSjoerg using namespace clang::driver::toolchains;
3006f32e7eSjoerg 
makeMultilib(StringRef commonSuffix)31*13fbcb42Sjoerg static Multilib makeMultilib(StringRef commonSuffix) {
32*13fbcb42Sjoerg   return Multilib(commonSuffix, commonSuffix, commonSuffix);
33*13fbcb42Sjoerg }
34*13fbcb42Sjoerg 
findRISCVMultilibs(const Driver & D,const llvm::Triple & TargetTriple,const ArgList & Args,DetectedMultilibs & Result)35*13fbcb42Sjoerg static bool findRISCVMultilibs(const Driver &D,
36*13fbcb42Sjoerg                                const llvm::Triple &TargetTriple,
37*13fbcb42Sjoerg                                const ArgList &Args, DetectedMultilibs &Result) {
38*13fbcb42Sjoerg   Multilib::flags_list Flags;
39*13fbcb42Sjoerg   StringRef Arch = riscv::getRISCVArch(Args, TargetTriple);
40*13fbcb42Sjoerg   StringRef Abi = tools::riscv::getRISCVABI(Args, TargetTriple);
41*13fbcb42Sjoerg 
42*13fbcb42Sjoerg   if (TargetTriple.getArch() == llvm::Triple::riscv64) {
43*13fbcb42Sjoerg     Multilib Imac = makeMultilib("").flag("+march=rv64imac").flag("+mabi=lp64");
44*13fbcb42Sjoerg     Multilib Imafdc = makeMultilib("/rv64imafdc/lp64d")
45*13fbcb42Sjoerg                           .flag("+march=rv64imafdc")
46*13fbcb42Sjoerg                           .flag("+mabi=lp64d");
47*13fbcb42Sjoerg 
48*13fbcb42Sjoerg     // Multilib reuse
49*13fbcb42Sjoerg     bool UseImafdc =
50*13fbcb42Sjoerg         (Arch == "rv64imafdc") || (Arch == "rv64gc"); // gc => imafdc
51*13fbcb42Sjoerg 
52*13fbcb42Sjoerg     addMultilibFlag((Arch == "rv64imac"), "march=rv64imac", Flags);
53*13fbcb42Sjoerg     addMultilibFlag(UseImafdc, "march=rv64imafdc", Flags);
54*13fbcb42Sjoerg     addMultilibFlag(Abi == "lp64", "mabi=lp64", Flags);
55*13fbcb42Sjoerg     addMultilibFlag(Abi == "lp64d", "mabi=lp64d", Flags);
56*13fbcb42Sjoerg 
57*13fbcb42Sjoerg     Result.Multilibs = MultilibSet().Either(Imac, Imafdc);
58*13fbcb42Sjoerg     return Result.Multilibs.select(Flags, Result.SelectedMultilib);
59*13fbcb42Sjoerg   }
60*13fbcb42Sjoerg   if (TargetTriple.getArch() == llvm::Triple::riscv32) {
61*13fbcb42Sjoerg     Multilib Imac =
62*13fbcb42Sjoerg         makeMultilib("").flag("+march=rv32imac").flag("+mabi=ilp32");
63*13fbcb42Sjoerg     Multilib I =
64*13fbcb42Sjoerg         makeMultilib("/rv32i/ilp32").flag("+march=rv32i").flag("+mabi=ilp32");
65*13fbcb42Sjoerg     Multilib Im =
66*13fbcb42Sjoerg         makeMultilib("/rv32im/ilp32").flag("+march=rv32im").flag("+mabi=ilp32");
67*13fbcb42Sjoerg     Multilib Iac = makeMultilib("/rv32iac/ilp32")
68*13fbcb42Sjoerg                        .flag("+march=rv32iac")
69*13fbcb42Sjoerg                        .flag("+mabi=ilp32");
70*13fbcb42Sjoerg     Multilib Imafc = makeMultilib("/rv32imafc/ilp32f")
71*13fbcb42Sjoerg                          .flag("+march=rv32imafc")
72*13fbcb42Sjoerg                          .flag("+mabi=ilp32f");
73*13fbcb42Sjoerg 
74*13fbcb42Sjoerg     // Multilib reuse
75*13fbcb42Sjoerg     bool UseI = (Arch == "rv32i") || (Arch == "rv32ic");    // ic => i
76*13fbcb42Sjoerg     bool UseIm = (Arch == "rv32im") || (Arch == "rv32imc"); // imc => im
77*13fbcb42Sjoerg     bool UseImafc = (Arch == "rv32imafc") || (Arch == "rv32imafdc") ||
78*13fbcb42Sjoerg                     (Arch == "rv32gc"); // imafdc,gc => imafc
79*13fbcb42Sjoerg 
80*13fbcb42Sjoerg     addMultilibFlag(UseI, "march=rv32i", Flags);
81*13fbcb42Sjoerg     addMultilibFlag(UseIm, "march=rv32im", Flags);
82*13fbcb42Sjoerg     addMultilibFlag((Arch == "rv32iac"), "march=rv32iac", Flags);
83*13fbcb42Sjoerg     addMultilibFlag((Arch == "rv32imac"), "march=rv32imac", Flags);
84*13fbcb42Sjoerg     addMultilibFlag(UseImafc, "march=rv32imafc", Flags);
85*13fbcb42Sjoerg     addMultilibFlag(Abi == "ilp32", "mabi=ilp32", Flags);
86*13fbcb42Sjoerg     addMultilibFlag(Abi == "ilp32f", "mabi=ilp32f", Flags);
87*13fbcb42Sjoerg 
88*13fbcb42Sjoerg     Result.Multilibs = MultilibSet().Either(I, Im, Iac, Imac, Imafc);
89*13fbcb42Sjoerg     return Result.Multilibs.select(Flags, Result.SelectedMultilib);
90*13fbcb42Sjoerg   }
91*13fbcb42Sjoerg   return false;
92*13fbcb42Sjoerg }
93*13fbcb42Sjoerg 
BareMetal(const Driver & D,const llvm::Triple & Triple,const ArgList & Args)9406f32e7eSjoerg BareMetal::BareMetal(const Driver &D, const llvm::Triple &Triple,
9506f32e7eSjoerg                            const ArgList &Args)
9606f32e7eSjoerg     : ToolChain(D, Triple, Args) {
9706f32e7eSjoerg   getProgramPaths().push_back(getDriver().getInstalledDir());
9806f32e7eSjoerg   if (getDriver().getInstalledDir() != getDriver().Dir)
9906f32e7eSjoerg     getProgramPaths().push_back(getDriver().Dir);
10006f32e7eSjoerg 
101*13fbcb42Sjoerg   findMultilibs(D, Triple, Args);
102*13fbcb42Sjoerg   SmallString<128> SysRoot(computeSysRoot());
103*13fbcb42Sjoerg   if (!SysRoot.empty()) {
104*13fbcb42Sjoerg     llvm::sys::path::append(SysRoot, "lib");
105*13fbcb42Sjoerg     getFilePaths().push_back(std::string(SysRoot));
106*13fbcb42Sjoerg   }
107*13fbcb42Sjoerg }
10806f32e7eSjoerg 
10906f32e7eSjoerg /// Is the triple {arm,thumb}-none-none-{eabi,eabihf} ?
isARMBareMetal(const llvm::Triple & Triple)11006f32e7eSjoerg static bool isARMBareMetal(const llvm::Triple &Triple) {
11106f32e7eSjoerg   if (Triple.getArch() != llvm::Triple::arm &&
11206f32e7eSjoerg       Triple.getArch() != llvm::Triple::thumb)
11306f32e7eSjoerg     return false;
11406f32e7eSjoerg 
11506f32e7eSjoerg   if (Triple.getVendor() != llvm::Triple::UnknownVendor)
11606f32e7eSjoerg     return false;
11706f32e7eSjoerg 
11806f32e7eSjoerg   if (Triple.getOS() != llvm::Triple::UnknownOS)
11906f32e7eSjoerg     return false;
12006f32e7eSjoerg 
12106f32e7eSjoerg   if (Triple.getEnvironment() != llvm::Triple::EABI &&
12206f32e7eSjoerg       Triple.getEnvironment() != llvm::Triple::EABIHF)
12306f32e7eSjoerg     return false;
12406f32e7eSjoerg 
12506f32e7eSjoerg   return true;
12606f32e7eSjoerg }
12706f32e7eSjoerg 
isRISCVBareMetal(const llvm::Triple & Triple)128*13fbcb42Sjoerg static bool isRISCVBareMetal(const llvm::Triple &Triple) {
129*13fbcb42Sjoerg   if (Triple.getArch() != llvm::Triple::riscv32 &&
130*13fbcb42Sjoerg       Triple.getArch() != llvm::Triple::riscv64)
131*13fbcb42Sjoerg     return false;
132*13fbcb42Sjoerg 
133*13fbcb42Sjoerg   if (Triple.getVendor() != llvm::Triple::UnknownVendor)
134*13fbcb42Sjoerg     return false;
135*13fbcb42Sjoerg 
136*13fbcb42Sjoerg   if (Triple.getOS() != llvm::Triple::UnknownOS)
137*13fbcb42Sjoerg     return false;
138*13fbcb42Sjoerg 
139*13fbcb42Sjoerg   return Triple.getEnvironmentName() == "elf";
140*13fbcb42Sjoerg }
141*13fbcb42Sjoerg 
findMultilibs(const Driver & D,const llvm::Triple & Triple,const ArgList & Args)142*13fbcb42Sjoerg void BareMetal::findMultilibs(const Driver &D, const llvm::Triple &Triple,
143*13fbcb42Sjoerg                               const ArgList &Args) {
144*13fbcb42Sjoerg   DetectedMultilibs Result;
145*13fbcb42Sjoerg   if (isRISCVBareMetal(Triple)) {
146*13fbcb42Sjoerg     if (findRISCVMultilibs(D, Triple, Args, Result)) {
147*13fbcb42Sjoerg       SelectedMultilib = Result.SelectedMultilib;
148*13fbcb42Sjoerg       Multilibs = Result.Multilibs;
149*13fbcb42Sjoerg     }
150*13fbcb42Sjoerg   }
151*13fbcb42Sjoerg }
152*13fbcb42Sjoerg 
handlesTarget(const llvm::Triple & Triple)15306f32e7eSjoerg bool BareMetal::handlesTarget(const llvm::Triple &Triple) {
154*13fbcb42Sjoerg   return isARMBareMetal(Triple) || isRISCVBareMetal(Triple);
15506f32e7eSjoerg }
15606f32e7eSjoerg 
buildLinker() const15706f32e7eSjoerg Tool *BareMetal::buildLinker() const {
15806f32e7eSjoerg   return new tools::baremetal::Linker(*this);
15906f32e7eSjoerg }
16006f32e7eSjoerg 
getCompilerRTPath() const161*13fbcb42Sjoerg std::string BareMetal::getCompilerRTPath() const { return getRuntimesDir(); }
162*13fbcb42Sjoerg 
buildCompilerRTBasename(const llvm::opt::ArgList &,StringRef,FileType,bool) const163*13fbcb42Sjoerg std::string BareMetal::buildCompilerRTBasename(const llvm::opt::ArgList &,
164*13fbcb42Sjoerg                                                StringRef, FileType,
165*13fbcb42Sjoerg                                                bool) const {
166*13fbcb42Sjoerg   return ("libclang_rt.builtins-" + getTriple().getArchName() + ".a").str();
167*13fbcb42Sjoerg }
168*13fbcb42Sjoerg 
getRuntimesDir() const16906f32e7eSjoerg std::string BareMetal::getRuntimesDir() const {
17006f32e7eSjoerg   SmallString<128> Dir(getDriver().ResourceDir);
17106f32e7eSjoerg   llvm::sys::path::append(Dir, "lib", "baremetal");
172*13fbcb42Sjoerg   Dir += SelectedMultilib.gccSuffix();
173*13fbcb42Sjoerg   return std::string(Dir.str());
174*13fbcb42Sjoerg }
175*13fbcb42Sjoerg 
computeSysRoot() const176*13fbcb42Sjoerg std::string BareMetal::computeSysRoot() const {
177*13fbcb42Sjoerg   if (!getDriver().SysRoot.empty())
178*13fbcb42Sjoerg     return getDriver().SysRoot + SelectedMultilib.osSuffix();
179*13fbcb42Sjoerg 
180*13fbcb42Sjoerg   SmallString<128> SysRootDir;
181*13fbcb42Sjoerg   llvm::sys::path::append(SysRootDir, getDriver().Dir, "../lib/clang-runtimes",
182*13fbcb42Sjoerg                           getDriver().getTargetTriple());
183*13fbcb42Sjoerg 
184*13fbcb42Sjoerg   SysRootDir += SelectedMultilib.osSuffix();
185*13fbcb42Sjoerg   return std::string(SysRootDir);
18606f32e7eSjoerg }
18706f32e7eSjoerg 
AddClangSystemIncludeArgs(const ArgList & DriverArgs,ArgStringList & CC1Args) const18806f32e7eSjoerg void BareMetal::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
18906f32e7eSjoerg                                           ArgStringList &CC1Args) const {
19006f32e7eSjoerg   if (DriverArgs.hasArg(options::OPT_nostdinc))
19106f32e7eSjoerg     return;
19206f32e7eSjoerg 
19306f32e7eSjoerg   if (!DriverArgs.hasArg(options::OPT_nobuiltininc)) {
19406f32e7eSjoerg     SmallString<128> Dir(getDriver().ResourceDir);
19506f32e7eSjoerg     llvm::sys::path::append(Dir, "include");
19606f32e7eSjoerg     addSystemInclude(DriverArgs, CC1Args, Dir.str());
19706f32e7eSjoerg   }
19806f32e7eSjoerg 
19906f32e7eSjoerg   if (!DriverArgs.hasArg(options::OPT_nostdlibinc)) {
200*13fbcb42Sjoerg     SmallString<128> Dir(computeSysRoot());
201*13fbcb42Sjoerg     if (!Dir.empty()) {
20206f32e7eSjoerg       llvm::sys::path::append(Dir, "include");
20306f32e7eSjoerg       addSystemInclude(DriverArgs, CC1Args, Dir.str());
20406f32e7eSjoerg     }
20506f32e7eSjoerg   }
206*13fbcb42Sjoerg }
20706f32e7eSjoerg 
addClangTargetOptions(const ArgList & DriverArgs,ArgStringList & CC1Args,Action::OffloadKind) const20806f32e7eSjoerg void BareMetal::addClangTargetOptions(const ArgList &DriverArgs,
20906f32e7eSjoerg                                       ArgStringList &CC1Args,
21006f32e7eSjoerg                                       Action::OffloadKind) const {
21106f32e7eSjoerg   CC1Args.push_back("-nostdsysteminc");
21206f32e7eSjoerg }
21306f32e7eSjoerg 
AddClangCXXStdlibIncludeArgs(const ArgList & DriverArgs,ArgStringList & CC1Args) const21406f32e7eSjoerg void BareMetal::AddClangCXXStdlibIncludeArgs(
21506f32e7eSjoerg     const ArgList &DriverArgs, ArgStringList &CC1Args) const {
21606f32e7eSjoerg   if (DriverArgs.hasArg(options::OPT_nostdinc) ||
21706f32e7eSjoerg       DriverArgs.hasArg(options::OPT_nostdlibinc) ||
21806f32e7eSjoerg       DriverArgs.hasArg(options::OPT_nostdincxx))
21906f32e7eSjoerg     return;
22006f32e7eSjoerg 
221*13fbcb42Sjoerg   std::string SysRoot(computeSysRoot());
22206f32e7eSjoerg   if (SysRoot.empty())
22306f32e7eSjoerg     return;
22406f32e7eSjoerg 
22506f32e7eSjoerg   switch (GetCXXStdlibType(DriverArgs)) {
22606f32e7eSjoerg   case ToolChain::CST_Libcxx: {
22706f32e7eSjoerg     SmallString<128> Dir(SysRoot);
22806f32e7eSjoerg     llvm::sys::path::append(Dir, "include", "c++", "v1");
22906f32e7eSjoerg     addSystemInclude(DriverArgs, CC1Args, Dir.str());
23006f32e7eSjoerg     break;
23106f32e7eSjoerg   }
23206f32e7eSjoerg   case ToolChain::CST_Libstdcxx: {
23306f32e7eSjoerg     SmallString<128> Dir(SysRoot);
23406f32e7eSjoerg     llvm::sys::path::append(Dir, "include", "c++");
23506f32e7eSjoerg     std::error_code EC;
23606f32e7eSjoerg     Generic_GCC::GCCVersion Version = {"", -1, -1, -1, "", "", ""};
23706f32e7eSjoerg     // Walk the subdirs, and find the one with the newest gcc version:
23806f32e7eSjoerg     for (llvm::vfs::directory_iterator
23906f32e7eSjoerg              LI = getDriver().getVFS().dir_begin(Dir.str(), EC),
24006f32e7eSjoerg              LE;
24106f32e7eSjoerg          !EC && LI != LE; LI = LI.increment(EC)) {
24206f32e7eSjoerg       StringRef VersionText = llvm::sys::path::filename(LI->path());
24306f32e7eSjoerg       auto CandidateVersion = Generic_GCC::GCCVersion::Parse(VersionText);
24406f32e7eSjoerg       if (CandidateVersion.Major == -1)
24506f32e7eSjoerg         continue;
24606f32e7eSjoerg       if (CandidateVersion <= Version)
24706f32e7eSjoerg         continue;
24806f32e7eSjoerg       Version = CandidateVersion;
24906f32e7eSjoerg     }
25006f32e7eSjoerg     if (Version.Major == -1)
25106f32e7eSjoerg       return;
25206f32e7eSjoerg     llvm::sys::path::append(Dir, Version.Text);
25306f32e7eSjoerg     addSystemInclude(DriverArgs, CC1Args, Dir.str());
25406f32e7eSjoerg     break;
25506f32e7eSjoerg   }
25606f32e7eSjoerg   }
25706f32e7eSjoerg }
25806f32e7eSjoerg 
AddCXXStdlibLibArgs(const ArgList & Args,ArgStringList & CmdArgs) const25906f32e7eSjoerg void BareMetal::AddCXXStdlibLibArgs(const ArgList &Args,
26006f32e7eSjoerg                                     ArgStringList &CmdArgs) const {
26106f32e7eSjoerg   switch (GetCXXStdlibType(Args)) {
26206f32e7eSjoerg   case ToolChain::CST_Libcxx:
26306f32e7eSjoerg     CmdArgs.push_back("-lc++");
26406f32e7eSjoerg     CmdArgs.push_back("-lc++abi");
26506f32e7eSjoerg     break;
26606f32e7eSjoerg   case ToolChain::CST_Libstdcxx:
26706f32e7eSjoerg     CmdArgs.push_back("-lstdc++");
26806f32e7eSjoerg     CmdArgs.push_back("-lsupc++");
26906f32e7eSjoerg     break;
27006f32e7eSjoerg   }
27106f32e7eSjoerg   CmdArgs.push_back("-lunwind");
27206f32e7eSjoerg }
27306f32e7eSjoerg 
AddLinkRuntimeLib(const ArgList & Args,ArgStringList & CmdArgs) const27406f32e7eSjoerg void BareMetal::AddLinkRuntimeLib(const ArgList &Args,
27506f32e7eSjoerg                                   ArgStringList &CmdArgs) const {
276*13fbcb42Sjoerg   ToolChain::RuntimeLibType RLT = GetRuntimeLibType(Args);
277*13fbcb42Sjoerg   switch (RLT) {
278*13fbcb42Sjoerg   case ToolChain::RLT_CompilerRT:
279*13fbcb42Sjoerg     CmdArgs.push_back(
280*13fbcb42Sjoerg         Args.MakeArgString("-lclang_rt.builtins-" + getTriple().getArchName()));
281*13fbcb42Sjoerg     return;
282*13fbcb42Sjoerg   case ToolChain::RLT_Libgcc:
283*13fbcb42Sjoerg     CmdArgs.push_back("-lgcc");
284*13fbcb42Sjoerg     return;
285*13fbcb42Sjoerg   }
286*13fbcb42Sjoerg   llvm_unreachable("Unhandled RuntimeLibType.");
28706f32e7eSjoerg }
28806f32e7eSjoerg 
ConstructJob(Compilation & C,const JobAction & JA,const InputInfo & Output,const InputInfoList & Inputs,const ArgList & Args,const char * LinkingOutput) const28906f32e7eSjoerg void baremetal::Linker::ConstructJob(Compilation &C, const JobAction &JA,
29006f32e7eSjoerg                                      const InputInfo &Output,
29106f32e7eSjoerg                                      const InputInfoList &Inputs,
29206f32e7eSjoerg                                      const ArgList &Args,
29306f32e7eSjoerg                                      const char *LinkingOutput) const {
29406f32e7eSjoerg   ArgStringList CmdArgs;
29506f32e7eSjoerg 
29606f32e7eSjoerg   auto &TC = static_cast<const toolchains::BareMetal&>(getToolChain());
29706f32e7eSjoerg 
29806f32e7eSjoerg   AddLinkerInputs(TC, Inputs, Args, CmdArgs, JA);
29906f32e7eSjoerg 
30006f32e7eSjoerg   CmdArgs.push_back("-Bstatic");
30106f32e7eSjoerg 
30206f32e7eSjoerg   Args.AddAllArgs(CmdArgs, {options::OPT_L, options::OPT_T_Group,
30306f32e7eSjoerg                             options::OPT_e, options::OPT_s, options::OPT_t,
30406f32e7eSjoerg                             options::OPT_Z_Flag, options::OPT_r});
30506f32e7eSjoerg 
306*13fbcb42Sjoerg   TC.AddFilePathLibArgs(Args, CmdArgs);
307*13fbcb42Sjoerg 
308*13fbcb42Sjoerg   CmdArgs.push_back(Args.MakeArgString("-L" + TC.getRuntimesDir()));
309*13fbcb42Sjoerg 
31006f32e7eSjoerg   if (TC.ShouldLinkCXXStdlib(Args))
31106f32e7eSjoerg     TC.AddCXXStdlibLibArgs(Args, CmdArgs);
31206f32e7eSjoerg   if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
31306f32e7eSjoerg     CmdArgs.push_back("-lc");
31406f32e7eSjoerg     CmdArgs.push_back("-lm");
31506f32e7eSjoerg 
31606f32e7eSjoerg     TC.AddLinkRuntimeLib(Args, CmdArgs);
31706f32e7eSjoerg   }
31806f32e7eSjoerg 
31906f32e7eSjoerg   CmdArgs.push_back("-o");
32006f32e7eSjoerg   CmdArgs.push_back(Output.getFilename());
32106f32e7eSjoerg 
322*13fbcb42Sjoerg   C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(),
32306f32e7eSjoerg                                          Args.MakeArgString(TC.GetLinkerPath()),
324*13fbcb42Sjoerg                                          CmdArgs, Inputs, Output));
32506f32e7eSjoerg }
326