17330f729Sjoerg //===-- clang-offload-wrapper/ClangOffloadWrapper.cpp -----------*- C++ -*-===//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg ///
97330f729Sjoerg /// \file
107330f729Sjoerg /// Implementation of the offload wrapper tool. It takes offload target binaries
117330f729Sjoerg /// as input and creates wrapper bitcode file containing target binaries
127330f729Sjoerg /// packaged as data. Wrapper bitcode also includes initialization code which
137330f729Sjoerg /// registers target binaries in offloading runtime at program startup.
147330f729Sjoerg ///
157330f729Sjoerg //===----------------------------------------------------------------------===//
167330f729Sjoerg
177330f729Sjoerg #include "clang/Basic/Version.h"
187330f729Sjoerg #include "llvm/ADT/ArrayRef.h"
197330f729Sjoerg #include "llvm/ADT/Triple.h"
207330f729Sjoerg #include "llvm/Bitcode/BitcodeWriter.h"
217330f729Sjoerg #include "llvm/IR/Constants.h"
227330f729Sjoerg #include "llvm/IR/GlobalVariable.h"
237330f729Sjoerg #include "llvm/IR/IRBuilder.h"
247330f729Sjoerg #include "llvm/IR/LLVMContext.h"
257330f729Sjoerg #include "llvm/IR/Module.h"
267330f729Sjoerg #include "llvm/Support/CommandLine.h"
277330f729Sjoerg #include "llvm/Support/Errc.h"
287330f729Sjoerg #include "llvm/Support/Error.h"
297330f729Sjoerg #include "llvm/Support/ErrorOr.h"
30*e038c9c4Sjoerg #include "llvm/Support/FileSystem.h"
317330f729Sjoerg #include "llvm/Support/MemoryBuffer.h"
327330f729Sjoerg #include "llvm/Support/Signals.h"
337330f729Sjoerg #include "llvm/Support/ToolOutputFile.h"
347330f729Sjoerg #include "llvm/Support/WithColor.h"
357330f729Sjoerg #include "llvm/Support/raw_ostream.h"
367330f729Sjoerg #include "llvm/Transforms/Utils/ModuleUtils.h"
377330f729Sjoerg #include <cassert>
387330f729Sjoerg #include <cstdint>
397330f729Sjoerg
407330f729Sjoerg using namespace llvm;
417330f729Sjoerg
427330f729Sjoerg static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
437330f729Sjoerg
447330f729Sjoerg // Mark all our options with this category, everything else (except for -version
457330f729Sjoerg // and -help) will be hidden.
467330f729Sjoerg static cl::OptionCategory
477330f729Sjoerg ClangOffloadWrapperCategory("clang-offload-wrapper options");
487330f729Sjoerg
497330f729Sjoerg static cl::opt<std::string> Output("o", cl::Required,
507330f729Sjoerg cl::desc("Output filename"),
517330f729Sjoerg cl::value_desc("filename"),
527330f729Sjoerg cl::cat(ClangOffloadWrapperCategory));
537330f729Sjoerg
547330f729Sjoerg static cl::list<std::string> Inputs(cl::Positional, cl::OneOrMore,
557330f729Sjoerg cl::desc("<input files>"),
567330f729Sjoerg cl::cat(ClangOffloadWrapperCategory));
577330f729Sjoerg
587330f729Sjoerg static cl::opt<std::string>
597330f729Sjoerg Target("target", cl::Required,
607330f729Sjoerg cl::desc("Target triple for the output module"),
617330f729Sjoerg cl::value_desc("triple"), cl::cat(ClangOffloadWrapperCategory));
627330f729Sjoerg
637330f729Sjoerg namespace {
647330f729Sjoerg
657330f729Sjoerg class BinaryWrapper {
667330f729Sjoerg LLVMContext C;
677330f729Sjoerg Module M;
687330f729Sjoerg
697330f729Sjoerg StructType *EntryTy = nullptr;
707330f729Sjoerg StructType *ImageTy = nullptr;
717330f729Sjoerg StructType *DescTy = nullptr;
727330f729Sjoerg
737330f729Sjoerg private:
getSizeTTy()747330f729Sjoerg IntegerType *getSizeTTy() {
757330f729Sjoerg switch (M.getDataLayout().getPointerTypeSize(Type::getInt8PtrTy(C))) {
767330f729Sjoerg case 4u:
777330f729Sjoerg return Type::getInt32Ty(C);
787330f729Sjoerg case 8u:
797330f729Sjoerg return Type::getInt64Ty(C);
807330f729Sjoerg }
817330f729Sjoerg llvm_unreachable("unsupported pointer type size");
827330f729Sjoerg }
837330f729Sjoerg
847330f729Sjoerg // struct __tgt_offload_entry {
857330f729Sjoerg // void *addr;
867330f729Sjoerg // char *name;
877330f729Sjoerg // size_t size;
887330f729Sjoerg // int32_t flags;
897330f729Sjoerg // int32_t reserved;
907330f729Sjoerg // };
getEntryTy()917330f729Sjoerg StructType *getEntryTy() {
927330f729Sjoerg if (!EntryTy)
937330f729Sjoerg EntryTy = StructType::create("__tgt_offload_entry", Type::getInt8PtrTy(C),
947330f729Sjoerg Type::getInt8PtrTy(C), getSizeTTy(),
957330f729Sjoerg Type::getInt32Ty(C), Type::getInt32Ty(C));
967330f729Sjoerg return EntryTy;
977330f729Sjoerg }
987330f729Sjoerg
getEntryPtrTy()997330f729Sjoerg PointerType *getEntryPtrTy() { return PointerType::getUnqual(getEntryTy()); }
1007330f729Sjoerg
1017330f729Sjoerg // struct __tgt_device_image {
1027330f729Sjoerg // void *ImageStart;
1037330f729Sjoerg // void *ImageEnd;
1047330f729Sjoerg // __tgt_offload_entry *EntriesBegin;
1057330f729Sjoerg // __tgt_offload_entry *EntriesEnd;
1067330f729Sjoerg // };
getDeviceImageTy()1077330f729Sjoerg StructType *getDeviceImageTy() {
1087330f729Sjoerg if (!ImageTy)
1097330f729Sjoerg ImageTy = StructType::create("__tgt_device_image", Type::getInt8PtrTy(C),
1107330f729Sjoerg Type::getInt8PtrTy(C), getEntryPtrTy(),
1117330f729Sjoerg getEntryPtrTy());
1127330f729Sjoerg return ImageTy;
1137330f729Sjoerg }
1147330f729Sjoerg
getDeviceImagePtrTy()1157330f729Sjoerg PointerType *getDeviceImagePtrTy() {
1167330f729Sjoerg return PointerType::getUnqual(getDeviceImageTy());
1177330f729Sjoerg }
1187330f729Sjoerg
1197330f729Sjoerg // struct __tgt_bin_desc {
1207330f729Sjoerg // int32_t NumDeviceImages;
1217330f729Sjoerg // __tgt_device_image *DeviceImages;
1227330f729Sjoerg // __tgt_offload_entry *HostEntriesBegin;
1237330f729Sjoerg // __tgt_offload_entry *HostEntriesEnd;
1247330f729Sjoerg // };
getBinDescTy()1257330f729Sjoerg StructType *getBinDescTy() {
1267330f729Sjoerg if (!DescTy)
1277330f729Sjoerg DescTy = StructType::create("__tgt_bin_desc", Type::getInt32Ty(C),
1287330f729Sjoerg getDeviceImagePtrTy(), getEntryPtrTy(),
1297330f729Sjoerg getEntryPtrTy());
1307330f729Sjoerg return DescTy;
1317330f729Sjoerg }
1327330f729Sjoerg
getBinDescPtrTy()1337330f729Sjoerg PointerType *getBinDescPtrTy() {
1347330f729Sjoerg return PointerType::getUnqual(getBinDescTy());
1357330f729Sjoerg }
1367330f729Sjoerg
1377330f729Sjoerg /// Creates binary descriptor for the given device images. Binary descriptor
1387330f729Sjoerg /// is an object that is passed to the offloading runtime at program startup
1397330f729Sjoerg /// and it describes all device images available in the executable or shared
1407330f729Sjoerg /// library. It is defined as follows
1417330f729Sjoerg ///
1427330f729Sjoerg /// __attribute__((visibility("hidden")))
1437330f729Sjoerg /// extern __tgt_offload_entry *__start_omp_offloading_entries;
1447330f729Sjoerg /// __attribute__((visibility("hidden")))
1457330f729Sjoerg /// extern __tgt_offload_entry *__stop_omp_offloading_entries;
1467330f729Sjoerg ///
1477330f729Sjoerg /// static const char Image0[] = { <Bufs.front() contents> };
1487330f729Sjoerg /// ...
1497330f729Sjoerg /// static const char ImageN[] = { <Bufs.back() contents> };
1507330f729Sjoerg ///
1517330f729Sjoerg /// static const __tgt_device_image Images[] = {
1527330f729Sjoerg /// {
1537330f729Sjoerg /// Image0, /*ImageStart*/
1547330f729Sjoerg /// Image0 + sizeof(Image0), /*ImageEnd*/
1557330f729Sjoerg /// __start_omp_offloading_entries, /*EntriesBegin*/
1567330f729Sjoerg /// __stop_omp_offloading_entries /*EntriesEnd*/
1577330f729Sjoerg /// },
1587330f729Sjoerg /// ...
1597330f729Sjoerg /// {
1607330f729Sjoerg /// ImageN, /*ImageStart*/
1617330f729Sjoerg /// ImageN + sizeof(ImageN), /*ImageEnd*/
1627330f729Sjoerg /// __start_omp_offloading_entries, /*EntriesBegin*/
1637330f729Sjoerg /// __stop_omp_offloading_entries /*EntriesEnd*/
1647330f729Sjoerg /// }
1657330f729Sjoerg /// };
1667330f729Sjoerg ///
1677330f729Sjoerg /// static const __tgt_bin_desc BinDesc = {
1687330f729Sjoerg /// sizeof(Images) / sizeof(Images[0]), /*NumDeviceImages*/
1697330f729Sjoerg /// Images, /*DeviceImages*/
1707330f729Sjoerg /// __start_omp_offloading_entries, /*HostEntriesBegin*/
1717330f729Sjoerg /// __stop_omp_offloading_entries /*HostEntriesEnd*/
1727330f729Sjoerg /// };
1737330f729Sjoerg ///
1747330f729Sjoerg /// Global variable that represents BinDesc is returned.
createBinDesc(ArrayRef<ArrayRef<char>> Bufs)1757330f729Sjoerg GlobalVariable *createBinDesc(ArrayRef<ArrayRef<char>> Bufs) {
1767330f729Sjoerg // Create external begin/end symbols for the offload entries table.
1777330f729Sjoerg auto *EntriesB = new GlobalVariable(
1787330f729Sjoerg M, getEntryTy(), /*isConstant*/ true, GlobalValue::ExternalLinkage,
1797330f729Sjoerg /*Initializer*/ nullptr, "__start_omp_offloading_entries");
1807330f729Sjoerg EntriesB->setVisibility(GlobalValue::HiddenVisibility);
1817330f729Sjoerg auto *EntriesE = new GlobalVariable(
1827330f729Sjoerg M, getEntryTy(), /*isConstant*/ true, GlobalValue::ExternalLinkage,
1837330f729Sjoerg /*Initializer*/ nullptr, "__stop_omp_offloading_entries");
1847330f729Sjoerg EntriesE->setVisibility(GlobalValue::HiddenVisibility);
1857330f729Sjoerg
1867330f729Sjoerg // We assume that external begin/end symbols that we have created above will
1877330f729Sjoerg // be defined by the linker. But linker will do that only if linker inputs
1887330f729Sjoerg // have section with "omp_offloading_entries" name which is not guaranteed.
1897330f729Sjoerg // So, we just create dummy zero sized object in the offload entries section
1907330f729Sjoerg // to force linker to define those symbols.
1917330f729Sjoerg auto *DummyInit =
1927330f729Sjoerg ConstantAggregateZero::get(ArrayType::get(getEntryTy(), 0u));
1937330f729Sjoerg auto *DummyEntry = new GlobalVariable(
1947330f729Sjoerg M, DummyInit->getType(), true, GlobalVariable::ExternalLinkage,
1957330f729Sjoerg DummyInit, "__dummy.omp_offloading.entry");
1967330f729Sjoerg DummyEntry->setSection("omp_offloading_entries");
1977330f729Sjoerg DummyEntry->setVisibility(GlobalValue::HiddenVisibility);
1987330f729Sjoerg
1997330f729Sjoerg auto *Zero = ConstantInt::get(getSizeTTy(), 0u);
2007330f729Sjoerg Constant *ZeroZero[] = {Zero, Zero};
2017330f729Sjoerg
2027330f729Sjoerg // Create initializer for the images array.
2037330f729Sjoerg SmallVector<Constant *, 4u> ImagesInits;
2047330f729Sjoerg ImagesInits.reserve(Bufs.size());
2057330f729Sjoerg for (ArrayRef<char> Buf : Bufs) {
2067330f729Sjoerg auto *Data = ConstantDataArray::get(C, Buf);
2077330f729Sjoerg auto *Image = new GlobalVariable(M, Data->getType(), /*isConstant*/ true,
2087330f729Sjoerg GlobalVariable::InternalLinkage, Data,
2097330f729Sjoerg ".omp_offloading.device_image");
2107330f729Sjoerg Image->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
2117330f729Sjoerg
2127330f729Sjoerg auto *Size = ConstantInt::get(getSizeTTy(), Buf.size());
2137330f729Sjoerg Constant *ZeroSize[] = {Zero, Size};
2147330f729Sjoerg
2157330f729Sjoerg auto *ImageB = ConstantExpr::getGetElementPtr(Image->getValueType(),
2167330f729Sjoerg Image, ZeroZero);
2177330f729Sjoerg auto *ImageE = ConstantExpr::getGetElementPtr(Image->getValueType(),
2187330f729Sjoerg Image, ZeroSize);
2197330f729Sjoerg
2207330f729Sjoerg ImagesInits.push_back(ConstantStruct::get(getDeviceImageTy(), ImageB,
2217330f729Sjoerg ImageE, EntriesB, EntriesE));
2227330f729Sjoerg }
2237330f729Sjoerg
2247330f729Sjoerg // Then create images array.
2257330f729Sjoerg auto *ImagesData = ConstantArray::get(
2267330f729Sjoerg ArrayType::get(getDeviceImageTy(), ImagesInits.size()), ImagesInits);
2277330f729Sjoerg
2287330f729Sjoerg auto *Images =
2297330f729Sjoerg new GlobalVariable(M, ImagesData->getType(), /*isConstant*/ true,
2307330f729Sjoerg GlobalValue::InternalLinkage, ImagesData,
2317330f729Sjoerg ".omp_offloading.device_images");
2327330f729Sjoerg Images->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
2337330f729Sjoerg
2347330f729Sjoerg auto *ImagesB = ConstantExpr::getGetElementPtr(Images->getValueType(),
2357330f729Sjoerg Images, ZeroZero);
2367330f729Sjoerg
2377330f729Sjoerg // And finally create the binary descriptor object.
2387330f729Sjoerg auto *DescInit = ConstantStruct::get(
2397330f729Sjoerg getBinDescTy(),
2407330f729Sjoerg ConstantInt::get(Type::getInt32Ty(C), ImagesInits.size()), ImagesB,
2417330f729Sjoerg EntriesB, EntriesE);
2427330f729Sjoerg
2437330f729Sjoerg return new GlobalVariable(M, DescInit->getType(), /*isConstant*/ true,
2447330f729Sjoerg GlobalValue::InternalLinkage, DescInit,
2457330f729Sjoerg ".omp_offloading.descriptor");
2467330f729Sjoerg }
2477330f729Sjoerg
createRegisterFunction(GlobalVariable * BinDesc)2487330f729Sjoerg void createRegisterFunction(GlobalVariable *BinDesc) {
2497330f729Sjoerg auto *FuncTy = FunctionType::get(Type::getVoidTy(C), /*isVarArg*/ false);
2507330f729Sjoerg auto *Func = Function::Create(FuncTy, GlobalValue::InternalLinkage,
2517330f729Sjoerg ".omp_offloading.descriptor_reg", &M);
2527330f729Sjoerg Func->setSection(".text.startup");
2537330f729Sjoerg
2547330f729Sjoerg // Get __tgt_register_lib function declaration.
2557330f729Sjoerg auto *RegFuncTy = FunctionType::get(Type::getVoidTy(C), getBinDescPtrTy(),
2567330f729Sjoerg /*isVarArg*/ false);
2577330f729Sjoerg FunctionCallee RegFuncC =
2587330f729Sjoerg M.getOrInsertFunction("__tgt_register_lib", RegFuncTy);
2597330f729Sjoerg
2607330f729Sjoerg // Construct function body
2617330f729Sjoerg IRBuilder<> Builder(BasicBlock::Create(C, "entry", Func));
2627330f729Sjoerg Builder.CreateCall(RegFuncC, BinDesc);
2637330f729Sjoerg Builder.CreateRetVoid();
2647330f729Sjoerg
2657330f729Sjoerg // Add this function to constructors.
266*e038c9c4Sjoerg // Set priority to 1 so that __tgt_register_lib is executed AFTER
267*e038c9c4Sjoerg // __tgt_register_requires (we want to know what requirements have been
268*e038c9c4Sjoerg // asked for before we load a libomptarget plugin so that by the time the
269*e038c9c4Sjoerg // plugin is loaded it can report how many devices there are which can
270*e038c9c4Sjoerg // satisfy these requirements).
271*e038c9c4Sjoerg appendToGlobalCtors(M, Func, /*Priority*/ 1);
2727330f729Sjoerg }
2737330f729Sjoerg
createUnregisterFunction(GlobalVariable * BinDesc)2747330f729Sjoerg void createUnregisterFunction(GlobalVariable *BinDesc) {
2757330f729Sjoerg auto *FuncTy = FunctionType::get(Type::getVoidTy(C), /*isVarArg*/ false);
2767330f729Sjoerg auto *Func = Function::Create(FuncTy, GlobalValue::InternalLinkage,
2777330f729Sjoerg ".omp_offloading.descriptor_unreg", &M);
2787330f729Sjoerg Func->setSection(".text.startup");
2797330f729Sjoerg
2807330f729Sjoerg // Get __tgt_unregister_lib function declaration.
2817330f729Sjoerg auto *UnRegFuncTy = FunctionType::get(Type::getVoidTy(C), getBinDescPtrTy(),
2827330f729Sjoerg /*isVarArg*/ false);
2837330f729Sjoerg FunctionCallee UnRegFuncC =
2847330f729Sjoerg M.getOrInsertFunction("__tgt_unregister_lib", UnRegFuncTy);
2857330f729Sjoerg
2867330f729Sjoerg // Construct function body
2877330f729Sjoerg IRBuilder<> Builder(BasicBlock::Create(C, "entry", Func));
2887330f729Sjoerg Builder.CreateCall(UnRegFuncC, BinDesc);
2897330f729Sjoerg Builder.CreateRetVoid();
2907330f729Sjoerg
2917330f729Sjoerg // Add this function to global destructors.
292*e038c9c4Sjoerg // Match priority of __tgt_register_lib
293*e038c9c4Sjoerg appendToGlobalDtors(M, Func, /*Priority*/ 1);
2947330f729Sjoerg }
2957330f729Sjoerg
2967330f729Sjoerg public:
BinaryWrapper(StringRef Target)2977330f729Sjoerg BinaryWrapper(StringRef Target) : M("offload.wrapper.object", C) {
2987330f729Sjoerg M.setTargetTriple(Target);
2997330f729Sjoerg }
3007330f729Sjoerg
wrapBinaries(ArrayRef<ArrayRef<char>> Binaries)3017330f729Sjoerg const Module &wrapBinaries(ArrayRef<ArrayRef<char>> Binaries) {
3027330f729Sjoerg GlobalVariable *Desc = createBinDesc(Binaries);
3037330f729Sjoerg assert(Desc && "no binary descriptor");
3047330f729Sjoerg createRegisterFunction(Desc);
3057330f729Sjoerg createUnregisterFunction(Desc);
3067330f729Sjoerg return M;
3077330f729Sjoerg }
3087330f729Sjoerg };
3097330f729Sjoerg
3107330f729Sjoerg } // anonymous namespace
3117330f729Sjoerg
main(int argc,const char ** argv)3127330f729Sjoerg int main(int argc, const char **argv) {
3137330f729Sjoerg sys::PrintStackTraceOnErrorSignal(argv[0]);
3147330f729Sjoerg
3157330f729Sjoerg cl::HideUnrelatedOptions(ClangOffloadWrapperCategory);
3167330f729Sjoerg cl::SetVersionPrinter([](raw_ostream &OS) {
3177330f729Sjoerg OS << clang::getClangToolFullVersion("clang-offload-wrapper") << '\n';
3187330f729Sjoerg });
3197330f729Sjoerg cl::ParseCommandLineOptions(
3207330f729Sjoerg argc, argv,
3217330f729Sjoerg "A tool to create a wrapper bitcode for offload target binaries. Takes "
3227330f729Sjoerg "offload\ntarget binaries as input and produces bitcode file containing "
3237330f729Sjoerg "target binaries packaged\nas data and initialization code which "
3247330f729Sjoerg "registers target binaries in offload runtime.\n");
3257330f729Sjoerg
3267330f729Sjoerg if (Help) {
3277330f729Sjoerg cl::PrintHelpMessage();
3287330f729Sjoerg return 0;
3297330f729Sjoerg }
3307330f729Sjoerg
3317330f729Sjoerg auto reportError = [argv](Error E) {
3327330f729Sjoerg logAllUnhandledErrors(std::move(E), WithColor::error(errs(), argv[0]));
3337330f729Sjoerg };
3347330f729Sjoerg
3357330f729Sjoerg if (Triple(Target).getArch() == Triple::UnknownArch) {
3367330f729Sjoerg reportError(createStringError(
3377330f729Sjoerg errc::invalid_argument, "'" + Target + "': unsupported target triple"));
3387330f729Sjoerg return 1;
3397330f729Sjoerg }
3407330f729Sjoerg
3417330f729Sjoerg // Read device binaries.
3427330f729Sjoerg SmallVector<std::unique_ptr<MemoryBuffer>, 4u> Buffers;
3437330f729Sjoerg SmallVector<ArrayRef<char>, 4u> Images;
3447330f729Sjoerg Buffers.reserve(Inputs.size());
3457330f729Sjoerg Images.reserve(Inputs.size());
3467330f729Sjoerg for (const std::string &File : Inputs) {
3477330f729Sjoerg ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
3487330f729Sjoerg MemoryBuffer::getFileOrSTDIN(File);
3497330f729Sjoerg if (!BufOrErr) {
3507330f729Sjoerg reportError(createFileError(File, BufOrErr.getError()));
3517330f729Sjoerg return 1;
3527330f729Sjoerg }
3537330f729Sjoerg const std::unique_ptr<MemoryBuffer> &Buf =
3547330f729Sjoerg Buffers.emplace_back(std::move(*BufOrErr));
3557330f729Sjoerg Images.emplace_back(Buf->getBufferStart(), Buf->getBufferSize());
3567330f729Sjoerg }
3577330f729Sjoerg
3587330f729Sjoerg // Create the output file to write the resulting bitcode to.
3597330f729Sjoerg std::error_code EC;
3607330f729Sjoerg ToolOutputFile Out(Output, EC, sys::fs::OF_None);
3617330f729Sjoerg if (EC) {
3627330f729Sjoerg reportError(createFileError(Output, EC));
3637330f729Sjoerg return 1;
3647330f729Sjoerg }
3657330f729Sjoerg
3667330f729Sjoerg // Create a wrapper for device binaries and write its bitcode to the file.
3677330f729Sjoerg WriteBitcodeToFile(BinaryWrapper(Target).wrapBinaries(
3687330f729Sjoerg makeArrayRef(Images.data(), Images.size())),
3697330f729Sjoerg Out.os());
3707330f729Sjoerg if (Out.os().has_error()) {
3717330f729Sjoerg reportError(createFileError(Output, Out.os().error()));
3727330f729Sjoerg return 1;
3737330f729Sjoerg }
3747330f729Sjoerg
3757330f729Sjoerg // Success.
3767330f729Sjoerg Out.keep();
3777330f729Sjoerg return 0;
3787330f729Sjoerg }
379