Files
scriptc/native/llvm-codegen/src/emit.cpp
Chris Tate c41f1a8be9 Expand LLVM native output targets (#280)
* Expand LLVM native output targets

- Add owned helper and runtime-pack contracts for macOS x64, Linux glibc/musl, Windows x64, and WASI.
- Generalize LLVM target selection, package validation, linker plans, and release/CI matrices.
- Document linker boundaries and add focused native-output and runtime-pack coverage.

* Pin Windows LLVM helper CI to VS 2022

The official LLVM archive needs the Visual Studio 2022 CMake generator, which is not guaranteed by windows-latest.

* Align bootstrap cache with LLVM runtime-pack builds

Use the same target-specific helper and linker identity before and after compiler loading so routed executable cache hits remain lightweight.

* Install Windows LLVM CI tools with Chocolatey

windows-2022 includes Visual Studio but not winget; use its available Chocolatey bootstrap for CMake and Ninja.

* Prevent Zig version probes from leaving runtime-pack objects

Run runtime-pack compiler version probes in a private temporary directory and remove the accidental tracked WASI a.o file.

* Use 7-Zip for Windows LLVM setup

Extract the official LLVM development archive with two-stage 7-Zip instead of Windows tar.exe, which timed out while materializing the toolchain tree.

* Define ssize_t for the MSVC runtime pack

Clang's MSVC target does not expose POSIX ssize_t through sys/types.h; define the pointer-sized runtime type without affecting MinGW.

* Build the Windows runtime pack with Zig

Use the MinGW-compatible Windows sysroot required by the runtime while retaining COFF helper output and installing Zig in the Windows native CI lane.

* Give macOS LLVM differential shard time to finish

Shard 1 passed its setup and focused contracts but was cancelled during its cold LLVM differential slice at the 20-minute job limit.
2026-09-01 13:41:34 -05:00

192 lines
6.6 KiB
C++

#include "emit.h"
#include "diagnostics.h"
#include "target.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Verifier.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/TargetParser/Triple.h"
#include <optional>
#include <string>
#include <system_error>
using namespace llvm;
namespace scriptc {
std::optional<EmitOptions> parseEmitOptions(int Argc, char **Argv) {
EmitOptions Options;
Options.Target = DefaultTarget.str();
for (int I = 2; I < Argc; ++I) {
StringRef Arg(Argv[I]);
if (!Arg.starts_with("--") || I + 1 >= Argc)
return std::nullopt;
StringRef Value(Argv[++I]);
if (Arg == "--input")
Options.Input = Value.str();
else if (Arg == "--output")
Options.Output = Value.str();
else if (Arg == "--filetype")
Options.FileType = Value.str();
else if (Arg == "--target")
Options.Target = Value.str();
else if (Arg == "--opt-level")
Options.OptLevel = Value.str();
else if (Arg == "--relocation-model")
Options.RelocationModel = Value.str();
else if (Arg == "--diagnostic-format")
Options.DiagnosticFormat = Value.str();
else if (Arg == "--source-path")
Options.SourcePath = Value.str();
else
return std::nullopt;
}
if (Options.Input.empty() || Options.Output.empty())
return std::nullopt;
return Options;
}
static OptimizationLevel optimizationLevel(StringRef Level) {
if (Level == "0")
return OptimizationLevel::O0;
if (Level == "1")
return OptimizationLevel::O1;
if (Level == "3")
return OptimizationLevel::O3;
if (Level == "s")
return OptimizationLevel::Os;
if (Level == "z")
return OptimizationLevel::Oz;
return OptimizationLevel::O2;
}
int emit(const EmitOptions &Options) {
if (!supportsTarget(Options.Target))
return reportError("unsupported_target",
Twine("unsupported target '") + Options.Target +
"' (supported: " + AllowedTargets + ")",
Options.DiagnosticFormat);
if (Options.FileType != "obj" && Options.FileType != "asm")
return reportError("invalid_filetype", "filetype must be obj or asm",
Options.DiagnosticFormat);
if (Options.OptLevel != "0" && Options.OptLevel != "1" &&
Options.OptLevel != "2" && Options.OptLevel != "3" &&
Options.OptLevel != "s" && Options.OptLevel != "z")
return reportError("invalid_opt_level",
"opt-level must be 0, 1, 2, 3, s, or z",
Options.DiagnosticFormat);
if (Options.RelocationModel != "pic")
return reportError("invalid_relocation_model",
"only the pic relocation model is supported",
Options.DiagnosticFormat);
SMDiagnostic ParseDiagnostic;
LLVMContext Context;
std::unique_ptr<Module> Mod =
parseIRFile(Options.Input, ParseDiagnostic, Context);
if (!Mod) {
std::string Detail;
raw_string_ostream Stream(Detail);
ParseDiagnostic.print("scriptc-llvm-codegen", Stream);
return reportError("invalid_ir", Stream.str(), Options.DiagnosticFormat);
}
if (!Options.SourcePath.empty())
Mod->setSourceFileName(Options.SourcePath);
std::string LookupError;
std::unique_ptr<TargetMachine> Machine =
createTargetMachine(Options.Target, Options.OptLevel, LookupError);
if (!Machine)
return reportError("target_machine_failed", LookupError,
Options.DiagnosticFormat);
Triple TargetTriple(Options.Target);
Mod->setTargetTriple(TargetTriple);
Mod->setDataLayout(Machine->createDataLayout());
std::string VerificationError;
raw_string_ostream VerificationStream(VerificationError);
if (verifyModule(*Mod, &VerificationStream))
return reportError("verification_failed", VerificationStream.str(),
Options.DiagnosticFormat);
LoopAnalysisManager LAM;
FunctionAnalysisManager FAM;
CGSCCAnalysisManager CGAM;
ModuleAnalysisManager MAM;
PassBuilder PB(Machine.get());
PB.registerModuleAnalyses(MAM);
PB.registerCGSCCAnalyses(CGAM);
PB.registerFunctionAnalyses(FAM);
PB.registerLoopAnalyses(LAM);
PB.crossRegisterProxies(LAM, FAM, CGAM, MAM);
ModulePassManager Optimizations =
PB.buildPerModuleDefaultPipeline(optimizationLevel(Options.OptLevel));
Optimizations.run(*Mod, MAM);
VerificationError.clear();
if (verifyModule(*Mod, &VerificationStream))
return reportError("post_optimization_verification_failed",
VerificationStream.str(), Options.DiagnosticFormat);
SmallString<256> OutputPath(Options.Output);
SmallString<256> TemporaryPath(OutputPath);
TemporaryPath.append(".tmp-%%%%%%");
int TemporaryFd = -1;
if (std::error_code EC =
sys::fs::createUniqueFile(TemporaryPath, TemporaryFd, TemporaryPath))
return reportError("output_open_failed", EC.message(),
Options.DiagnosticFormat);
{
raw_fd_ostream Output(TemporaryFd, true);
legacy::PassManager CodeGeneration;
CodeGenFileType Type = Options.FileType == "obj"
? CodeGenFileType::ObjectFile
: CodeGenFileType::AssemblyFile;
if (Machine->addPassesToEmitFile(CodeGeneration, Output, nullptr, Type)) {
sys::fs::remove(TemporaryPath);
return reportError("emission_not_supported",
"target does not support the requested file type",
Options.DiagnosticFormat);
}
CodeGeneration.run(*Mod);
Output.flush();
if (Output.has_error()) {
std::error_code EC = Output.error();
sys::fs::remove(TemporaryPath);
return reportError("output_write_failed", EC.message(),
Options.DiagnosticFormat);
}
}
uint64_t Size = 0;
if (std::error_code EC = sys::fs::file_size(TemporaryPath, Size)) {
sys::fs::remove(TemporaryPath);
return reportError("output_verify_failed", EC.message(),
Options.DiagnosticFormat);
}
if (Size == 0) {
sys::fs::remove(TemporaryPath);
return reportError("output_verify_failed", "LLVM emitted an empty file",
Options.DiagnosticFormat);
}
if (std::error_code EC = sys::fs::rename(TemporaryPath, OutputPath)) {
sys::fs::remove(TemporaryPath);
return reportError("output_publish_failed", EC.message(),
Options.DiagnosticFormat);
}
return 0;
}
} // namespace scriptc