about summary refs log tree commit diff
path: root/compiler/rustc_codegen_gcc/src/attributes.rs
blob: 7a1ae6ca9c8b7c7d342361eb22b8cd50acc23f48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#[cfg(feature = "master")]
use gccjit::FnAttribute;
use gccjit::Function;
#[cfg(feature = "master")]
use rustc_attr_data_structures::InlineAttr;
use rustc_attr_data_structures::InstructionSetAttr;
#[cfg(feature = "master")]
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
#[cfg(feature = "master")]
use rustc_middle::mir::TerminatorKind;
use rustc_middle::ty;

use crate::context::CodegenCx;
use crate::gcc_util::to_gcc_features;

/// Checks if the function `instance` is recursively inline.
/// Returns `false` if a functions is guaranteed to be non-recursive, and `true` if it *might* be recursive.
#[cfg(feature = "master")]
fn recursively_inline<'gcc, 'tcx>(
    cx: &CodegenCx<'gcc, 'tcx>,
    instance: ty::Instance<'tcx>,
) -> bool {
    // No body, so we can't check if this is recursively inline, so we assume it is.
    if !cx.tcx.is_mir_available(instance.def_id()) {
        return true;
    }
    // `expect_local` ought to never fail: we should be checking a function within this codegen unit.
    let body = cx.tcx.optimized_mir(instance.def_id());
    for block in body.basic_blocks.iter() {
        let Some(ref terminator) = block.terminator else { continue };
        // I assume that the recursive-inline issue applies only to functions, and not to drops.
        // In principle, a recursive, `#[inline(always)]` drop could(?) exist, but I don't think it does.
        let TerminatorKind::Call { ref func, .. } = terminator.kind else { continue };
        let Some((def, _args)) = func.const_fn_def() else { continue };
        // Check if the called function is recursively inline.
        if matches!(
            cx.tcx.codegen_fn_attrs(def).inline,
            InlineAttr::Always | InlineAttr::Force { .. }
        ) {
            return true;
        }
    }
    false
}

/// Get GCC attribute for the provided inline heuristic, attached to `instance`.
#[cfg(feature = "master")]
#[inline]
fn inline_attr<'gcc, 'tcx>(
    cx: &CodegenCx<'gcc, 'tcx>,
    inline: InlineAttr,
    instance: ty::Instance<'tcx>,
) -> Option<FnAttribute<'gcc>> {
    match inline {
        InlineAttr::Always => {
            // We can't simply always return `always_inline` unconditionally.
            // It is *NOT A HINT* and does not work for recursive functions.
            //
            // So, it can only be applied *if*:
            // The current function does not call any functions marked `#[inline(always)]`.
            //
            // That prevents issues steming from recursive `#[inline(always)]` at a *relatively* small cost.
            // We *only* need to check all the terminators of a function marked with this attribute.
            if recursively_inline(cx, instance) {
                Some(FnAttribute::Inline)
            } else {
                Some(FnAttribute::AlwaysInline)
            }
        }
        InlineAttr::Hint => Some(FnAttribute::Inline),
        InlineAttr::Force { .. } => Some(FnAttribute::AlwaysInline),
        InlineAttr::Never => {
            if cx.sess().target.arch != "amdgpu" {
                Some(FnAttribute::NoInline)
            } else {
                None
            }
        }
        InlineAttr::None => None,
    }
}

/// Composite function which sets GCC attributes for function depending on its AST (`#[attribute]`)
/// attributes.
pub fn from_fn_attrs<'gcc, 'tcx>(
    cx: &CodegenCx<'gcc, 'tcx>,
    #[cfg_attr(not(feature = "master"), allow(unused_variables))] func: Function<'gcc>,
    instance: ty::Instance<'tcx>,
) {
    let codegen_fn_attrs = cx.tcx.codegen_instance_attrs(instance.def);

    #[cfg(feature = "master")]
    {
        let inline = if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED) {
            InlineAttr::Never
        } else if codegen_fn_attrs.inline == InlineAttr::None
            && instance.def.requires_inline(cx.tcx)
        {
            InlineAttr::Hint
        } else {
            codegen_fn_attrs.inline
        };
        if let Some(attr) = inline_attr(cx, inline, instance) {
            if let FnAttribute::AlwaysInline = attr {
                func.add_attribute(FnAttribute::Inline);
            }
            func.add_attribute(attr);
        }

        if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::COLD) {
            func.add_attribute(FnAttribute::Cold);
        }
        if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::FFI_PURE) {
            func.add_attribute(FnAttribute::Pure);
        }
        if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::FFI_CONST) {
            func.add_attribute(FnAttribute::Const);
        }
    }

    let mut function_features = codegen_fn_attrs
        .target_features
        .iter()
        .map(|features| features.name.as_str())
        .flat_map(|feat| to_gcc_features(cx.tcx.sess, feat).into_iter())
        .chain(codegen_fn_attrs.instruction_set.iter().map(|x| match *x {
            InstructionSetAttr::ArmA32 => "-thumb-mode", // TODO(antoyo): support removing feature.
            InstructionSetAttr::ArmT32 => "thumb-mode",
        }))
        .collect::<Vec<_>>();

    // TODO(antoyo): cg_llvm adds global features to each function so that LTO keep them.
    // Check if GCC requires the same.
    let mut global_features = cx.tcx.global_backend_features(()).iter().map(|s| s.as_str());
    function_features.extend(&mut global_features);
    let target_features = function_features
        .iter()
        .filter_map(|feature| {
            // TODO(antoyo): support soft-float.
            if feature.contains("soft-float") {
                return None;
            }

            if feature.starts_with('-') {
                Some(format!("no{}", feature))
            } else if let Some(stripped) = feature.strip_prefix('+') {
                Some(stripped.to_string())
            } else {
                Some(feature.to_string())
            }
        })
        .collect::<Vec<_>>()
        .join(",");
    if !target_features.is_empty() {
        #[cfg(feature = "master")]
        match cx.sess().target.arch.as_ref() {
            "x86" | "x86_64" | "powerpc" => {
                func.add_attribute(FnAttribute::Target(&target_features))
            }
            // The target attribute is not supported on other targets in GCC.
            _ => (),
        }
    }
}