Clean up FFI calls for setting module flags

- Don't rely on enum values defined by LLVM's C++ API
- Use safe wrapper functions instead of direct `unsafe` calls
- Consistently pass pointer/length strings instead of C strings
This commit is contained in:
Zalathar 2024-10-29 13:38:17 +11:00
parent ba81dbf3c6
commit 8d2ed4f0f3
5 changed files with 242 additions and 212 deletions

View File

@ -210,133 +210,111 @@ pub(crate) unsafe fn create_module<'ll>(
// If skipping the PLT is enabled, we need to add some module metadata
// to ensure intrinsic calls don't use it.
if !sess.needs_plt() {
let avoid_plt = c"RtLibUseGOT".as_ptr();
unsafe {
llvm::LLVMRustAddModuleFlagU32(llmod, llvm::LLVMModFlagBehavior::Warning, avoid_plt, 1);
}
llvm::add_module_flag_u32(llmod, llvm::ModuleFlagMergeBehavior::Warning, "RtLibUseGOT", 1);
}
// Enable canonical jump tables if CFI is enabled. (See https://reviews.llvm.org/D65629.)
if sess.is_sanitizer_cfi_canonical_jump_tables_enabled() && sess.is_sanitizer_cfi_enabled() {
let canonical_jump_tables = c"CFI Canonical Jump Tables".as_ptr();
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Override,
canonical_jump_tables,
llvm::ModuleFlagMergeBehavior::Override,
"CFI Canonical Jump Tables",
1,
);
}
}
// If we're normalizing integers with CFI, ensure LLVM generated functions do the same.
// See https://github.com/llvm/llvm-project/pull/104826
if sess.is_sanitizer_cfi_normalize_integers_enabled() {
let cfi_normalize_integers = c"cfi-normalize-integers".as_ptr();
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Override,
cfi_normalize_integers,
llvm::ModuleFlagMergeBehavior::Override,
"cfi-normalize-integers",
1,
);
}
}
// Enable LTO unit splitting if specified or if CFI is enabled. (See
// https://reviews.llvm.org/D53891.)
if sess.is_split_lto_unit_enabled() || sess.is_sanitizer_cfi_enabled() {
let enable_split_lto_unit = c"EnableSplitLTOUnit".as_ptr();
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Override,
enable_split_lto_unit,
llvm::ModuleFlagMergeBehavior::Override,
"EnableSplitLTOUnit",
1,
);
}
}
// Add "kcfi" module flag if KCFI is enabled. (See https://reviews.llvm.org/D119296.)
if sess.is_sanitizer_kcfi_enabled() {
let kcfi = c"kcfi".as_ptr();
unsafe {
llvm::LLVMRustAddModuleFlagU32(llmod, llvm::LLVMModFlagBehavior::Override, kcfi, 1);
}
llvm::add_module_flag_u32(llmod, llvm::ModuleFlagMergeBehavior::Override, "kcfi", 1);
// Add "kcfi-offset" module flag with -Z patchable-function-entry (See
// https://reviews.llvm.org/D141172).
let pfe =
PatchableFunctionEntry::from_config(sess.opts.unstable_opts.patchable_function_entry);
if pfe.prefix() > 0 {
let kcfi_offset = c"kcfi-offset".as_ptr();
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Override,
kcfi_offset,
llvm::ModuleFlagMergeBehavior::Override,
"kcfi-offset",
pfe.prefix().into(),
);
}
}
}
// Control Flow Guard is currently only supported by the MSVC linker on Windows.
if sess.target.is_like_msvc {
unsafe {
match sess.opts.cg.control_flow_guard {
CFGuard::Disabled => {}
CFGuard::NoChecks => {
// Set `cfguard=1` module flag to emit metadata only.
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Warning,
c"cfguard".as_ptr() as *const _,
llvm::ModuleFlagMergeBehavior::Warning,
"cfguard",
1,
)
);
}
CFGuard::Checks => {
// Set `cfguard=2` module flag to emit metadata and checks.
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Warning,
c"cfguard".as_ptr() as *const _,
llvm::ModuleFlagMergeBehavior::Warning,
"cfguard",
2,
)
}
);
}
}
}
if let Some(BranchProtection { bti, pac_ret }) = sess.opts.unstable_opts.branch_protection {
if sess.target.arch == "aarch64" {
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Min,
c"branch-target-enforcement".as_ptr(),
llvm::ModuleFlagMergeBehavior::Min,
"branch-target-enforcement",
bti.into(),
);
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Min,
c"sign-return-address".as_ptr(),
llvm::ModuleFlagMergeBehavior::Min,
"sign-return-address",
pac_ret.is_some().into(),
);
let pac_opts = pac_ret.unwrap_or(PacRet { leaf: false, key: PAuthKey::A });
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Min,
c"sign-return-address-all".as_ptr(),
llvm::ModuleFlagMergeBehavior::Min,
"sign-return-address-all",
pac_opts.leaf.into(),
);
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Min,
c"sign-return-address-with-bkey".as_ptr(),
llvm::ModuleFlagMergeBehavior::Min,
"sign-return-address-with-bkey",
u32::from(pac_opts.key == PAuthKey::B),
);
}
} else {
bug!(
"branch-protection used on non-AArch64 target; \
@ -347,59 +325,46 @@ pub(crate) unsafe fn create_module<'ll>(
// Pass on the control-flow protection flags to LLVM (equivalent to `-fcf-protection` in Clang).
if let CFProtection::Branch | CFProtection::Full = sess.opts.unstable_opts.cf_protection {
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Override,
c"cf-protection-branch".as_ptr(),
llvm::ModuleFlagMergeBehavior::Override,
"cf-protection-branch",
1,
);
}
}
if let CFProtection::Return | CFProtection::Full = sess.opts.unstable_opts.cf_protection {
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Override,
c"cf-protection-return".as_ptr(),
llvm::ModuleFlagMergeBehavior::Override,
"cf-protection-return",
1,
);
}
}
if sess.opts.unstable_opts.virtual_function_elimination {
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Error,
c"Virtual Function Elim".as_ptr(),
llvm::ModuleFlagMergeBehavior::Error,
"Virtual Function Elim",
1,
);
}
}
// Set module flag to enable Windows EHCont Guard (/guard:ehcont).
if sess.opts.unstable_opts.ehcont_guard {
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llmod,
llvm::LLVMModFlagBehavior::Warning,
c"ehcontguard".as_ptr() as *const _,
1,
)
}
llvm::add_module_flag_u32(llmod, llvm::ModuleFlagMergeBehavior::Warning, "ehcontguard", 1);
}
match sess.opts.unstable_opts.function_return {
FunctionReturn::Keep => {}
FunctionReturn::ThunkExtern => unsafe {
llvm::LLVMRustAddModuleFlagU32(
FunctionReturn::ThunkExtern => {
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Override,
c"function_return_thunk_extern".as_ptr(),
llvm::ModuleFlagMergeBehavior::Override,
"function_return_thunk_extern",
1,
)
},
);
}
}
match (sess.opts.unstable_opts.small_data_threshold, sess.target.small_data_threshold_support())
@ -407,15 +372,12 @@ pub(crate) unsafe fn create_module<'ll>(
// Set up the small-data optimization limit for architectures that use
// an LLVM module flag to control this.
(Some(threshold), SmallDataThresholdSupport::LlvmModuleFlag(flag)) => {
let flag = SmallCStr::new(flag.as_ref());
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
llmod,
llvm::LLVMModFlagBehavior::Error,
flag.as_c_str().as_ptr(),
llvm::ModuleFlagMergeBehavior::Error,
&flag,
threshold as u32,
)
}
);
}
_ => (),
};
@ -449,33 +411,29 @@ pub(crate) unsafe fn create_module<'ll>(
// If llvm_abiname is empty, emit nothing.
let llvm_abiname = &sess.target.options.llvm_abiname;
if matches!(sess.target.arch.as_ref(), "riscv32" | "riscv64") && !llvm_abiname.is_empty() {
unsafe {
llvm::LLVMRustAddModuleFlagString(
llvm::add_module_flag_str(
llmod,
llvm::LLVMModFlagBehavior::Error,
c"target-abi".as_ptr(),
llvm_abiname.as_c_char_ptr(),
llvm_abiname.len(),
llvm::ModuleFlagMergeBehavior::Error,
"target-abi",
llvm_abiname,
);
}
}
// Add module flags specified via -Z llvm_module_flag
for (key, value, behavior) in &sess.opts.unstable_opts.llvm_module_flag {
let key = format!("{key}\0");
let behavior = match behavior.as_str() {
"error" => llvm::LLVMModFlagBehavior::Error,
"warning" => llvm::LLVMModFlagBehavior::Warning,
"require" => llvm::LLVMModFlagBehavior::Require,
"override" => llvm::LLVMModFlagBehavior::Override,
"append" => llvm::LLVMModFlagBehavior::Append,
"appendunique" => llvm::LLVMModFlagBehavior::AppendUnique,
"max" => llvm::LLVMModFlagBehavior::Max,
"min" => llvm::LLVMModFlagBehavior::Min,
for (key, value, merge_behavior) in &sess.opts.unstable_opts.llvm_module_flag {
let merge_behavior = match merge_behavior.as_str() {
"error" => llvm::ModuleFlagMergeBehavior::Error,
"warning" => llvm::ModuleFlagMergeBehavior::Warning,
"require" => llvm::ModuleFlagMergeBehavior::Require,
"override" => llvm::ModuleFlagMergeBehavior::Override,
"append" => llvm::ModuleFlagMergeBehavior::Append,
"appendunique" => llvm::ModuleFlagMergeBehavior::AppendUnique,
"max" => llvm::ModuleFlagMergeBehavior::Max,
"min" => llvm::ModuleFlagMergeBehavior::Min,
// We already checked this during option parsing
_ => unreachable!(),
};
unsafe { llvm::LLVMRustAddModuleFlagU32(llmod, behavior, key.as_c_char_ptr(), *value) }
llvm::add_module_flag_u32(llmod, merge_behavior, key, *value);
}
llmod

View File

@ -91,9 +91,7 @@ pub(crate) fn new(llmod: &'ll llvm::Module) -> Self {
}
pub(crate) fn finalize(&self, sess: &Session) {
unsafe {
llvm::LLVMRustDIBuilderFinalize(self.builder);
unsafe { llvm::LLVMRustDIBuilderFinalize(self.builder) };
if !sess.target.is_like_msvc {
// Debuginfo generation in LLVM by default uses a higher
// version of dwarf than macOS currently understands. We can
@ -101,37 +99,33 @@ pub(crate) fn finalize(&self, sess: &Session) {
// for macOS to understand. For more info see #11352
// This can be overridden using --llvm-opts -dwarf-version,N.
// Android has the same issue (#22398)
let dwarf_version = sess
.opts
.unstable_opts
.dwarf_version
.unwrap_or(sess.target.default_dwarf_version);
llvm::LLVMRustAddModuleFlagU32(
let dwarf_version =
sess.opts.unstable_opts.dwarf_version.unwrap_or(sess.target.default_dwarf_version);
llvm::add_module_flag_u32(
self.llmod,
llvm::LLVMModFlagBehavior::Warning,
c"Dwarf Version".as_ptr(),
llvm::ModuleFlagMergeBehavior::Warning,
"Dwarf Version",
dwarf_version,
);
} else {
// Indicate that we want CodeView debug information on MSVC
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
self.llmod,
llvm::LLVMModFlagBehavior::Warning,
c"CodeView".as_ptr(),
llvm::ModuleFlagMergeBehavior::Warning,
"CodeView",
1,
)
);
}
// Prevent bitcode readers from deleting the debug info.
llvm::LLVMRustAddModuleFlagU32(
llvm::add_module_flag_u32(
self.llmod,
llvm::LLVMModFlagBehavior::Warning,
c"Debug Info Version".as_ptr(),
llvm::LLVMRustDebugMetadataVersion(),
llvm::ModuleFlagMergeBehavior::Warning,
"Debug Info Version",
unsafe { llvm::LLVMRustDebugMetadataVersion() },
);
}
}
}
/// Creates any deferred debug metadata nodes
pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) {

View File

@ -85,7 +85,7 @@ pub enum LLVMMachineType {
ARM = 0x01c0,
}
/// LLVM's Module::ModFlagBehavior, defined in llvm/include/llvm/IR/Module.h.
/// Must match the layout of `LLVMRustModuleFlagMergeBehavior`.
///
/// When merging modules (e.g. during LTO), their metadata flags are combined. Conflicts are
/// resolved according to the merge behaviors specified here. Flags differing only in merge
@ -93,9 +93,13 @@ pub enum LLVMMachineType {
///
/// In order for Rust-C LTO to work, we must specify behaviors compatible with Clang. Notably,
/// 'Error' and 'Warning' cannot be mixed for a given flag.
///
/// There is a stable LLVM-C version of this enum (`LLVMModuleFlagBehavior`),
/// but as of LLVM 19 it does not support all of the enum values in the unstable
/// C++ API.
#[derive(Copy, Clone, PartialEq)]
#[repr(C)]
pub enum LLVMModFlagBehavior {
pub enum ModuleFlagMergeBehavior {
Error = 1,
Warning = 2,
Require = 3,
@ -1829,17 +1833,19 @@ pub(crate) fn LLVMRustCoverageCreatePGOFuncNameVar(
/// "compatible" means depends on the merge behaviors involved.
pub fn LLVMRustAddModuleFlagU32(
M: &Module,
merge_behavior: LLVMModFlagBehavior,
name: *const c_char,
value: u32,
MergeBehavior: ModuleFlagMergeBehavior,
Name: *const c_char,
NameLen: size_t,
Value: u32,
);
pub fn LLVMRustAddModuleFlagString(
M: &Module,
merge_behavior: LLVMModFlagBehavior,
name: *const c_char,
value: *const c_char,
value_len: size_t,
MergeBehavior: ModuleFlagMergeBehavior,
Name: *const c_char,
NameLen: size_t,
Value: *const c_char,
ValueLen: size_t,
);
pub fn LLVMRustDIBuilderCreate(M: &Module) -> &mut DIBuilder<'_>;

View File

@ -352,3 +352,32 @@ fn drop(&mut self) {
}
}
}
pub(crate) fn add_module_flag_u32(
module: &Module,
merge_behavior: ModuleFlagMergeBehavior,
key: &str,
value: u32,
) {
unsafe {
LLVMRustAddModuleFlagU32(module, merge_behavior, key.as_c_char_ptr(), key.len(), value);
}
}
pub(crate) fn add_module_flag_str(
module: &Module,
merge_behavior: ModuleFlagMergeBehavior,
key: &str,
value: &str,
) {
unsafe {
LLVMRustAddModuleFlagString(
module,
merge_behavior,
key.as_c_char_ptr(),
key.len(),
value.as_c_char_ptr(),
value.len(),
);
}
}

View File

@ -853,17 +853,60 @@ extern "C" uint32_t LLVMRustVersionMinor() { return LLVM_VERSION_MINOR; }
extern "C" uint32_t LLVMRustVersionMajor() { return LLVM_VERSION_MAJOR; }
extern "C" void LLVMRustAddModuleFlagU32(LLVMModuleRef M,
Module::ModFlagBehavior MergeBehavior,
const char *Name, uint32_t Value) {
unwrap(M)->addModuleFlag(MergeBehavior, Name, Value);
// FFI equivalent of LLVM's `llvm::Module::ModFlagBehavior`.
// Must match the layout of
// `rustc_codegen_llvm::llvm::ffi::ModuleFlagMergeBehavior`.
//
// There is a stable LLVM-C version of this enum (`LLVMModuleFlagBehavior`),
// but as of LLVM 19 it does not support all of the enum values in the unstable
// C++ API.
enum class LLVMRustModuleFlagMergeBehavior {
Error = 1,
Warning = 2,
Require = 3,
Override = 4,
Append = 5,
AppendUnique = 6,
Max = 7,
Min = 8,
};
static Module::ModFlagBehavior
fromRust(LLVMRustModuleFlagMergeBehavior Behavior) {
switch (Behavior) {
case LLVMRustModuleFlagMergeBehavior::Error:
return Module::ModFlagBehavior::Error;
case LLVMRustModuleFlagMergeBehavior::Warning:
return Module::ModFlagBehavior::Warning;
case LLVMRustModuleFlagMergeBehavior::Require:
return Module::ModFlagBehavior::Require;
case LLVMRustModuleFlagMergeBehavior::Override:
return Module::ModFlagBehavior::Override;
case LLVMRustModuleFlagMergeBehavior::Append:
return Module::ModFlagBehavior::Append;
case LLVMRustModuleFlagMergeBehavior::AppendUnique:
return Module::ModFlagBehavior::AppendUnique;
case LLVMRustModuleFlagMergeBehavior::Max:
return Module::ModFlagBehavior::Max;
case LLVMRustModuleFlagMergeBehavior::Min:
return Module::ModFlagBehavior::Min;
}
report_fatal_error("bad LLVMRustModuleFlagMergeBehavior");
}
extern "C" void
LLVMRustAddModuleFlagU32(LLVMModuleRef M,
LLVMRustModuleFlagMergeBehavior MergeBehavior,
const char *Name, size_t NameLen, uint32_t Value) {
unwrap(M)->addModuleFlag(fromRust(MergeBehavior), StringRef(Name, NameLen),
Value);
}
extern "C" void LLVMRustAddModuleFlagString(
LLVMModuleRef M, Module::ModFlagBehavior MergeBehavior, const char *Name,
const char *Value, size_t ValueLen) {
LLVMModuleRef M, LLVMRustModuleFlagMergeBehavior MergeBehavior,
const char *Name, size_t NameLen, const char *Value, size_t ValueLen) {
unwrap(M)->addModuleFlag(
MergeBehavior, Name,
fromRust(MergeBehavior), StringRef(Name, NameLen),
MDString::get(unwrap(M)->getContext(), StringRef(Value, ValueLen)));
}