diff --git a/compiler+runtime/CMakeLists.txt b/compiler+runtime/CMakeLists.txt index aae32492..129d15f1 100644 --- a/compiler+runtime/CMakeLists.txt +++ b/compiler+runtime/CMakeLists.txt @@ -161,6 +161,7 @@ add_library( src/cpp/jank/runtime/obj/number.cpp src/cpp/jank/runtime/obj/native_function_wrapper.cpp src/cpp/jank/runtime/obj/jit_function.cpp + src/cpp/jank/runtime/obj/jit_closure.cpp src/cpp/jank/runtime/obj/multi_function.cpp src/cpp/jank/runtime/obj/symbol.cpp src/cpp/jank/runtime/obj/keyword.cpp diff --git a/compiler+runtime/include/cpp/jank/c_api.h b/compiler+runtime/include/cpp/jank/c_api.h index 5d13605a..b3fbf19c 100644 --- a/compiler+runtime/include/cpp/jank/c_api.h +++ b/compiler+runtime/include/cpp/jank/c_api.h @@ -47,16 +47,23 @@ extern "C" jank_native_bool is_variadic, jank_native_bool is_variadic_ambiguous); jank_object_ptr jank_function_create(jank_arity_flags arity_flags); - jank_object_ptr jank_function_create_closure(jank_arity_flags arity_flags, void *context); - jank_object_ptr jank_function_set_arity0(jank_object_ptr fn, jank_object_ptr (*f)()); - jank_object_ptr - jank_function_set_arity1(jank_object_ptr fn, jank_object_ptr (*f)(jank_object_ptr)); - jank_object_ptr jank_function_set_arity2(jank_object_ptr fn, - jank_object_ptr (*f)(jank_object_ptr, jank_object_ptr)); - jank_object_ptr + void jank_function_set_arity0(jank_object_ptr fn, jank_object_ptr (*f)()); + void jank_function_set_arity1(jank_object_ptr fn, jank_object_ptr (*f)(jank_object_ptr)); + void jank_function_set_arity2(jank_object_ptr fn, + jank_object_ptr (*f)(jank_object_ptr, jank_object_ptr)); + void jank_function_set_arity3(jank_object_ptr fn, jank_object_ptr (*f)(jank_object_ptr, jank_object_ptr, jank_object_ptr)); + jank_object_ptr jank_closure_create(jank_arity_flags arity_flags, void *context); + void jank_closure_set_arity0(jank_object_ptr fn, jank_object_ptr (*f)()); + void jank_closure_set_arity1(jank_object_ptr fn, jank_object_ptr (*f)(jank_object_ptr)); + void jank_closure_set_arity2(jank_object_ptr fn, + jank_object_ptr (*f)(jank_object_ptr, jank_object_ptr)); + void + jank_closure_set_arity3(jank_object_ptr fn, + jank_object_ptr (*f)(jank_object_ptr, jank_object_ptr, jank_object_ptr)); + jank_native_bool jank_truthy(jank_object_ptr o); jank_native_bool jank_equal(jank_object_ptr l, jank_object_ptr r); jank_native_hash jank_to_hash(jank_object_ptr o); diff --git a/compiler+runtime/include/cpp/jank/runtime/erasure.hpp b/compiler+runtime/include/cpp/jank/runtime/erasure.hpp index c99fbb2a..2f1a53cb 100644 --- a/compiler+runtime/include/cpp/jank/runtime/erasure.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/erasure.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -363,6 +364,11 @@ namespace jank::runtime return fn(expect_object(erased), std::forward(args)...); } break; + case object_type::jit_closure: + { + return fn(expect_object(erased), std::forward(args)...); + } + break; case object_type::multi_function: { return fn(expect_object(erased), std::forward(args)...); diff --git a/compiler+runtime/include/cpp/jank/runtime/obj/jit_closure.hpp b/compiler+runtime/include/cpp/jank/runtime/obj/jit_closure.hpp new file mode 100644 index 00000000..2c7202d4 --- /dev/null +++ b/compiler+runtime/include/cpp/jank/runtime/obj/jit_closure.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include +#include + +namespace jank::runtime +{ + template <> + struct static_object + : gc + , behavior::callable + { + static constexpr native_bool pointer_free{ false }; + + static_object() = default; + static_object(static_object &&) = default; + static_object(static_object const &) = default; + static_object(arity_flag_t arity_flags); + static_object(arity_flag_t arity_flags, void *context); + static_object(object_ptr meta); + + /* behavior::object_like */ + native_bool equal(object const &) const; + native_persistent_string to_string(); + void to_string(fmt::memory_buffer &buff); + native_persistent_string to_code_string(); + native_hash to_hash() const; + + /* behavior::metadatable */ + native_box with_meta(object_ptr m); + + /* behavior::callable */ + object_ptr call() final; + object_ptr call(object_ptr) final; + object_ptr call(object_ptr, object_ptr) final; + object_ptr call(object_ptr, object_ptr, object_ptr) final; + object_ptr call(object_ptr, object_ptr, object_ptr, object_ptr) final; + object_ptr call(object_ptr, object_ptr, object_ptr, object_ptr, object_ptr) final; + object_ptr call(object_ptr, object_ptr, object_ptr, object_ptr, object_ptr, object_ptr) final; + object_ptr + call(object_ptr, object_ptr, object_ptr, object_ptr, object_ptr, object_ptr, object_ptr) + final; + object_ptr call(object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr) final; + object_ptr call(object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr) final; + object_ptr call(object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr, + object_ptr) final; + + arity_flag_t get_arity_flags() const final; + + object_ptr this_object_ptr() final; + + object base{ object_type::jit_closure }; + void *context{}; + object *(*arity_0)(void *){}; + object *(*arity_1)(void *, object *){}; + object *(*arity_2)(void *, object *, object *){}; + object *(*arity_3)(void *, object *, object *, object *){}; + object *(*arity_4)(void *, object *, object *, object *, object *){}; + object *(*arity_5)(void *, object *, object *, object *, object *, object *){}; + object *(*arity_6)(void *, object *, object *, object *, object *, object *, object *){}; + object *( + *arity_7)(void *, object *, object *, object *, object *, object *, object *, object *){}; + object *(*arity_8)(void *, + object *, + object *, + object *, + object *, + object *, + object *, + object *, + object *){}; + object *(*arity_9)(void *, + object *, + object *, + object *, + object *, + object *, + object *, + object *, + object *, + object *){}; + object *(*arity_10)(void *, + object *, + object *, + object *, + object *, + object *, + object *, + object *, + object *, + object *, + object *){}; + option meta; + arity_flag_t arity_flags{}; + }; + + namespace obj + { + using jit_closure = static_object; + using jit_closure_ptr = native_box; + } +} diff --git a/compiler+runtime/include/cpp/jank/runtime/object.hpp b/compiler+runtime/include/cpp/jank/runtime/object.hpp index 701b141a..f053d23d 100644 --- a/compiler+runtime/include/cpp/jank/runtime/object.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/object.hpp @@ -62,6 +62,7 @@ namespace jank::runtime native_function_wrapper, jit_function, + jit_closure, multi_function, atom, diff --git a/compiler+runtime/src/cpp/clang/cc1_main.cpp b/compiler+runtime/src/cpp/clang/cc1_main.cpp new file mode 100644 index 00000000..3c0599c2 --- /dev/null +++ b/compiler+runtime/src/cpp/clang/cc1_main.cpp @@ -0,0 +1,320 @@ +//===-- cc1_main.cpp - Clang CC1 Compiler Frontend ------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This is the entry point to the clang -cc1 functionality, which implements the +// core compiler functionality along with a number of additional tools for +// demonstration and testing purposes. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Stack.h" +#include "clang/Basic/TargetOptions.h" +#include "clang/CodeGen/ObjectFilePCHContainerOperations.h" +#include "clang/Config/config.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/Options.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/TextDiagnosticBuffer.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Frontend/Utils.h" +#include "clang/FrontendTool/Utils.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/LinkAllPasses.h" +#include "llvm/MC/MCSubtargetInfo.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Support/BuryPointer.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/TimeProfiler.h" +#include "llvm/Support/Timer.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/TargetParser/AArch64TargetParser.h" +#include "llvm/TargetParser/ARMTargetParser.h" +#include "llvm/TargetParser/RISCVISAInfo.h" +#include + +#ifdef CLANG_HAVE_RLIMITS +#include +#endif + +using namespace clang; +using namespace llvm::opt; + +//===----------------------------------------------------------------------===// +// Main driver +//===----------------------------------------------------------------------===// + +static void LLVMErrorHandler(void *UserData, const char *Message, + bool GenCrashDiag) { + DiagnosticsEngine &Diags = *static_cast(UserData); + + Diags.Report(diag::err_fe_error_backend) << Message; + + // Run the interrupt handlers to make sure any special cleanups get done, in + // particular that we remove files registered with RemoveFileOnSignal. + llvm::sys::RunInterruptHandlers(); + + // We cannot recover from llvm errors. When reporting a fatal error, exit + // with status 70 to generate crash diagnostics. For BSD systems this is + // defined as an internal software error. Otherwise, exit with status 1. + llvm::sys::Process::Exit(GenCrashDiag ? 70 : 1); +} + +#ifdef CLANG_HAVE_RLIMITS +/// Attempt to ensure that we have at least 8MiB of usable stack space. +static void ensureSufficientStack() { + struct rlimit rlim; + if (getrlimit(RLIMIT_STACK, &rlim) != 0) + return; + + // Increase the soft stack limit to our desired level, if necessary and + // possible. + if (rlim.rlim_cur != RLIM_INFINITY && + rlim.rlim_cur < rlim_t(DesiredStackSize)) { + // Try to allocate sufficient stack. + if (rlim.rlim_max == RLIM_INFINITY || + rlim.rlim_max >= rlim_t(DesiredStackSize)) + rlim.rlim_cur = DesiredStackSize; + else if (rlim.rlim_cur == rlim.rlim_max) + return; + else + rlim.rlim_cur = rlim.rlim_max; + + if (setrlimit(RLIMIT_STACK, &rlim) != 0 || + rlim.rlim_cur != DesiredStackSize) + return; + } +} +#else +static void ensureSufficientStack() {} +#endif + +/// Print supported cpus of the given target. +static int PrintSupportedCPUs(std::string TargetStr) { + std::string Error; + const llvm::Target *TheTarget = + llvm::TargetRegistry::lookupTarget(TargetStr, Error); + if (!TheTarget) { + llvm::errs() << Error; + return 1; + } + + // the target machine will handle the mcpu printing + llvm::TargetOptions Options; + std::unique_ptr TheTargetMachine( + TheTarget->createTargetMachine(TargetStr, "", "+cpuhelp", Options, + std::nullopt)); + return 0; +} + +static int PrintSupportedExtensions(std::string TargetStr) { + std::string Error; + const llvm::Target *TheTarget = + llvm::TargetRegistry::lookupTarget(TargetStr, Error); + if (!TheTarget) { + llvm::errs() << Error; + return 1; + } + + llvm::TargetOptions Options; + std::unique_ptr TheTargetMachine( + TheTarget->createTargetMachine(TargetStr, "", "", Options, std::nullopt)); + const llvm::Triple &MachineTriple = TheTargetMachine->getTargetTriple(); + const llvm::MCSubtargetInfo *MCInfo = TheTargetMachine->getMCSubtargetInfo(); + const llvm::ArrayRef Features = + MCInfo->getAllProcessorFeatures(); + + llvm::StringMap DescMap; + for (const llvm::SubtargetFeatureKV &feature : Features) + DescMap.insert({feature.Key, feature.Desc}); + + if (MachineTriple.isRISCV()) + llvm::riscvExtensionsHelp(DescMap); + else if (MachineTriple.isAArch64()) + llvm::AArch64::PrintSupportedExtensions(); + else if (MachineTriple.isARM()) + llvm::ARM::PrintSupportedExtensions(DescMap); + else { + // The option was already checked in Driver::HandleImmediateArgs, + // so we do not expect to get here if we are not a supported architecture. + assert(0 && "Unhandled triple for --print-supported-extensions option."); + return 1; + } + + return 0; +} + +static int PrintEnabledExtensions(const TargetOptions& TargetOpts) { + std::string Error; + const llvm::Target *TheTarget = + llvm::TargetRegistry::lookupTarget(TargetOpts.Triple, Error); + if (!TheTarget) { + llvm::errs() << Error; + return 1; + } + + // Create a target machine using the input features, the triple information + // and a dummy instance of llvm::TargetOptions. Note that this is _not_ the + // same as the `clang::TargetOptions` instance we have access to here. + llvm::TargetOptions BackendOptions; + std::string FeaturesStr = llvm::join(TargetOpts.FeaturesAsWritten, ","); + std::unique_ptr TheTargetMachine( + TheTarget->createTargetMachine(TargetOpts.Triple, TargetOpts.CPU, FeaturesStr, BackendOptions, std::nullopt)); + const llvm::Triple &MachineTriple = TheTargetMachine->getTargetTriple(); + const llvm::MCSubtargetInfo *MCInfo = TheTargetMachine->getMCSubtargetInfo(); + + // Extract the feature names that are enabled for the given target. + // We do that by capturing the key from the set of SubtargetFeatureKV entries + // provided by MCSubtargetInfo, which match the '-target-feature' values. + const std::vector Features = + MCInfo->getEnabledProcessorFeatures(); + std::set EnabledFeatureNames; + for (const llvm::SubtargetFeatureKV &feature : Features) + EnabledFeatureNames.insert(feature.Key); + + if (!MachineTriple.isAArch64()) { + // The option was already checked in Driver::HandleImmediateArgs, + // so we do not expect to get here if we are not a supported architecture. + assert(0 && "Unhandled triple for --print-enabled-extensions option."); + return 1; + } + llvm::AArch64::printEnabledExtensions(EnabledFeatureNames); + + return 0; +} + +int cc1_main(ArrayRef Argv, const char *Argv0, void *MainAddr) { + ensureSufficientStack(); + + std::unique_ptr Clang(new CompilerInstance()); + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + + // Register the support for object-file-wrapped Clang modules. + auto PCHOps = Clang->getPCHContainerOperations(); + PCHOps->registerWriter(std::make_unique()); + PCHOps->registerReader(std::make_unique()); + + // Initialize targets first, so that --version shows registered targets. + llvm::InitializeAllTargets(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmPrinters(); + llvm::InitializeAllAsmParsers(); + + // Buffer diagnostics from argument parsing so that we can output them using a + // well formed diagnostic object. + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + TextDiagnosticBuffer *DiagsBuffer = new TextDiagnosticBuffer; + DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagsBuffer); + + // Setup round-trip remarks for the DiagnosticsEngine used in CreateFromArgs. + if (find(Argv, StringRef("-Rround-trip-cc1-args")) != Argv.end()) + Diags.setSeverity(diag::remark_cc1_round_trip_generated, + diag::Severity::Remark, {}); + + bool Success = CompilerInvocation::CreateFromArgs(Clang->getInvocation(), + Argv, Diags, Argv0); + + if (!Clang->getFrontendOpts().TimeTracePath.empty()) { + llvm::timeTraceProfilerInitialize( + Clang->getFrontendOpts().TimeTraceGranularity, Argv0); + } + // --print-supported-cpus takes priority over the actual compilation. + if (Clang->getFrontendOpts().PrintSupportedCPUs) + return PrintSupportedCPUs(Clang->getTargetOpts().Triple); + + // --print-supported-extensions takes priority over the actual compilation. + if (Clang->getFrontendOpts().PrintSupportedExtensions) + return PrintSupportedExtensions(Clang->getTargetOpts().Triple); + + // --print-enabled-extensions takes priority over the actual compilation. + if (Clang->getFrontendOpts().PrintEnabledExtensions) + return PrintEnabledExtensions(Clang->getTargetOpts()); + + // Infer the builtin include path if unspecified. + if (Clang->getHeaderSearchOpts().UseBuiltinIncludes && + Clang->getHeaderSearchOpts().ResourceDir.empty()) + Clang->getHeaderSearchOpts().ResourceDir = + CompilerInvocation::GetResourcesPath(Argv0, MainAddr); + + // Create the actual diagnostics engine. + Clang->createDiagnostics(); + if (!Clang->hasDiagnostics()) + return 1; + + // Set an error handler, so that any LLVM backend diagnostics go through our + // error handler. + llvm::install_fatal_error_handler(LLVMErrorHandler, + static_cast(&Clang->getDiagnostics())); + + DiagsBuffer->FlushDiagnostics(Clang->getDiagnostics()); + if (!Success) { + Clang->getDiagnosticClient().finish(); + return 1; + } + + // Execute the frontend actions. + { + llvm::TimeTraceScope TimeScope("ExecuteCompiler"); + Success = ExecuteCompilerInvocation(Clang.get()); + } + + // If any timers were active but haven't been destroyed yet, print their + // results now. This happens in -disable-free mode. + llvm::TimerGroup::printAll(llvm::errs()); + llvm::TimerGroup::clearAll(); + + if (llvm::timeTraceProfilerEnabled()) { + // It is possible that the compiler instance doesn't own a file manager here + // if we're compiling a module unit. Since the file manager are owned by AST + // when we're compiling a module unit. So the file manager may be invalid + // here. + // + // It should be fine to create file manager here since the file system + // options are stored in the compiler invocation and we can recreate the VFS + // from the compiler invocation. + if (!Clang->hasFileManager()) + Clang->createFileManager(createVFSFromCompilerInvocation( + Clang->getInvocation(), Clang->getDiagnostics())); + + if (auto profilerOutput = Clang->createOutputFile( + Clang->getFrontendOpts().TimeTracePath, /*Binary=*/false, + /*RemoveFileOnSignal=*/false, + /*useTemporary=*/false)) { + llvm::timeTraceProfilerWrite(*profilerOutput); + profilerOutput.reset(); + llvm::timeTraceProfilerCleanup(); + Clang->clearOutputFiles(false); + } + } + + // Our error handler depends on the Diagnostics object, which we're + // potentially about to delete. Uninstall the handler now so that any + // later errors use the default handling behavior instead. + llvm::remove_fatal_error_handler(); + + // When running with -disable-free, don't do any destruction or shutdown. + if (Clang->getFrontendOpts().DisableFree) { + llvm::BuryPointer(std::move(Clang)); + return !Success; + } + + return !Success; +} diff --git a/compiler+runtime/src/cpp/clang/cc1as_main.cpp b/compiler+runtime/src/cpp/clang/cc1as_main.cpp new file mode 100644 index 00000000..4e0aa145 --- /dev/null +++ b/compiler+runtime/src/cpp/clang/cc1as_main.cpp @@ -0,0 +1,703 @@ +//===-- cc1as_main.cpp - Clang Assembler ---------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This is the entry point to the clang -cc1as functionality, which implements +// the direct interface to the LLVM MC based assembler. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/Options.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Frontend/Utils.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/MC/MCAsmBackend.h" +#include "llvm/MC/MCAsmInfo.h" +#include "llvm/MC/MCCodeEmitter.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCInstrInfo.h" +#include "llvm/MC/MCObjectFileInfo.h" +#include "llvm/MC/MCObjectWriter.h" +#include "llvm/MC/MCParser/MCAsmParser.h" +#include "llvm/MC/MCParser/MCTargetAsmParser.h" +#include "llvm/MC/MCRegisterInfo.h" +#include "llvm/MC/MCSectionMachO.h" +#include "llvm/MC/MCStreamer.h" +#include "llvm/MC/MCSubtargetInfo.h" +#include "llvm/MC/MCTargetOptions.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormattedStream.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/Timer.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/Host.h" +#include "llvm/TargetParser/Triple.h" +#include +#include +#include +using namespace clang; +using namespace clang::driver; +using namespace clang::driver::options; +using namespace llvm; +using namespace llvm::opt; + +namespace { + +/// Helper class for representing a single invocation of the assembler. +struct AssemblerInvocation { + /// @name Target Options + /// @{ + + /// The name of the target triple to assemble for. + std::string Triple; + + /// If given, the name of the target CPU to determine which instructions + /// are legal. + std::string CPU; + + /// The list of target specific features to enable or disable -- this should + /// be a list of strings starting with '+' or '-'. + std::vector Features; + + /// The list of symbol definitions. + std::vector SymbolDefs; + + /// @} + /// @name Language Options + /// @{ + + std::vector IncludePaths; + LLVM_PREFERRED_TYPE(bool) + unsigned NoInitialTextSection : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned SaveTemporaryLabels : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned GenDwarfForAssembly : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned RelaxELFRelocations : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned Dwarf64 : 1; + unsigned DwarfVersion; + std::string DwarfDebugFlags; + std::string DwarfDebugProducer; + std::string DebugCompilationDir; + llvm::SmallVector, 0> DebugPrefixMap; + llvm::DebugCompressionType CompressDebugSections = + llvm::DebugCompressionType::None; + std::string MainFileName; + std::string SplitDwarfOutput; + + /// @} + /// @name Frontend Options + /// @{ + + std::string InputFile; + std::vector LLVMArgs; + std::string OutputPath; + enum FileType { + FT_Asm, ///< Assembly (.s) output, transliterate mode. + FT_Null, ///< No output, for timing purposes. + FT_Obj ///< Object file output. + }; + FileType OutputType; + LLVM_PREFERRED_TYPE(bool) + unsigned ShowHelp : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned ShowVersion : 1; + + /// @} + /// @name Transliterate Options + /// @{ + + unsigned OutputAsmVariant; + LLVM_PREFERRED_TYPE(bool) + unsigned ShowEncoding : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned ShowInst : 1; + + /// @} + /// @name Assembler Options + /// @{ + + LLVM_PREFERRED_TYPE(bool) + unsigned RelaxAll : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned NoExecStack : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned FatalWarnings : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned NoWarn : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned NoTypeCheck : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned IncrementalLinkerCompatible : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned EmbedBitcode : 1; + + /// Whether to emit DWARF unwind info. + EmitDwarfUnwindType EmitDwarfUnwind; + + // Whether to emit compact-unwind for non-canonical entries. + // Note: maybe overriden by other constraints. + LLVM_PREFERRED_TYPE(bool) + unsigned EmitCompactUnwindNonCanonical : 1; + + LLVM_PREFERRED_TYPE(bool) + unsigned Crel : 1; + + /// The name of the relocation model to use. + std::string RelocationModel; + + /// The ABI targeted by the backend. Specified using -target-abi. Empty + /// otherwise. + std::string TargetABI; + + /// Darwin target variant triple, the variant of the deployment target + /// for which the code is being compiled. + std::optional DarwinTargetVariantTriple; + + /// The version of the darwin target variant SDK which was used during the + /// compilation + llvm::VersionTuple DarwinTargetVariantSDKVersion; + + /// The name of a file to use with \c .secure_log_unique directives. + std::string AsSecureLogFile; + /// @} + +public: + AssemblerInvocation() { + Triple = ""; + NoInitialTextSection = 0; + InputFile = "-"; + OutputPath = "-"; + OutputType = FT_Asm; + OutputAsmVariant = 0; + ShowInst = 0; + ShowEncoding = 0; + RelaxAll = 0; + NoExecStack = 0; + FatalWarnings = 0; + NoWarn = 0; + NoTypeCheck = 0; + IncrementalLinkerCompatible = 0; + Dwarf64 = 0; + DwarfVersion = 0; + EmbedBitcode = 0; + EmitDwarfUnwind = EmitDwarfUnwindType::Default; + EmitCompactUnwindNonCanonical = false; + Crel = false; + } + + static bool CreateFromArgs(AssemblerInvocation &Res, + ArrayRef Argv, + DiagnosticsEngine &Diags); +}; + +} + +bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, + ArrayRef Argv, + DiagnosticsEngine &Diags) { + bool Success = true; + + // Parse the arguments. + const OptTable &OptTbl = getDriverOptTable(); + + llvm::opt::Visibility VisibilityMask(options::CC1AsOption); + unsigned MissingArgIndex, MissingArgCount; + InputArgList Args = + OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, VisibilityMask); + + // Check for missing argument error. + if (MissingArgCount) { + Diags.Report(diag::err_drv_missing_argument) + << Args.getArgString(MissingArgIndex) << MissingArgCount; + Success = false; + } + + // Issue errors on unknown arguments. + for (const Arg *A : Args.filtered(OPT_UNKNOWN)) { + auto ArgString = A->getAsString(Args); + std::string Nearest; + if (OptTbl.findNearest(ArgString, Nearest, VisibilityMask) > 1) + Diags.Report(diag::err_drv_unknown_argument) << ArgString; + else + Diags.Report(diag::err_drv_unknown_argument_with_suggestion) + << ArgString << Nearest; + Success = false; + } + + // Construct the invocation. + + // Target Options + Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple)); + if (Arg *A = Args.getLastArg(options::OPT_darwin_target_variant_triple)) + Opts.DarwinTargetVariantTriple = llvm::Triple(A->getValue()); + if (Arg *A = Args.getLastArg(OPT_darwin_target_variant_sdk_version_EQ)) { + VersionTuple Version; + if (Version.tryParse(A->getValue())) + Diags.Report(diag::err_drv_invalid_value) + << A->getAsString(Args) << A->getValue(); + else + Opts.DarwinTargetVariantSDKVersion = Version; + } + + Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu)); + Opts.Features = Args.getAllArgValues(OPT_target_feature); + + // Use the default target triple if unspecified. + if (Opts.Triple.empty()) + Opts.Triple = llvm::sys::getDefaultTargetTriple(); + + // Language Options + Opts.IncludePaths = Args.getAllArgValues(OPT_I); + Opts.NoInitialTextSection = Args.hasArg(OPT_n); + Opts.SaveTemporaryLabels = Args.hasArg(OPT_msave_temp_labels); + // Any DebugInfoKind implies GenDwarfForAssembly. + Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ); + + if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections_EQ)) { + Opts.CompressDebugSections = + llvm::StringSwitch(A->getValue()) + .Case("none", llvm::DebugCompressionType::None) + .Case("zlib", llvm::DebugCompressionType::Zlib) + .Case("zstd", llvm::DebugCompressionType::Zstd) + .Default(llvm::DebugCompressionType::None); + } + + Opts.RelaxELFRelocations = !Args.hasArg(OPT_mrelax_relocations_no); + if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32)) + Opts.Dwarf64 = DwarfFormatArg->getOption().matches(OPT_gdwarf64); + Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags); + Opts.DwarfDebugFlags = + std::string(Args.getLastArgValue(OPT_dwarf_debug_flags)); + Opts.DwarfDebugProducer = + std::string(Args.getLastArgValue(OPT_dwarf_debug_producer)); + if (const Arg *A = Args.getLastArg(options::OPT_ffile_compilation_dir_EQ, + options::OPT_fdebug_compilation_dir_EQ)) + Opts.DebugCompilationDir = A->getValue(); + Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name)); + + for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) { + auto Split = StringRef(Arg).split('='); + Opts.DebugPrefixMap.emplace_back(Split.first, Split.second); + } + + // Frontend Options + if (Args.hasArg(OPT_INPUT)) { + bool First = true; + for (const Arg *A : Args.filtered(OPT_INPUT)) { + if (First) { + Opts.InputFile = A->getValue(); + First = false; + } else { + Diags.Report(diag::err_drv_unknown_argument) << A->getAsString(Args); + Success = false; + } + } + } + Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm); + Opts.OutputPath = std::string(Args.getLastArgValue(OPT_o)); + Opts.SplitDwarfOutput = + std::string(Args.getLastArgValue(OPT_split_dwarf_output)); + if (Arg *A = Args.getLastArg(OPT_filetype)) { + StringRef Name = A->getValue(); + unsigned OutputType = StringSwitch(Name) + .Case("asm", FT_Asm) + .Case("null", FT_Null) + .Case("obj", FT_Obj) + .Default(~0U); + if (OutputType == ~0U) { + Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name; + Success = false; + } else + Opts.OutputType = FileType(OutputType); + } + Opts.ShowHelp = Args.hasArg(OPT_help); + Opts.ShowVersion = Args.hasArg(OPT_version); + + // Transliterate Options + Opts.OutputAsmVariant = + getLastArgIntValue(Args, OPT_output_asm_variant, 0, Diags); + Opts.ShowEncoding = Args.hasArg(OPT_show_encoding); + Opts.ShowInst = Args.hasArg(OPT_show_inst); + + // Assemble Options + Opts.RelaxAll = Args.hasArg(OPT_mrelax_all); + Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack); + Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings); + Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn); + Opts.NoTypeCheck = Args.hasArg(OPT_mno_type_check); + Opts.RelocationModel = + std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic")); + Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi)); + Opts.IncrementalLinkerCompatible = + Args.hasArg(OPT_mincremental_linker_compatible); + Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym); + + // EmbedBitcode Option. If -fembed-bitcode is enabled, set the flag. + // EmbedBitcode behaves the same for all embed options for assembly files. + if (auto *A = Args.getLastArg(OPT_fembed_bitcode_EQ)) { + Opts.EmbedBitcode = llvm::StringSwitch(A->getValue()) + .Case("all", 1) + .Case("bitcode", 1) + .Case("marker", 1) + .Default(0); + } + + if (auto *A = Args.getLastArg(OPT_femit_dwarf_unwind_EQ)) { + Opts.EmitDwarfUnwind = + llvm::StringSwitch(A->getValue()) + .Case("always", EmitDwarfUnwindType::Always) + .Case("no-compact-unwind", EmitDwarfUnwindType::NoCompactUnwind) + .Case("default", EmitDwarfUnwindType::Default); + } + + Opts.EmitCompactUnwindNonCanonical = + Args.hasArg(OPT_femit_compact_unwind_non_canonical); + Opts.Crel = Args.hasArg(OPT_crel); + + Opts.AsSecureLogFile = Args.getLastArgValue(OPT_as_secure_log_file); + + return Success; +} + +static std::unique_ptr +getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) { + // Make sure that the Out file gets unlinked from the disk if we get a + // SIGINT. + if (Path != "-") + sys::RemoveFileOnSignal(Path); + + std::error_code EC; + auto Out = std::make_unique( + Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_TextWithCRLF)); + if (EC) { + Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message(); + return nullptr; + } + + return Out; +} + +static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, + DiagnosticsEngine &Diags) { + // Get the target specific parser. + std::string Error; + const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error); + if (!TheTarget) + return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple; + + ErrorOr> Buffer = + MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true); + + if (std::error_code EC = Buffer.getError()) { + return Diags.Report(diag::err_fe_error_reading) + << Opts.InputFile << EC.message(); + } + + SourceMgr SrcMgr; + + // Tell SrcMgr about this buffer, which is what the parser will pick up. + unsigned BufferIndex = SrcMgr.AddNewSourceBuffer(std::move(*Buffer), SMLoc()); + + // Record the location of the include directories so that the lexer can find + // it later. + SrcMgr.setIncludeDirs(Opts.IncludePaths); + + std::unique_ptr MRI(TheTarget->createMCRegInfo(Opts.Triple)); + assert(MRI && "Unable to create target register info!"); + + MCTargetOptions MCOptions; + MCOptions.MCRelaxAll = Opts.RelaxAll; + MCOptions.EmitDwarfUnwind = Opts.EmitDwarfUnwind; + MCOptions.EmitCompactUnwindNonCanonical = Opts.EmitCompactUnwindNonCanonical; + MCOptions.MCSaveTempLabels = Opts.SaveTemporaryLabels; + MCOptions.Crel = Opts.Crel; + MCOptions.X86RelaxRelocations = Opts.RelaxELFRelocations; + MCOptions.CompressDebugSections = Opts.CompressDebugSections; + MCOptions.AsSecureLogFile = Opts.AsSecureLogFile; + + std::unique_ptr MAI( + TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions)); + assert(MAI && "Unable to create target asm info!"); + + // Ensure MCAsmInfo initialization occurs before any use, otherwise sections + // may be created with a combination of default and explicit settings. + + + bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj; + if (Opts.OutputPath.empty()) + Opts.OutputPath = "-"; + std::unique_ptr FDOS = + getOutputStream(Opts.OutputPath, Diags, IsBinary); + if (!FDOS) + return true; + std::unique_ptr DwoOS; + if (!Opts.SplitDwarfOutput.empty()) + DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary); + + // Build up the feature string from the target feature list. + std::string FS = llvm::join(Opts.Features, ","); + + std::unique_ptr STI( + TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS)); + assert(STI && "Unable to create subtarget info!"); + + MCContext Ctx(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr, + &MCOptions); + + bool PIC = false; + if (Opts.RelocationModel == "static") { + PIC = false; + } else if (Opts.RelocationModel == "pic") { + PIC = true; + } else { + assert(Opts.RelocationModel == "dynamic-no-pic" && + "Invalid PIC model!"); + PIC = false; + } + + // FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and + // MCObjectFileInfo needs a MCContext reference in order to initialize itself. + std::unique_ptr MOFI( + TheTarget->createMCObjectFileInfo(Ctx, PIC)); + if (Opts.DarwinTargetVariantTriple) + MOFI->setDarwinTargetVariantTriple(*Opts.DarwinTargetVariantTriple); + if (!Opts.DarwinTargetVariantSDKVersion.empty()) + MOFI->setDarwinTargetVariantSDKVersion(Opts.DarwinTargetVariantSDKVersion); + Ctx.setObjectFileInfo(MOFI.get()); + + if (Opts.GenDwarfForAssembly) + Ctx.setGenDwarfForAssembly(true); + if (!Opts.DwarfDebugFlags.empty()) + Ctx.setDwarfDebugFlags(StringRef(Opts.DwarfDebugFlags)); + if (!Opts.DwarfDebugProducer.empty()) + Ctx.setDwarfDebugProducer(StringRef(Opts.DwarfDebugProducer)); + if (!Opts.DebugCompilationDir.empty()) + Ctx.setCompilationDir(Opts.DebugCompilationDir); + else { + // If no compilation dir is set, try to use the current directory. + SmallString<128> CWD; + if (!sys::fs::current_path(CWD)) + Ctx.setCompilationDir(CWD); + } + if (!Opts.DebugPrefixMap.empty()) + for (const auto &KV : Opts.DebugPrefixMap) + Ctx.addDebugPrefixMapEntry(KV.first, KV.second); + if (!Opts.MainFileName.empty()) + Ctx.setMainFileName(StringRef(Opts.MainFileName)); + Ctx.setDwarfFormat(Opts.Dwarf64 ? dwarf::DWARF64 : dwarf::DWARF32); + Ctx.setDwarfVersion(Opts.DwarfVersion); + if (Opts.GenDwarfForAssembly) + Ctx.setGenDwarfRootFile(Opts.InputFile, + SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer()); + + std::unique_ptr Str; + + std::unique_ptr MCII(TheTarget->createMCInstrInfo()); + assert(MCII && "Unable to create instruction info!"); + + raw_pwrite_stream *Out = FDOS.get(); + std::unique_ptr BOS; + + MCOptions.MCNoWarn = Opts.NoWarn; + MCOptions.MCFatalWarnings = Opts.FatalWarnings; + MCOptions.MCNoTypeCheck = Opts.NoTypeCheck; + MCOptions.ABIName = Opts.TargetABI; + + // FIXME: There is a bit of code duplication with addPassesToEmitFile. + if (Opts.OutputType == AssemblerInvocation::FT_Asm) { + MCInstPrinter *IP = TheTarget->createMCInstPrinter( + llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI); + + std::unique_ptr CE; + if (Opts.ShowEncoding) + CE.reset(TheTarget->createMCCodeEmitter(*MCII, Ctx)); + std::unique_ptr MAB( + TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions)); + + auto FOut = std::make_unique(*Out); + Str.reset(TheTarget->createAsmStreamer( + Ctx, std::move(FOut), /*asmverbose*/ true, + /*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB), + Opts.ShowInst)); + } else if (Opts.OutputType == AssemblerInvocation::FT_Null) { + Str.reset(createNullStreamer(Ctx)); + } else { + assert(Opts.OutputType == AssemblerInvocation::FT_Obj && + "Invalid file type!"); + if (!FDOS->supportsSeeking()) { + BOS = std::make_unique(*FDOS); + Out = BOS.get(); + } + + std::unique_ptr CE( + TheTarget->createMCCodeEmitter(*MCII, Ctx)); + std::unique_ptr MAB( + TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions)); + assert(MAB && "Unable to create asm backend!"); + + std::unique_ptr OW = + DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS) + : MAB->createObjectWriter(*Out); + + Triple T(Opts.Triple); + Str.reset(TheTarget->createMCObjectStreamer( + T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI, + Opts.RelaxAll, Opts.IncrementalLinkerCompatible, + /*DWARFMustBeAtTheEnd*/ true)); + Str.get()->initSections(Opts.NoExecStack, *STI); + } + + // When -fembed-bitcode is passed to clang_as, a 1-byte marker + // is emitted in __LLVM,__asm section if the object file is MachO format. + if (Opts.EmbedBitcode && Ctx.getObjectFileType() == MCContext::IsMachO) { + MCSection *AsmLabel = Ctx.getMachOSection( + "__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly()); + Str.get()->switchSection(AsmLabel); + Str.get()->emitZeros(1); + } + + bool Failed = false; + + std::unique_ptr Parser( + createMCAsmParser(SrcMgr, Ctx, *Str.get(), *MAI)); + + // FIXME: init MCTargetOptions from sanitizer flags here. + std::unique_ptr TAP( + TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions)); + if (!TAP) + Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple; + + // Set values for symbols, if any. + for (auto &S : Opts.SymbolDefs) { + auto Pair = StringRef(S).split('='); + auto Sym = Pair.first; + auto Val = Pair.second; + int64_t Value; + // We have already error checked this in the driver. + Val.getAsInteger(0, Value); + Ctx.setSymbolValue(Parser->getStreamer(), Sym, Value); + } + + if (!Failed) { + Parser->setTargetParser(*TAP.get()); + Failed = Parser->Run(Opts.NoInitialTextSection); + } + + return Failed; +} + +static bool ExecuteAssembler(AssemblerInvocation &Opts, + DiagnosticsEngine &Diags) { + bool Failed = ExecuteAssemblerImpl(Opts, Diags); + + // Delete output file if there were errors. + if (Failed) { + if (Opts.OutputPath != "-") + sys::fs::remove(Opts.OutputPath); + if (!Opts.SplitDwarfOutput.empty() && Opts.SplitDwarfOutput != "-") + sys::fs::remove(Opts.SplitDwarfOutput); + } + + return Failed; +} + +static void LLVMErrorHandler(void *UserData, const char *Message, + bool GenCrashDiag) { + DiagnosticsEngine &Diags = *static_cast(UserData); + + Diags.Report(diag::err_fe_error_backend) << Message; + + // We cannot recover from llvm errors. + sys::Process::Exit(1); +} + +int cc1as_main(ArrayRef Argv, const char *Argv0, void *MainAddr) { + // Initialize targets and assembly printers/parsers. + InitializeAllTargetInfos(); + InitializeAllTargetMCs(); + InitializeAllAsmParsers(); + + // Construct our diagnostic client. + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + TextDiagnosticPrinter *DiagClient + = new TextDiagnosticPrinter(errs(), &*DiagOpts); + DiagClient->setPrefix("clang -cc1as"); + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient); + + // Set an error handler, so that any LLVM backend diagnostics go through our + // error handler. + ScopedFatalErrorHandler FatalErrorHandler + (LLVMErrorHandler, static_cast(&Diags)); + + // Parse the arguments. + AssemblerInvocation Asm; + if (!AssemblerInvocation::CreateFromArgs(Asm, Argv, Diags)) + return 1; + + if (Asm.ShowHelp) { + getDriverOptTable().printHelp( + llvm::outs(), "clang -cc1as [options] file...", + "Clang Integrated Assembler", /*ShowHidden=*/false, + /*ShowAllAliases=*/false, + llvm::opt::Visibility(driver::options::CC1AsOption)); + + return 0; + } + + // Honor -version. + // + // FIXME: Use a better -version message? + if (Asm.ShowVersion) { + llvm::cl::PrintVersionMessage(); + return 0; + } + + // Honor -mllvm. + // + // FIXME: Remove this, one day. + if (!Asm.LLVMArgs.empty()) { + unsigned NumArgs = Asm.LLVMArgs.size(); + auto Args = std::make_unique(NumArgs + 2); + Args[0] = "clang (LLVM option parsing)"; + for (unsigned i = 0; i != NumArgs; ++i) + Args[i + 1] = Asm.LLVMArgs[i].c_str(); + Args[NumArgs + 1] = nullptr; + llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); + } + + // Execute the invocation, unless there were parsing errors. + bool Failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags); + + // If any timers were active but haven't been destroyed yet, print their + // results now. + TimerGroup::printAll(errs()); + TimerGroup::clearAll(); + + return !!Failed; +} diff --git a/compiler+runtime/src/cpp/clang/cc1gen_reproducer_main.cpp b/compiler+runtime/src/cpp/clang/cc1gen_reproducer_main.cpp new file mode 100644 index 00000000..e97fa3d2 --- /dev/null +++ b/compiler+runtime/src/cpp/clang/cc1gen_reproducer_main.cpp @@ -0,0 +1,204 @@ +//===-- cc1gen_reproducer_main.cpp - Clang reproducer generator ----------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This is the entry point to the clang -cc1gen-reproducer functionality, which +// generates reproducers for invocations for clang-based tools. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LLVM.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/LLVMDriver.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/Host.h" +#include + +using namespace clang; + +namespace { + +struct UnsavedFileHash { + std::string Name; + std::string MD5; +}; + +struct ClangInvocationInfo { + std::string Toolchain; + std::string LibclangOperation; + std::string LibclangOptions; + std::vector Arguments; + std::vector InvocationArguments; + std::vector UnsavedFileHashes; + bool Dump = false; +}; + +} // end anonymous namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(UnsavedFileHash) + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + static void mapping(IO &IO, UnsavedFileHash &Info) { + IO.mapRequired("name", Info.Name); + IO.mapRequired("md5", Info.MD5); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ClangInvocationInfo &Info) { + IO.mapRequired("toolchain", Info.Toolchain); + IO.mapOptional("libclang.operation", Info.LibclangOperation); + IO.mapOptional("libclang.opts", Info.LibclangOptions); + IO.mapRequired("args", Info.Arguments); + IO.mapOptional("invocation-args", Info.InvocationArguments); + IO.mapOptional("unsaved_file_hashes", Info.UnsavedFileHashes); + } +}; + +} // end namespace yaml +} // end namespace llvm + +static std::string generateReproducerMetaInfo(const ClangInvocationInfo &Info) { + std::string Result; + llvm::raw_string_ostream OS(Result); + OS << '{'; + bool NeedComma = false; + auto EmitKey = [&](StringRef Key) { + if (NeedComma) + OS << ", "; + NeedComma = true; + OS << '"' << Key << "\": "; + }; + auto EmitStringKey = [&](StringRef Key, StringRef Value) { + if (Value.empty()) + return; + EmitKey(Key); + OS << '"' << Value << '"'; + }; + EmitStringKey("libclang.operation", Info.LibclangOperation); + EmitStringKey("libclang.opts", Info.LibclangOptions); + if (!Info.InvocationArguments.empty()) { + EmitKey("invocation-args"); + OS << '['; + for (const auto &Arg : llvm::enumerate(Info.InvocationArguments)) { + if (Arg.index()) + OS << ','; + OS << '"' << Arg.value() << '"'; + } + OS << ']'; + } + OS << '}'; + // FIXME: Compare unsaved file hashes and report mismatch in the reproducer. + if (Info.Dump) + llvm::outs() << "REPRODUCER METAINFO: " << OS.str() << "\n"; + return std::move(OS.str()); +} + +/// Generates a reproducer for a set of arguments from a specific invocation. +static std::optional +generateReproducerForInvocationArguments(ArrayRef Argv, + const ClangInvocationInfo &Info, + const llvm::ToolContext &ToolContext) { + using namespace driver; + auto TargetAndMode = ToolChain::getTargetAndModeFromProgramName(Argv[0]); + + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions; + + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + DiagnosticsEngine Diags(DiagID, &*DiagOpts, new IgnoringDiagConsumer()); + ProcessWarningOptions(Diags, *DiagOpts, /*ReportDiags=*/false); + Driver TheDriver(ToolContext.Path, llvm::sys::getDefaultTargetTriple(), + Diags); + TheDriver.setTargetAndMode(TargetAndMode); + if (ToolContext.NeedsPrependArg) + TheDriver.setPrependArg(ToolContext.PrependArg); + + std::unique_ptr C(TheDriver.BuildCompilation(Argv)); + if (C && !C->containsError()) { + for (const auto &J : C->getJobs()) { + if (const Command *Cmd = dyn_cast(&J)) { + Driver::CompilationDiagnosticReport Report; + TheDriver.generateCompilationDiagnostics( + *C, *Cmd, generateReproducerMetaInfo(Info), &Report); + return Report; + } + } + } + + return std::nullopt; +} + +std::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes); + +static void printReproducerInformation( + llvm::raw_ostream &OS, const ClangInvocationInfo &Info, + const driver::Driver::CompilationDiagnosticReport &Report) { + OS << "REPRODUCER:\n"; + OS << "{\n"; + OS << R"("files":[)"; + for (const auto &File : llvm::enumerate(Report.TemporaryFiles)) { + if (File.index()) + OS << ','; + OS << '"' << File.value() << '"'; + } + OS << "]\n}\n"; +} + +int cc1gen_reproducer_main(ArrayRef Argv, const char *Argv0, + void *MainAddr, + const llvm::ToolContext &ToolContext) { + if (Argv.size() < 1) { + llvm::errs() << "error: missing invocation file\n"; + return 1; + } + // Parse the invocation descriptor. + StringRef Input = Argv[0]; + llvm::ErrorOr> Buffer = + llvm::MemoryBuffer::getFile(Input, /*IsText=*/true); + if (!Buffer) { + llvm::errs() << "error: failed to read " << Input << ": " + << Buffer.getError().message() << "\n"; + return 1; + } + llvm::yaml::Input YAML(Buffer.get()->getBuffer()); + ClangInvocationInfo InvocationInfo; + YAML >> InvocationInfo; + if (Argv.size() > 1 && Argv[1] == StringRef("-v")) + InvocationInfo.Dump = true; + + // Create an invocation that will produce the reproducer. + std::vector DriverArgs; + for (const auto &Arg : InvocationInfo.Arguments) + DriverArgs.push_back(Arg.c_str()); + std::string Path = GetExecutablePath(Argv0, /*CanonicalPrefixes=*/true); + DriverArgs[0] = Path.c_str(); + std::optional Report = + generateReproducerForInvocationArguments(DriverArgs, InvocationInfo, + ToolContext); + + // Emit the information about the reproduce files to stdout. + int Result = 1; + if (Report) { + printReproducerInformation(llvm::outs(), InvocationInfo, *Report); + Result = 0; + } + + // Remove the input file. + llvm::sys::fs::remove(Input); + return Result; +} diff --git a/compiler+runtime/src/cpp/clang/driver.cpp b/compiler+runtime/src/cpp/clang/driver.cpp new file mode 100644 index 00000000..83b5bbb7 --- /dev/null +++ b/compiler+runtime/src/cpp/clang/driver.cpp @@ -0,0 +1,454 @@ +//===-- driver.cpp - Clang GCC-Compatible Driver --------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This is the entry point to the clang driver; it is a thin wrapper +// for functionality in the Driver clang library. +// +//===----------------------------------------------------------------------===// + +#include "clang/Driver/Driver.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/HeaderInclude.h" +#include "clang/Basic/Stack.h" +#include "clang/Config/config.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/Options.h" +#include "clang/Driver/ToolChain.h" +#include "clang/Frontend/ChainedDiagnosticConsumer.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/SerializedDiagnosticPrinter.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Frontend/Utils.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/BuryPointer.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/CrashRecoveryContext.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/LLVMDriver.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/StringSaver.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/Timer.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/Host.h" +#include +#include +#include +#include +using namespace clang; +using namespace clang::driver; +using namespace llvm::opt; + +std::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes) { + if (!CanonicalPrefixes) { + SmallString<128> ExecutablePath(Argv0); + // Do a PATH lookup if Argv0 isn't a valid path. + if (!llvm::sys::fs::exists(ExecutablePath)) + if (llvm::ErrorOr P = + llvm::sys::findProgramByName(ExecutablePath)) + ExecutablePath = *P; + return std::string(ExecutablePath); + } + + // This just needs to be some symbol in the binary; C++ doesn't + // allow taking the address of ::main however. + void *P = (void*) (intptr_t) GetExecutablePath; + return llvm::sys::fs::getMainExecutable(Argv0, P); +} + +static const char *GetStableCStr(llvm::StringSet<> &SavedStrings, StringRef S) { + return SavedStrings.insert(S).first->getKeyData(); +} + +extern int cc1_main(ArrayRef Argv, const char *Argv0, + void *MainAddr); +extern int cc1as_main(ArrayRef Argv, const char *Argv0, + void *MainAddr); +extern int cc1gen_reproducer_main(ArrayRef Argv, + const char *Argv0, void *MainAddr, + const llvm::ToolContext &); + +static void insertTargetAndModeArgs(const ParsedClangName &NameParts, + SmallVectorImpl &ArgVector, + llvm::StringSet<> &SavedStrings) { + // Put target and mode arguments at the start of argument list so that + // arguments specified in command line could override them. Avoid putting + // them at index 0, as an option like '-cc1' must remain the first. + int InsertionPoint = 0; + if (ArgVector.size() > 0) + ++InsertionPoint; + + if (NameParts.DriverMode) { + // Add the mode flag to the arguments. + ArgVector.insert(ArgVector.begin() + InsertionPoint, + GetStableCStr(SavedStrings, NameParts.DriverMode)); + } + + if (NameParts.TargetIsValid) { + const char *arr[] = {"-target", GetStableCStr(SavedStrings, + NameParts.TargetPrefix)}; + ArgVector.insert(ArgVector.begin() + InsertionPoint, + std::begin(arr), std::end(arr)); + } +} + +static void getCLEnvVarOptions(std::string &EnvValue, llvm::StringSaver &Saver, + SmallVectorImpl &Opts) { + llvm::cl::TokenizeWindowsCommandLine(EnvValue, Saver, Opts); + // The first instance of '#' should be replaced with '=' in each option. + for (const char *Opt : Opts) + if (char *NumberSignPtr = const_cast(::strchr(Opt, '#'))) + *NumberSignPtr = '='; +} + +template +static T checkEnvVar(const char *EnvOptSet, const char *EnvOptFile, + std::string &OptFile) { + const char *Str = ::getenv(EnvOptSet); + if (!Str) + return T{}; + + T OptVal = Str; + if (const char *Var = ::getenv(EnvOptFile)) + OptFile = Var; + return OptVal; +} + +static bool SetBackdoorDriverOutputsFromEnvVars(Driver &TheDriver) { + TheDriver.CCPrintOptions = + checkEnvVar("CC_PRINT_OPTIONS", "CC_PRINT_OPTIONS_FILE", + TheDriver.CCPrintOptionsFilename); + if (checkEnvVar("CC_PRINT_HEADERS", "CC_PRINT_HEADERS_FILE", + TheDriver.CCPrintHeadersFilename)) { + TheDriver.CCPrintHeadersFormat = HIFMT_Textual; + TheDriver.CCPrintHeadersFiltering = HIFIL_None; + } else { + std::string EnvVar = checkEnvVar( + "CC_PRINT_HEADERS_FORMAT", "CC_PRINT_HEADERS_FILE", + TheDriver.CCPrintHeadersFilename); + if (!EnvVar.empty()) { + TheDriver.CCPrintHeadersFormat = + stringToHeaderIncludeFormatKind(EnvVar.c_str()); + if (!TheDriver.CCPrintHeadersFormat) { + TheDriver.Diag(clang::diag::err_drv_print_header_env_var) + << 0 << EnvVar; + return false; + } + + const char *FilteringStr = ::getenv("CC_PRINT_HEADERS_FILTERING"); + HeaderIncludeFilteringKind Filtering; + if (!stringToHeaderIncludeFiltering(FilteringStr, Filtering)) { + TheDriver.Diag(clang::diag::err_drv_print_header_env_var) + << 1 << FilteringStr; + return false; + } + + if ((TheDriver.CCPrintHeadersFormat == HIFMT_Textual && + Filtering != HIFIL_None) || + (TheDriver.CCPrintHeadersFormat == HIFMT_JSON && + Filtering != HIFIL_Only_Direct_System)) { + TheDriver.Diag(clang::diag::err_drv_print_header_env_var_combination) + << EnvVar << FilteringStr; + return false; + } + TheDriver.CCPrintHeadersFiltering = Filtering; + } + } + + TheDriver.CCLogDiagnostics = + checkEnvVar("CC_LOG_DIAGNOSTICS", "CC_LOG_DIAGNOSTICS_FILE", + TheDriver.CCLogDiagnosticsFilename); + TheDriver.CCPrintProcessStats = + checkEnvVar("CC_PRINT_PROC_STAT", "CC_PRINT_PROC_STAT_FILE", + TheDriver.CCPrintStatReportFilename); + TheDriver.CCPrintInternalStats = + checkEnvVar("CC_PRINT_INTERNAL_STAT", "CC_PRINT_INTERNAL_STAT_FILE", + TheDriver.CCPrintInternalStatReportFilename); + + return true; +} + +static void FixupDiagPrefixExeName(TextDiagnosticPrinter *DiagClient, + const std::string &Path) { + // If the clang binary happens to be named cl.exe for compatibility reasons, + // use clang-cl.exe as the prefix to avoid confusion between clang and MSVC. + StringRef ExeBasename(llvm::sys::path::stem(Path)); + if (ExeBasename.equals_insensitive("cl")) + ExeBasename = "clang-cl"; + DiagClient->setPrefix(std::string(ExeBasename)); +} + +static int ExecuteCC1Tool(SmallVectorImpl &ArgV, + const llvm::ToolContext &ToolContext) { + // If we call the cc1 tool from the clangDriver library (through + // Driver::CC1Main), we need to clean up the options usage count. The options + // are currently global, and they might have been used previously by the + // driver. + llvm::cl::ResetAllOptionOccurrences(); + + llvm::BumpPtrAllocator A; + llvm::cl::ExpansionContext ECtx(A, llvm::cl::TokenizeGNUCommandLine); + if (llvm::Error Err = ECtx.expandResponseFiles(ArgV)) { + llvm::errs() << toString(std::move(Err)) << '\n'; + return 1; + } + StringRef Tool = ArgV[1]; + void *GetExecutablePathVP = (void *)(intptr_t)GetExecutablePath; + if (Tool == "-cc1") + return cc1_main(ArrayRef(ArgV).slice(1), ArgV[0], GetExecutablePathVP); + if (Tool == "-cc1as") + return cc1as_main(ArrayRef(ArgV).slice(2), ArgV[0], GetExecutablePathVP); + if (Tool == "-cc1gen-reproducer") + return cc1gen_reproducer_main(ArrayRef(ArgV).slice(2), ArgV[0], + GetExecutablePathVP, ToolContext); + // Reject unknown tools. + llvm::errs() + << "error: unknown integrated tool '" << Tool << "'. " + << "Valid tools include '-cc1', '-cc1as' and '-cc1gen-reproducer'.\n"; + return 1; +} + +int clang_main(int Argc, char **Argv, const llvm::ToolContext &ToolContext) { + noteBottomOfStack(); + llvm::setBugReportMsg("PLEASE submit a bug report to " BUG_REPORT_URL + " and include the crash backtrace, preprocessed " + "source, and associated run script.\n"); + SmallVector Args(Argv, Argv + Argc); + + if (llvm::sys::Process::FixupStandardFileDescriptors()) + return 1; + + llvm::InitializeAllTargets(); + + llvm::BumpPtrAllocator A; + llvm::StringSaver Saver(A); + + const char *ProgName = + ToolContext.NeedsPrependArg ? ToolContext.PrependArg : ToolContext.Path; + + bool ClangCLMode = + IsClangCL(getDriverMode(ProgName, llvm::ArrayRef(Args).slice(1))); + + if (llvm::Error Err = expandResponseFiles(Args, ClangCLMode, A)) { + llvm::errs() << toString(std::move(Err)) << '\n'; + return 1; + } + + // Handle -cc1 integrated tools. + if (Args.size() >= 2 && StringRef(Args[1]).starts_with("-cc1")) + return ExecuteCC1Tool(Args, ToolContext); + + // Handle options that need handling before the real command line parsing in + // Driver::BuildCompilation() + bool CanonicalPrefixes = true; + for (int i = 1, size = Args.size(); i < size; ++i) { + // Skip end-of-line response file markers + if (Args[i] == nullptr) + continue; + if (StringRef(Args[i]) == "-canonical-prefixes") + CanonicalPrefixes = true; + else if (StringRef(Args[i]) == "-no-canonical-prefixes") + CanonicalPrefixes = false; + } + + // Handle CL and _CL_ which permits additional command line options to be + // prepended or appended. + if (ClangCLMode) { + // Arguments in "CL" are prepended. + std::optional OptCL = llvm::sys::Process::GetEnv("CL"); + if (OptCL) { + SmallVector PrependedOpts; + getCLEnvVarOptions(*OptCL, Saver, PrependedOpts); + + // Insert right after the program name to prepend to the argument list. + Args.insert(Args.begin() + 1, PrependedOpts.begin(), PrependedOpts.end()); + } + // Arguments in "_CL_" are appended. + std::optional Opt_CL_ = llvm::sys::Process::GetEnv("_CL_"); + if (Opt_CL_) { + SmallVector AppendedOpts; + getCLEnvVarOptions(*Opt_CL_, Saver, AppendedOpts); + + // Insert at the end of the argument list to append. + Args.append(AppendedOpts.begin(), AppendedOpts.end()); + } + } + + llvm::StringSet<> SavedStrings; + // Handle CCC_OVERRIDE_OPTIONS, used for editing a command line behind the + // scenes. + if (const char *OverrideStr = ::getenv("CCC_OVERRIDE_OPTIONS")) { + // FIXME: Driver shouldn't take extra initial argument. + driver::applyOverrideOptions(Args, OverrideStr, SavedStrings, + &llvm::errs()); + } + + std::string Path = GetExecutablePath(ToolContext.Path, CanonicalPrefixes); + + // Whether the cc1 tool should be called inside the current process, or if we + // should spawn a new clang subprocess (old behavior). + // Not having an additional process saves some execution time of Windows, + // and makes debugging and profiling easier. + bool UseNewCC1Process = CLANG_SPAWN_CC1; + for (const char *Arg : Args) + UseNewCC1Process = llvm::StringSwitch(Arg) + .Case("-fno-integrated-cc1", true) + .Case("-fintegrated-cc1", false) + .Default(UseNewCC1Process); + + IntrusiveRefCntPtr DiagOpts = + CreateAndPopulateDiagOpts(Args); + + TextDiagnosticPrinter *DiagClient + = new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts); + FixupDiagPrefixExeName(DiagClient, ProgName); + + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + + DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient); + + if (!DiagOpts->DiagnosticSerializationFile.empty()) { + auto SerializedConsumer = + clang::serialized_diags::create(DiagOpts->DiagnosticSerializationFile, + &*DiagOpts, /*MergeChildRecords=*/true); + Diags.setClient(new ChainedDiagnosticConsumer( + Diags.takeClient(), std::move(SerializedConsumer))); + } + + ProcessWarningOptions(Diags, *DiagOpts, /*ReportDiags=*/false); + + Driver TheDriver(Path, llvm::sys::getDefaultTargetTriple(), Diags); + auto TargetAndMode = ToolChain::getTargetAndModeFromProgramName(ProgName); + TheDriver.setTargetAndMode(TargetAndMode); + // If -canonical-prefixes is set, GetExecutablePath will have resolved Path + // to the llvm driver binary, not clang. In this case, we need to use + // PrependArg which should be clang-*. Checking just CanonicalPrefixes is + // safe even in the normal case because PrependArg will be null so + // setPrependArg will be a no-op. + if (ToolContext.NeedsPrependArg || CanonicalPrefixes) + TheDriver.setPrependArg(ToolContext.PrependArg); + + insertTargetAndModeArgs(TargetAndMode, Args, SavedStrings); + + if (!SetBackdoorDriverOutputsFromEnvVars(TheDriver)) + return 1; + + if (!UseNewCC1Process) { + TheDriver.CC1Main = [ToolContext](SmallVectorImpl &ArgV) { + return ExecuteCC1Tool(ArgV, ToolContext); + }; + // Ensure the CC1Command actually catches cc1 crashes + llvm::CrashRecoveryContext::Enable(); + } + + std::unique_ptr C(TheDriver.BuildCompilation(Args)); + + Driver::ReproLevel ReproLevel = Driver::ReproLevel::OnCrash; + if (Arg *A = C->getArgs().getLastArg(options::OPT_gen_reproducer_eq)) { + auto Level = + llvm::StringSwitch>(A->getValue()) + .Case("off", Driver::ReproLevel::Off) + .Case("crash", Driver::ReproLevel::OnCrash) + .Case("error", Driver::ReproLevel::OnError) + .Case("always", Driver::ReproLevel::Always) + .Default(std::nullopt); + if (!Level) { + llvm::errs() << "Unknown value for " << A->getSpelling() << ": '" + << A->getValue() << "'\n"; + return 1; + } + ReproLevel = *Level; + } + if (!!::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH")) + ReproLevel = Driver::ReproLevel::Always; + + int Res = 1; + bool IsCrash = false; + Driver::CommandStatus CommandStatus = Driver::CommandStatus::Ok; + // Pretend the first command failed if ReproStatus is Always. + const Command *FailingCommand = nullptr; + if (!C->getJobs().empty()) + FailingCommand = &*C->getJobs().begin(); + if (C && !C->containsError()) { + SmallVector, 4> FailingCommands; + Res = TheDriver.ExecuteCompilation(*C, FailingCommands); + + for (const auto &P : FailingCommands) { + int CommandRes = P.first; + FailingCommand = P.second; + if (!Res) + Res = CommandRes; + + // If result status is < 0, then the driver command signalled an error. + // If result status is 70, then the driver command reported a fatal error. + // On Windows, abort will return an exit code of 3. In these cases, + // generate additional diagnostic information if possible. + IsCrash = CommandRes < 0 || CommandRes == 70; +#ifdef _WIN32 + IsCrash |= CommandRes == 3; +#endif +#if LLVM_ON_UNIX + // When running in integrated-cc1 mode, the CrashRecoveryContext returns + // the same codes as if the program crashed. See section "Exit Status for + // Commands": + // https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html + IsCrash |= CommandRes > 128; +#endif + CommandStatus = + IsCrash ? Driver::CommandStatus::Crash : Driver::CommandStatus::Error; + if (IsCrash) + break; + } + } + + // Print the bug report message that would be printed if we did actually + // crash, but only if we're crashing due to FORCE_CLANG_DIAGNOSTICS_CRASH. + if (::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH")) + llvm::dbgs() << llvm::getBugReportMsg(); + if (FailingCommand != nullptr && + TheDriver.maybeGenerateCompilationDiagnostics(CommandStatus, ReproLevel, + *C, *FailingCommand)) + Res = 1; + + Diags.getClient()->finish(); + + if (!UseNewCC1Process && IsCrash) { + // When crashing in -fintegrated-cc1 mode, bury the timer pointers, because + // the internal linked list might point to already released stack frames. + llvm::BuryPointer(llvm::TimerGroup::aquireDefaultGroup()); + } else { + // If any timers were active but haven't been destroyed yet, print their + // results now. This happens in -disable-free mode. + llvm::TimerGroup::printAll(llvm::errs()); + llvm::TimerGroup::clearAll(); + } + +#ifdef _WIN32 + // Exit status should not be negative on Win32, unless abnormal termination. + // Once abnormal termination was caught, negative status should not be + // propagated. + if (Res < 0) + Res = 1; +#endif + + // If we have multiple failing commands, we return the result of the first + // failing command. + return Res; +} diff --git a/compiler+runtime/src/cpp/jank/c_api.cpp b/compiler+runtime/src/cpp/jank/c_api.cpp index 010731c6..b2797a8c 100644 --- a/compiler+runtime/src/cpp/jank/c_api.cpp +++ b/compiler+runtime/src/cpp/jank/c_api.cpp @@ -3,6 +3,30 @@ using namespace jank; using namespace jank::runtime; +template +struct make_closure_arity; + +template +struct make_closure_arity_arg +{ + using type = object *; +}; + +template +struct make_closure_arity> +{ + using type = object *(*)(void *, typename make_closure_arity_arg::type...); +}; + +template <> +struct make_closure_arity> +{ + using type = object *(*)(void *); +}; + +template +using closure_arity = typename make_closure_arity>::type; + extern "C" { jank_object_ptr jank_eval(jank_object_ptr const s) @@ -111,45 +135,74 @@ extern "C" jank_object_ptr jank_function_create(jank_arity_flags const arity_flags) { - fmt::println("jank_function_create"); - return erase(obj::nil::nil_const()); + return erase(make_box(arity_flags)); } - jank_object_ptr - jank_function_create_closure(jank_arity_flags const arity_flags, void * const context) + void jank_function_set_arity0(jank_object_ptr const fn, jank_object_ptr (* const f)()) { - fmt::println("jank_function_create_closure {}", fmt::ptr(context)); - return erase(obj::nil::nil_const()); } - jank_object_ptr jank_function_set_arity0(jank_object_ptr const fn, jank_object_ptr (* const f)()) + void + jank_function_set_arity1(jank_object_ptr const fn, jank_object_ptr (* const f)(jank_object_ptr)) { - fmt::println("jank_function_set_arity0"); - return nullptr; } - jank_object_ptr - jank_function_set_arity1(jank_object_ptr const fn, jank_object_ptr (* const f)(jank_object_ptr)) + void jank_function_set_arity2(jank_object_ptr const fn, + jank_object_ptr (* const f)(jank_object_ptr, jank_object_ptr)) { - fmt::println("jank_function_set_arity1"); - return nullptr; } - jank_object_ptr - jank_function_set_arity2(jank_object_ptr const fn, - jank_object_ptr (* const f)(jank_object_ptr, jank_object_ptr)) + void jank_function_set_arity3(jank_object_ptr const fn, + jank_object_ptr (* const f)(jank_object_ptr, + jank_object_ptr, + jank_object_ptr)) + { + } + + jank_object_ptr jank_closure_create(jank_arity_flags const arity_flags, void * const context) + { + return erase(make_box(arity_flags, context)); + } + + void jank_closure_set_arity0(jank_object_ptr const fn, jank_object_ptr (* const f)()) + { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type-mismatch" + auto const fn_obj(reinterpret_cast(fn)); + try_object(fn_obj)->arity_0 = reinterpret_cast>(f); +#pragma clang diagnostic pop + } + + void + jank_closure_set_arity1(jank_object_ptr const fn, jank_object_ptr (* const f)(jank_object_ptr)) + { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type-mismatch" + auto const fn_obj(reinterpret_cast(fn)); + try_object(fn_obj)->arity_1 = reinterpret_cast>(f); +#pragma clang diagnostic pop + } + + void jank_closure_set_arity2(jank_object_ptr const fn, + jank_object_ptr (* const f)(jank_object_ptr, jank_object_ptr)) { - fmt::println("jank_function_set_arity2"); - return nullptr; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type-mismatch" + auto const fn_obj(reinterpret_cast(fn)); + try_object(fn_obj)->arity_2 = reinterpret_cast>(f); +#pragma clang diagnostic pop } - jank_object_ptr jank_function_set_arity3(jank_object_ptr const fn, - jank_object_ptr (* const f)(jank_object_ptr, - jank_object_ptr, - jank_object_ptr)) + void jank_closure_set_arity3(jank_object_ptr const fn, + jank_object_ptr (* const f)(jank_object_ptr, + jank_object_ptr, + jank_object_ptr)) { - fmt::println("jank_function_set_arity3"); - return nullptr; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type-mismatch" + auto const fn_obj(reinterpret_cast(fn)); + try_object(fn_obj)->arity_3 = reinterpret_cast>(f); +#pragma clang diagnostic pop } jank_native_bool jank_truthy(jank_object_ptr const o) diff --git a/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp b/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp index 0128ae8d..4bc51e83 100644 --- a/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp +++ b/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp @@ -390,7 +390,9 @@ namespace jank::codegen llvm::Value *fn_obj{}; - if(captures.empty()) + auto const is_closure(!captures.empty()); + + if(!is_closure) { auto const create_fn_type( llvm::FunctionType::get(builder->getPtrTy(), { builder->getInt8Ty() }, false)); @@ -425,21 +427,24 @@ namespace jank::codegen llvm::FunctionType::get(builder->getPtrTy(), { builder->getInt8Ty(), builder->getPtrTy() }, false)); - auto const create_fn( - module->getOrInsertFunction("jank_function_create_closure", create_fn_type)); + auto const create_fn(module->getOrInsertFunction("jank_closure_create", create_fn_type)); fn_obj = builder->CreateCall(create_fn, { arity_flags, closure_obj }); } for(auto const &arity : expr.arities) { - std::vector const arg_types{ arity.params.size() + 1, builder->getPtrTy() }; - auto const set_arity_fn_type(llvm::FunctionType::get(builder->getPtrTy(), arg_types, false)); - auto const set_arity_fn( - module->getOrInsertFunction(fmt::format("jank_function_set_arity{}", arity.params.size()), - set_arity_fn_type)); + auto const set_arity_fn_type( + llvm::FunctionType::get(builder->getVoidTy(), + { builder->getPtrTy(), builder->getPtrTy() }, + false)); + auto const set_arity_fn(module->getOrInsertFunction( + is_closure ? fmt::format("jank_closure_set_arity{}", arity.params.size()) + : fmt::format("jank_function_set_arity{}", arity.params.size()), + set_arity_fn_type)); std::vector const target_arg_types{ arity.params.size(), builder->getPtrTy() }; - auto const target_fn_type(llvm::FunctionType::get(builder->getPtrTy(), arg_types, false)); + auto const target_fn_type( + llvm::FunctionType::get(builder->getPtrTy(), target_arg_types, false)); auto target_fn(module->getOrInsertFunction( fmt::format("{}_{}", munge(expr.unique_name), arity.params.size()), target_fn_type)); diff --git a/compiler+runtime/src/cpp/jank/runtime/obj/jit_closure.cpp b/compiler+runtime/src/cpp/jank/runtime/obj/jit_closure.cpp new file mode 100644 index 00000000..8368900a --- /dev/null +++ b/compiler+runtime/src/cpp/jank/runtime/obj/jit_closure.cpp @@ -0,0 +1,212 @@ +#include + +namespace jank::runtime +{ + obj::jit_closure::static_object(arity_flag_t const arity_flags) + { + } + + obj::jit_closure::static_object(arity_flag_t const arity_flags, void * const context) + : context{ context } + , arity_flags{ arity_flags } + { + } + + obj::jit_closure::static_object(object_ptr const meta) + : meta{ meta } + { + } + + native_bool obj::jit_closure::equal(object const &rhs) const + { + return &base == &rhs; + } + + native_persistent_string obj::jit_closure::to_string() + { + fmt::memory_buffer buff; + to_string(buff); + return native_persistent_string{ buff.data(), buff.size() }; + } + + void obj::jit_closure::to_string(fmt::memory_buffer &buff) + { + auto const name( + get(meta.unwrap_or(obj::nil::nil_const()), __rt_ctx->intern_keyword("name").expect_ok())); + fmt::format_to(std::back_inserter(buff), + "{} ({}@{})", + (name->type == object_type::nil + ? "unknown" + : expect_object(name)->data), + magic_enum::enum_name(base.type), + fmt::ptr(&base)); + } + + native_persistent_string obj::jit_closure::to_code_string() + { + return to_string(); + } + + native_hash obj::jit_closure::to_hash() const + { + return static_cast(reinterpret_cast(this)); + } + + obj::jit_closure_ptr obj::jit_closure::with_meta(object_ptr const m) + { + auto const new_meta(behavior::detail::validate_meta(m)); + meta = new_meta; + return this; + } + + object_ptr obj::jit_closure::call() + { + if(!arity_0) + { + throw invalid_arity<0>{ runtime::to_string(this_object_ptr()) }; + } + return arity_0(context); + } + + object_ptr obj::jit_closure::call(object_ptr const a1) + { + if(!arity_1) + { + throw invalid_arity<1>{ runtime::to_string(this_object_ptr()) }; + } + return arity_1(context, a1); + } + + object_ptr obj::jit_closure::call(object_ptr const a1, object_ptr const a2) + { + if(!arity_2) + { + throw invalid_arity<2>{ runtime::to_string(this_object_ptr()) }; + } + return arity_2(context, a1, a2); + } + + object_ptr obj::jit_closure::call(object_ptr const a1, object_ptr const a2, object_ptr const a3) + { + if(!arity_3) + { + throw invalid_arity<3>{ runtime::to_string(this_object_ptr()) }; + } + return arity_3(context, a1, a2, a3); + } + + object_ptr obj::jit_closure::call(object_ptr const a1, + object_ptr const a2, + object_ptr const a3, + object_ptr const a4) + { + if(!arity_4) + { + throw invalid_arity<4>{ runtime::to_string(this_object_ptr()) }; + } + return arity_4(context, a1, a2, a3, a4); + } + + object_ptr obj::jit_closure::call(object_ptr const a1, + object_ptr const a2, + object_ptr const a3, + object_ptr const a4, + object_ptr const a5) + { + if(!arity_5) + { + throw invalid_arity<5>{ runtime::to_string(this_object_ptr()) }; + } + return arity_5(context, a1, a2, a3, a4, a5); + } + + object_ptr obj::jit_closure::call(object_ptr const a1, + object_ptr const a2, + object_ptr const a3, + object_ptr const a4, + object_ptr const a5, + object_ptr const a6) + { + if(!arity_6) + { + throw invalid_arity<6>{ runtime::to_string(this_object_ptr()) }; + } + return arity_6(context, a1, a2, a3, a4, a5, a6); + } + + object_ptr obj::jit_closure::call(object_ptr const a1, + object_ptr const a2, + object_ptr const a3, + object_ptr const a4, + object_ptr const a5, + object_ptr const a6, + object_ptr const a7) + { + if(!arity_7) + { + throw invalid_arity<7>{ runtime::to_string(this_object_ptr()) }; + } + return arity_7(context, a1, a2, a3, a4, a5, a6, a7); + } + + object_ptr obj::jit_closure::call(object_ptr const a1, + object_ptr const a2, + object_ptr const a3, + object_ptr const a4, + object_ptr const a5, + object_ptr const a6, + object_ptr const a7, + object_ptr const a8) + { + if(!arity_8) + { + throw invalid_arity<8>{ runtime::to_string(this_object_ptr()) }; + } + return arity_8(context, a1, a2, a3, a4, a5, a6, a7, a8); + } + + object_ptr obj::jit_closure::call(object_ptr const a1, + object_ptr const a2, + object_ptr const a3, + object_ptr const a4, + object_ptr const a5, + object_ptr const a6, + object_ptr const a7, + object_ptr const a8, + object_ptr const a9) + { + if(!arity_9) + { + throw invalid_arity<9>{ runtime::to_string(this_object_ptr()) }; + } + return arity_9(context, a1, a2, a3, a4, a5, a6, a7, a8, a9); + } + + object_ptr obj::jit_closure::call(object_ptr const a1, + object_ptr const a2, + object_ptr const a3, + object_ptr const a4, + object_ptr const a5, + object_ptr const a6, + object_ptr const a7, + object_ptr const a8, + object_ptr const a9, + object_ptr const a10) + { + if(!arity_10) + { + throw invalid_arity<10>{ runtime::to_string(this_object_ptr()) }; + } + return arity_10(context, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); + } + + behavior::callable::arity_flag_t obj::jit_closure::get_arity_flags() const + { + return arity_flags; + } + + object_ptr obj::jit_closure::this_object_ptr() + { + return &this->base; + } +}