Files
scriptc/native/llvm-codegen/src/target.cpp
T
Chris Tate 3ea231d70d Optimize and generate native code in parallel partitions
Large executables were simplified and inlined as one module before the
optimizer split them, so most of a release build ran on a single core,
and any edit recompiled the whole program.

The LLVM helper now divides the program before optimization. It splits
the emitter's assembly text directly, so partitions also parse
concurrently, and falls back to LLVM's module splitter for any input
outside the emitter's grammar. Internal functions referenced by a single
function stay with that function, and globals stay with their first user.
Optimized partitions follow the ThinLTO model: each is simplified and
summarized on its own thread, a thin link imports small functions across
partitions for inlining and returns unexported hidden definitions to
internal linkage, then each partition is optimized and compiled on its
own thread. Unoptimized partitions compile independently, so dev builds
of large programs are parallel too.

A private per-helper cache directory stores simplified partitions under
the hash of their source and objects under the hash of the module after
importing, so a rebuild recompiles only the partitions an edit can
affect. To keep unchanged partitions identical across edits, each
partition numbers its metadata in first-use order, lists only the debug
compile units it uses, and orders declarations by name, and interned
string constants are named by their content rather than first-use order.

Partition counts are powers of two that depend only on the module size,
up to 64, so output stays the same on every host and functions keep
their partition across ordinary edits. LLVM text that arrives in parts is
written in large batches and handed to the helper in place instead of
being written a second time.
2026-10-09 19:51:47 -05:00

148 lines
4.4 KiB
C++

#include "target.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/CodeGen.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Target/TargetOptions.h"
#include "llvm/TargetParser/Triple.h"
using namespace llvm;
#if defined(SCRIPTC_ENABLE_AARCH64)
extern "C" {
void LLVMInitializeAArch64TargetInfo();
void LLVMInitializeAArch64Target();
void LLVMInitializeAArch64TargetMC();
void LLVMInitializeAArch64AsmPrinter();
void LLVMInitializeAArch64AsmParser();
}
#endif
#if defined(SCRIPTC_ENABLE_X86)
extern "C" {
void LLVMInitializeX86TargetInfo();
void LLVMInitializeX86Target();
void LLVMInitializeX86TargetMC();
void LLVMInitializeX86AsmPrinter();
void LLVMInitializeX86AsmParser();
}
#endif
#if defined(SCRIPTC_ENABLE_WEBASSEMBLY)
extern "C" {
void LLVMInitializeWebAssemblyTargetInfo();
void LLVMInitializeWebAssemblyTarget();
void LLVMInitializeWebAssemblyTargetMC();
void LLVMInitializeWebAssemblyAsmPrinter();
void LLVMInitializeWebAssemblyAsmParser();
}
#endif
namespace scriptc {
static bool registerTargets() {
#if defined(SCRIPTC_ENABLE_AARCH64)
LLVMInitializeAArch64TargetInfo();
LLVMInitializeAArch64Target();
LLVMInitializeAArch64TargetMC();
LLVMInitializeAArch64AsmPrinter();
// Runtime units may contain inline assembly (runtime-unit emission).
LLVMInitializeAArch64AsmParser();
#endif
#if defined(SCRIPTC_ENABLE_X86)
LLVMInitializeX86TargetInfo();
LLVMInitializeX86Target();
LLVMInitializeX86TargetMC();
LLVMInitializeX86AsmPrinter();
// Runtime units may contain inline assembly (runtime-unit emission).
LLVMInitializeX86AsmParser();
#endif
#if defined(SCRIPTC_ENABLE_WEBASSEMBLY)
LLVMInitializeWebAssemblyTargetInfo();
LLVMInitializeWebAssemblyTarget();
LLVMInitializeWebAssemblyTargetMC();
LLVMInitializeWebAssemblyAsmPrinter();
// Runtime units may contain inline assembly (runtime-unit emission).
LLVMInitializeWebAssemblyAsmParser();
#endif
return true;
}
// Partitions create target machines on several threads; the registry must be
// populated exactly once before any lookup.
void initializeTargets() {
static const bool Registered = registerTargets();
(void)Registered;
}
bool supportsTarget(StringRef TripleName) {
bool Allowed = false;
StringRef Remaining = AllowedTargets;
while (!Remaining.empty()) {
auto Split = Remaining.split(',');
if (Split.first == TripleName) {
Allowed = true;
break;
}
Remaining = Split.second;
}
if (!Allowed)
return false;
Triple Triple(TripleName);
switch (Triple.getArch()) {
#if defined(SCRIPTC_ENABLE_AARCH64)
case Triple::aarch64:
case Triple::aarch64_be:
return Triple.isOSBinFormatMachO() || Triple.isOSBinFormatELF();
#endif
#if defined(SCRIPTC_ENABLE_X86)
case Triple::x86_64:
case Triple::x86:
return Triple.isOSBinFormatMachO() || Triple.isOSBinFormatELF() ||
Triple.isOSBinFormatCOFF();
#endif
#if defined(SCRIPTC_ENABLE_WEBASSEMBLY)
case Triple::wasm32:
return Triple.isOSBinFormatWasm();
#endif
default:
return false;
}
}
CodeGenOptLevel codeGenLevel(StringRef Level) {
if (Level == "0")
return CodeGenOptLevel::None;
if (Level == "1")
return CodeGenOptLevel::Less;
if (Level == "3")
return CodeGenOptLevel::Aggressive;
return CodeGenOptLevel::Default;
}
TargetOptions targetOptions(const Triple &TargetTriple) {
TargetOptions Options;
// The Android API 26 runtime pack uses the NDK's emulated TLS ABI.
// Program globals and runtime thread instances must use the same ABI.
Options.EmulatedTLS = TargetTriple.isAndroid();
return Options;
}
std::unique_ptr<TargetMachine> createTargetMachine(StringRef TripleName,
StringRef OptLevel,
std::string &Error,
bool RequireAllowed) {
initializeTargets();
if (RequireAllowed && !supportsTarget(TripleName)) {
Error = (Twine("target '") + TripleName + "' was not compiled into this helper").str();
return nullptr;
}
Triple TargetTriple(TripleName);
const Target *Definition = TargetRegistry::lookupTarget(TargetTriple, Error);
if (Definition == nullptr)
return nullptr;
return std::unique_ptr<TargetMachine>(Definition->createTargetMachine(
TargetTriple, "generic", "", targetOptions(TargetTriple), Reloc::PIC_,
CodeModel::Small, codeGenLevel(OptLevel)));
}
} // namespace scriptc