mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-22 10:42:39 +01:00
[Attributes] Determine attribute properties from TableGen data
Continuing from D105763, this allows placing certain properties about attributes in the TableGen definition. In particular, we store whether an attribute applies to fn/param/ret (or a combination thereof). This information is used by the Verifier, as well as the ForceFunctionAttrs pass. I also plan to use this in LLParser, which also duplicates info on which attributes are valid where. This keeps metadata about attributes in one place, and makes it more likely that it stays in sync, rather than in various functions spread across the codebase. Differential Revision: https://reviews.llvm.org/D105780
This commit is contained in:
parent
714dda98bc
commit
495f2550b0
@ -90,6 +90,10 @@ public:
|
|||||||
return Kind >= FirstTypeAttr && Kind <= LastTypeAttr;
|
return Kind >= FirstTypeAttr && Kind <= LastTypeAttr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool canUseAsFnAttr(AttrKind Kind);
|
||||||
|
static bool canUseAsParamAttr(AttrKind Kind);
|
||||||
|
static bool canUseAsRetAttr(AttrKind Kind);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AttributeImpl *pImpl = nullptr;
|
AttributeImpl *pImpl = nullptr;
|
||||||
|
|
||||||
|
@ -10,228 +10,243 @@
|
|||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
/// Attribute property base class.
|
||||||
|
class AttrProperty;
|
||||||
|
|
||||||
|
/// Can be used as function attribute.
|
||||||
|
def FnAttr : AttrProperty;
|
||||||
|
|
||||||
|
/// Can be used as parameter attribute.
|
||||||
|
def ParamAttr : AttrProperty;
|
||||||
|
|
||||||
|
/// Can be used as return attribute.
|
||||||
|
def RetAttr : AttrProperty;
|
||||||
|
|
||||||
/// Attribute base class.
|
/// Attribute base class.
|
||||||
class Attr<string S> {
|
class Attr<string S, list<AttrProperty> P> {
|
||||||
// String representation of this attribute in the IR.
|
// String representation of this attribute in the IR.
|
||||||
string AttrString = S;
|
string AttrString = S;
|
||||||
|
list<AttrProperty> Properties = P;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enum attribute.
|
/// Enum attribute.
|
||||||
class EnumAttr<string S> : Attr<S>;
|
class EnumAttr<string S, list<AttrProperty> P> : Attr<S, P>;
|
||||||
|
|
||||||
/// Int attribute.
|
/// Int attribute.
|
||||||
class IntAttr<string S> : Attr<S>;
|
class IntAttr<string S, list<AttrProperty> P> : Attr<S, P>;
|
||||||
|
|
||||||
/// StringBool attribute.
|
|
||||||
class StrBoolAttr<string S> : Attr<S>;
|
|
||||||
|
|
||||||
/// Type attribute.
|
/// Type attribute.
|
||||||
class TypeAttr<string S> : Attr<S>;
|
class TypeAttr<string S, list<AttrProperty> P> : Attr<S, P>;
|
||||||
|
|
||||||
|
/// StringBool attribute.
|
||||||
|
class StrBoolAttr<string S> : Attr<S, []>;
|
||||||
|
|
||||||
/// Target-independent enum attributes.
|
/// Target-independent enum attributes.
|
||||||
|
|
||||||
/// Alignment of parameter (5 bits) stored as log2 of alignment with +1 bias.
|
/// Alignment of parameter (5 bits) stored as log2 of alignment with +1 bias.
|
||||||
/// 0 means unaligned (different from align(1)).
|
/// 0 means unaligned (different from align(1)).
|
||||||
def Alignment : IntAttr<"align">;
|
def Alignment : IntAttr<"align", [ParamAttr, RetAttr]>;
|
||||||
|
|
||||||
/// The result of the function is guaranteed to point to a number of bytes that
|
/// The result of the function is guaranteed to point to a number of bytes that
|
||||||
/// we can determine if we know the value of the function's arguments.
|
/// we can determine if we know the value of the function's arguments.
|
||||||
def AllocSize : IntAttr<"allocsize">;
|
def AllocSize : IntAttr<"allocsize", [FnAttr]>;
|
||||||
|
|
||||||
/// inline=always.
|
/// inline=always.
|
||||||
def AlwaysInline : EnumAttr<"alwaysinline">;
|
def AlwaysInline : EnumAttr<"alwaysinline", [FnAttr]>;
|
||||||
|
|
||||||
/// Function can access memory only using pointers based on its arguments.
|
/// Function can access memory only using pointers based on its arguments.
|
||||||
def ArgMemOnly : EnumAttr<"argmemonly">;
|
def ArgMemOnly : EnumAttr<"argmemonly", [FnAttr]>;
|
||||||
|
|
||||||
/// Callee is recognized as a builtin, despite nobuiltin attribute on its
|
/// Callee is recognized as a builtin, despite nobuiltin attribute on its
|
||||||
/// declaration.
|
/// declaration.
|
||||||
def Builtin : EnumAttr<"builtin">;
|
def Builtin : EnumAttr<"builtin", [FnAttr]>;
|
||||||
|
|
||||||
/// Pass structure by value.
|
/// Pass structure by value.
|
||||||
def ByVal : TypeAttr<"byval">;
|
def ByVal : TypeAttr<"byval", [ParamAttr]>;
|
||||||
|
|
||||||
/// Mark in-memory ABI type.
|
/// Mark in-memory ABI type.
|
||||||
def ByRef : TypeAttr<"byref">;
|
def ByRef : TypeAttr<"byref", [ParamAttr]>;
|
||||||
|
|
||||||
/// Parameter or return value may not contain uninitialized or poison bits.
|
/// Parameter or return value may not contain uninitialized or poison bits.
|
||||||
def NoUndef : EnumAttr<"noundef">;
|
def NoUndef : EnumAttr<"noundef", [ParamAttr, RetAttr]>;
|
||||||
|
|
||||||
/// Marks function as being in a cold path.
|
/// Marks function as being in a cold path.
|
||||||
def Cold : EnumAttr<"cold">;
|
def Cold : EnumAttr<"cold", [FnAttr]>;
|
||||||
|
|
||||||
/// Can only be moved to control-equivalent blocks.
|
/// Can only be moved to control-equivalent blocks.
|
||||||
def Convergent : EnumAttr<"convergent">;
|
def Convergent : EnumAttr<"convergent", [FnAttr]>;
|
||||||
|
|
||||||
/// Marks function as being in a hot path and frequently called.
|
/// Marks function as being in a hot path and frequently called.
|
||||||
def Hot: EnumAttr<"hot">;
|
def Hot: EnumAttr<"hot", [FnAttr]>;
|
||||||
|
|
||||||
/// Pointer is known to be dereferenceable.
|
/// Pointer is known to be dereferenceable.
|
||||||
def Dereferenceable : IntAttr<"dereferenceable">;
|
def Dereferenceable : IntAttr<"dereferenceable", [ParamAttr, RetAttr]>;
|
||||||
|
|
||||||
/// Pointer is either null or dereferenceable.
|
/// Pointer is either null or dereferenceable.
|
||||||
def DereferenceableOrNull : IntAttr<"dereferenceable_or_null">;
|
def DereferenceableOrNull : IntAttr<"dereferenceable_or_null",
|
||||||
|
[ParamAttr, RetAttr]>;
|
||||||
|
|
||||||
/// Function may only access memory that is inaccessible from IR.
|
/// Function may only access memory that is inaccessible from IR.
|
||||||
def InaccessibleMemOnly : EnumAttr<"inaccessiblememonly">;
|
def InaccessibleMemOnly : EnumAttr<"inaccessiblememonly", [FnAttr]>;
|
||||||
|
|
||||||
/// Function may only access memory that is either inaccessible from the IR,
|
/// Function may only access memory that is either inaccessible from the IR,
|
||||||
/// or pointed to by its pointer arguments.
|
/// or pointed to by its pointer arguments.
|
||||||
def InaccessibleMemOrArgMemOnly : EnumAttr<"inaccessiblemem_or_argmemonly">;
|
def InaccessibleMemOrArgMemOnly : EnumAttr<"inaccessiblemem_or_argmemonly",
|
||||||
|
[FnAttr]>;
|
||||||
|
|
||||||
/// Pass structure in an alloca.
|
/// Pass structure in an alloca.
|
||||||
def InAlloca : TypeAttr<"inalloca">;
|
def InAlloca : TypeAttr<"inalloca", [ParamAttr]>;
|
||||||
|
|
||||||
/// Source said inlining was desirable.
|
/// Source said inlining was desirable.
|
||||||
def InlineHint : EnumAttr<"inlinehint">;
|
def InlineHint : EnumAttr<"inlinehint", [FnAttr]>;
|
||||||
|
|
||||||
/// Force argument to be passed in register.
|
/// Force argument to be passed in register.
|
||||||
def InReg : EnumAttr<"inreg">;
|
def InReg : EnumAttr<"inreg", [ParamAttr, RetAttr]>;
|
||||||
|
|
||||||
/// Build jump-instruction tables and replace refs.
|
/// Build jump-instruction tables and replace refs.
|
||||||
def JumpTable : EnumAttr<"jumptable">;
|
def JumpTable : EnumAttr<"jumptable", [FnAttr]>;
|
||||||
|
|
||||||
/// Function must be optimized for size first.
|
/// Function must be optimized for size first.
|
||||||
def MinSize : EnumAttr<"minsize">;
|
def MinSize : EnumAttr<"minsize", [FnAttr]>;
|
||||||
|
|
||||||
/// Naked function.
|
/// Naked function.
|
||||||
def Naked : EnumAttr<"naked">;
|
def Naked : EnumAttr<"naked", [FnAttr]>;
|
||||||
|
|
||||||
/// Nested function static chain.
|
/// Nested function static chain.
|
||||||
def Nest : EnumAttr<"nest">;
|
def Nest : EnumAttr<"nest", [ParamAttr]>;
|
||||||
|
|
||||||
/// Considered to not alias after call.
|
/// Considered to not alias after call.
|
||||||
def NoAlias : EnumAttr<"noalias">;
|
def NoAlias : EnumAttr<"noalias", [ParamAttr, RetAttr]>;
|
||||||
|
|
||||||
/// Callee isn't recognized as a builtin.
|
/// Callee isn't recognized as a builtin.
|
||||||
def NoBuiltin : EnumAttr<"nobuiltin">;
|
def NoBuiltin : EnumAttr<"nobuiltin", [FnAttr]>;
|
||||||
|
|
||||||
/// Function cannot enter into caller's translation unit.
|
/// Function cannot enter into caller's translation unit.
|
||||||
def NoCallback : EnumAttr<"nocallback">;
|
def NoCallback : EnumAttr<"nocallback", [FnAttr]>;
|
||||||
|
|
||||||
/// Function creates no aliases of pointer.
|
/// Function creates no aliases of pointer.
|
||||||
def NoCapture : EnumAttr<"nocapture">;
|
def NoCapture : EnumAttr<"nocapture", [ParamAttr]>;
|
||||||
|
|
||||||
/// Call cannot be duplicated.
|
/// Call cannot be duplicated.
|
||||||
def NoDuplicate : EnumAttr<"noduplicate">;
|
def NoDuplicate : EnumAttr<"noduplicate", [FnAttr]>;
|
||||||
|
|
||||||
/// Function does not deallocate memory.
|
/// Function does not deallocate memory.
|
||||||
def NoFree : EnumAttr<"nofree">;
|
def NoFree : EnumAttr<"nofree", [FnAttr, ParamAttr]>;
|
||||||
|
|
||||||
/// Disable implicit floating point insts.
|
/// Disable implicit floating point insts.
|
||||||
def NoImplicitFloat : EnumAttr<"noimplicitfloat">;
|
def NoImplicitFloat : EnumAttr<"noimplicitfloat", [FnAttr]>;
|
||||||
|
|
||||||
/// inline=never.
|
/// inline=never.
|
||||||
def NoInline : EnumAttr<"noinline">;
|
def NoInline : EnumAttr<"noinline", [FnAttr]>;
|
||||||
|
|
||||||
/// Function is called early and/or often, so lazy binding isn't worthwhile.
|
/// Function is called early and/or often, so lazy binding isn't worthwhile.
|
||||||
def NonLazyBind : EnumAttr<"nonlazybind">;
|
def NonLazyBind : EnumAttr<"nonlazybind", [FnAttr]>;
|
||||||
|
|
||||||
/// Disable merging for specified functions or call sites.
|
/// Disable merging for specified functions or call sites.
|
||||||
def NoMerge : EnumAttr<"nomerge">;
|
def NoMerge : EnumAttr<"nomerge", [FnAttr]>;
|
||||||
|
|
||||||
/// Pointer is known to be not null.
|
/// Pointer is known to be not null.
|
||||||
def NonNull : EnumAttr<"nonnull">;
|
def NonNull : EnumAttr<"nonnull", [ParamAttr, RetAttr]>;
|
||||||
|
|
||||||
/// The function does not recurse.
|
/// The function does not recurse.
|
||||||
def NoRecurse : EnumAttr<"norecurse">;
|
def NoRecurse : EnumAttr<"norecurse", [FnAttr]>;
|
||||||
|
|
||||||
/// Disable redzone.
|
/// Disable redzone.
|
||||||
def NoRedZone : EnumAttr<"noredzone">;
|
def NoRedZone : EnumAttr<"noredzone", [FnAttr]>;
|
||||||
|
|
||||||
/// Mark the function as not returning.
|
/// Mark the function as not returning.
|
||||||
def NoReturn : EnumAttr<"noreturn">;
|
def NoReturn : EnumAttr<"noreturn", [FnAttr]>;
|
||||||
|
|
||||||
/// Function does not synchronize.
|
/// Function does not synchronize.
|
||||||
def NoSync : EnumAttr<"nosync">;
|
def NoSync : EnumAttr<"nosync", [FnAttr]>;
|
||||||
|
|
||||||
/// Disable Indirect Branch Tracking.
|
/// Disable Indirect Branch Tracking.
|
||||||
def NoCfCheck : EnumAttr<"nocf_check">;
|
def NoCfCheck : EnumAttr<"nocf_check", [FnAttr]>;
|
||||||
|
|
||||||
/// Function should not be instrumented.
|
/// Function should not be instrumented.
|
||||||
def NoProfile : EnumAttr<"noprofile">;
|
def NoProfile : EnumAttr<"noprofile", [FnAttr]>;
|
||||||
|
|
||||||
/// Function doesn't unwind stack.
|
/// Function doesn't unwind stack.
|
||||||
def NoUnwind : EnumAttr<"nounwind">;
|
def NoUnwind : EnumAttr<"nounwind", [FnAttr]>;
|
||||||
|
|
||||||
/// No SanitizeCoverage instrumentation.
|
/// No SanitizeCoverage instrumentation.
|
||||||
def NoSanitizeCoverage : EnumAttr<"nosanitize_coverage">;
|
def NoSanitizeCoverage : EnumAttr<"nosanitize_coverage", [FnAttr]>;
|
||||||
|
|
||||||
/// Null pointer in address space zero is valid.
|
/// Null pointer in address space zero is valid.
|
||||||
def NullPointerIsValid : EnumAttr<"null_pointer_is_valid">;
|
def NullPointerIsValid : EnumAttr<"null_pointer_is_valid", [FnAttr]>;
|
||||||
|
|
||||||
/// Select optimizations for best fuzzing signal.
|
/// Select optimizations for best fuzzing signal.
|
||||||
def OptForFuzzing : EnumAttr<"optforfuzzing">;
|
def OptForFuzzing : EnumAttr<"optforfuzzing", [FnAttr]>;
|
||||||
|
|
||||||
/// opt_size.
|
/// opt_size.
|
||||||
def OptimizeForSize : EnumAttr<"optsize">;
|
def OptimizeForSize : EnumAttr<"optsize", [FnAttr]>;
|
||||||
|
|
||||||
/// Function must not be optimized.
|
/// Function must not be optimized.
|
||||||
def OptimizeNone : EnumAttr<"optnone">;
|
def OptimizeNone : EnumAttr<"optnone", [FnAttr]>;
|
||||||
|
|
||||||
/// Similar to byval but without a copy.
|
/// Similar to byval but without a copy.
|
||||||
def Preallocated : TypeAttr<"preallocated">;
|
def Preallocated : TypeAttr<"preallocated", [FnAttr, ParamAttr]>;
|
||||||
|
|
||||||
/// Function does not access memory.
|
/// Function does not access memory.
|
||||||
def ReadNone : EnumAttr<"readnone">;
|
def ReadNone : EnumAttr<"readnone", [FnAttr, ParamAttr]>;
|
||||||
|
|
||||||
/// Function only reads from memory.
|
/// Function only reads from memory.
|
||||||
def ReadOnly : EnumAttr<"readonly">;
|
def ReadOnly : EnumAttr<"readonly", [FnAttr, ParamAttr]>;
|
||||||
|
|
||||||
/// Return value is always equal to this argument.
|
/// Return value is always equal to this argument.
|
||||||
def Returned : EnumAttr<"returned">;
|
def Returned : EnumAttr<"returned", [ParamAttr]>;
|
||||||
|
|
||||||
/// Parameter is required to be a trivial constant.
|
/// Parameter is required to be a trivial constant.
|
||||||
def ImmArg : EnumAttr<"immarg">;
|
def ImmArg : EnumAttr<"immarg", [ParamAttr]>;
|
||||||
|
|
||||||
/// Function can return twice.
|
/// Function can return twice.
|
||||||
def ReturnsTwice : EnumAttr<"returns_twice">;
|
def ReturnsTwice : EnumAttr<"returns_twice", [FnAttr]>;
|
||||||
|
|
||||||
/// Safe Stack protection.
|
/// Safe Stack protection.
|
||||||
def SafeStack : EnumAttr<"safestack">;
|
def SafeStack : EnumAttr<"safestack", [FnAttr]>;
|
||||||
|
|
||||||
/// Shadow Call Stack protection.
|
/// Shadow Call Stack protection.
|
||||||
def ShadowCallStack : EnumAttr<"shadowcallstack">;
|
def ShadowCallStack : EnumAttr<"shadowcallstack", [FnAttr]>;
|
||||||
|
|
||||||
/// Sign extended before/after call.
|
/// Sign extended before/after call.
|
||||||
def SExt : EnumAttr<"signext">;
|
def SExt : EnumAttr<"signext", [ParamAttr, RetAttr]>;
|
||||||
|
|
||||||
/// Alignment of stack for function (3 bits) stored as log2 of alignment with
|
/// Alignment of stack for function (3 bits) stored as log2 of alignment with
|
||||||
/// +1 bias 0 means unaligned (different from alignstack=(1)).
|
/// +1 bias 0 means unaligned (different from alignstack=(1)).
|
||||||
def StackAlignment : IntAttr<"alignstack">;
|
def StackAlignment : IntAttr<"alignstack", [FnAttr, ParamAttr]>;
|
||||||
|
|
||||||
/// Function can be speculated.
|
/// Function can be speculated.
|
||||||
def Speculatable : EnumAttr<"speculatable">;
|
def Speculatable : EnumAttr<"speculatable", [FnAttr]>;
|
||||||
|
|
||||||
/// Stack protection.
|
/// Stack protection.
|
||||||
def StackProtect : EnumAttr<"ssp">;
|
def StackProtect : EnumAttr<"ssp", [FnAttr]>;
|
||||||
|
|
||||||
/// Stack protection required.
|
/// Stack protection required.
|
||||||
def StackProtectReq : EnumAttr<"sspreq">;
|
def StackProtectReq : EnumAttr<"sspreq", [FnAttr]>;
|
||||||
|
|
||||||
/// Strong Stack protection.
|
/// Strong Stack protection.
|
||||||
def StackProtectStrong : EnumAttr<"sspstrong">;
|
def StackProtectStrong : EnumAttr<"sspstrong", [FnAttr]>;
|
||||||
|
|
||||||
/// Function was called in a scope requiring strict floating point semantics.
|
/// Function was called in a scope requiring strict floating point semantics.
|
||||||
def StrictFP : EnumAttr<"strictfp">;
|
def StrictFP : EnumAttr<"strictfp", [FnAttr]>;
|
||||||
|
|
||||||
/// Hidden pointer to structure to return.
|
/// Hidden pointer to structure to return.
|
||||||
def StructRet : TypeAttr<"sret">;
|
def StructRet : TypeAttr<"sret", [ParamAttr]>;
|
||||||
|
|
||||||
/// AddressSanitizer is on.
|
/// AddressSanitizer is on.
|
||||||
def SanitizeAddress : EnumAttr<"sanitize_address">;
|
def SanitizeAddress : EnumAttr<"sanitize_address", [FnAttr]>;
|
||||||
|
|
||||||
/// ThreadSanitizer is on.
|
/// ThreadSanitizer is on.
|
||||||
def SanitizeThread : EnumAttr<"sanitize_thread">;
|
def SanitizeThread : EnumAttr<"sanitize_thread", [FnAttr]>;
|
||||||
|
|
||||||
/// MemorySanitizer is on.
|
/// MemorySanitizer is on.
|
||||||
def SanitizeMemory : EnumAttr<"sanitize_memory">;
|
def SanitizeMemory : EnumAttr<"sanitize_memory", [FnAttr]>;
|
||||||
|
|
||||||
/// HWAddressSanitizer is on.
|
/// HWAddressSanitizer is on.
|
||||||
def SanitizeHWAddress : EnumAttr<"sanitize_hwaddress">;
|
def SanitizeHWAddress : EnumAttr<"sanitize_hwaddress", [FnAttr]>;
|
||||||
|
|
||||||
/// MemTagSanitizer is on.
|
/// MemTagSanitizer is on.
|
||||||
def SanitizeMemTag : EnumAttr<"sanitize_memtag">;
|
def SanitizeMemTag : EnumAttr<"sanitize_memtag", [FnAttr]>;
|
||||||
|
|
||||||
/// Speculative Load Hardening is enabled.
|
/// Speculative Load Hardening is enabled.
|
||||||
///
|
///
|
||||||
@ -239,34 +254,35 @@ def SanitizeMemTag : EnumAttr<"sanitize_memtag">;
|
|||||||
/// inlining) and a conservative merge strategy where inlining an attributed
|
/// inlining) and a conservative merge strategy where inlining an attributed
|
||||||
/// body will add the attribute to the caller. This ensures that code carrying
|
/// body will add the attribute to the caller. This ensures that code carrying
|
||||||
/// this attribute will always be lowered with hardening enabled.
|
/// this attribute will always be lowered with hardening enabled.
|
||||||
def SpeculativeLoadHardening : EnumAttr<"speculative_load_hardening">;
|
def SpeculativeLoadHardening : EnumAttr<"speculative_load_hardening",
|
||||||
|
[FnAttr]>;
|
||||||
|
|
||||||
/// Argument is swift error.
|
/// Argument is swift error.
|
||||||
def SwiftError : EnumAttr<"swifterror">;
|
def SwiftError : EnumAttr<"swifterror", [ParamAttr]>;
|
||||||
|
|
||||||
/// Argument is swift self/context.
|
/// Argument is swift self/context.
|
||||||
def SwiftSelf : EnumAttr<"swiftself">;
|
def SwiftSelf : EnumAttr<"swiftself", [ParamAttr]>;
|
||||||
|
|
||||||
/// Argument is swift async context.
|
/// Argument is swift async context.
|
||||||
def SwiftAsync : EnumAttr<"swiftasync">;
|
def SwiftAsync : EnumAttr<"swiftasync", [ParamAttr]>;
|
||||||
|
|
||||||
/// Function must be in a unwind table.
|
/// Function must be in a unwind table.
|
||||||
def UWTable : EnumAttr<"uwtable">;
|
def UWTable : EnumAttr<"uwtable", [FnAttr]>;
|
||||||
|
|
||||||
/// Minimum/Maximum vscale value for function.
|
/// Minimum/Maximum vscale value for function.
|
||||||
def VScaleRange : IntAttr<"vscale_range">;
|
def VScaleRange : IntAttr<"vscale_range", [FnAttr]>;
|
||||||
|
|
||||||
/// Function always comes back to callsite.
|
/// Function always comes back to callsite.
|
||||||
def WillReturn : EnumAttr<"willreturn">;
|
def WillReturn : EnumAttr<"willreturn", [FnAttr]>;
|
||||||
|
|
||||||
/// Function only writes to memory.
|
/// Function only writes to memory.
|
||||||
def WriteOnly : EnumAttr<"writeonly">;
|
def WriteOnly : EnumAttr<"writeonly", [FnAttr, ParamAttr]>;
|
||||||
|
|
||||||
/// Zero extended before/after call.
|
/// Zero extended before/after call.
|
||||||
def ZExt : EnumAttr<"zeroext">;
|
def ZExt : EnumAttr<"zeroext", [ParamAttr, RetAttr]>;
|
||||||
|
|
||||||
/// Function is required to make Forward Progress.
|
/// Function is required to make Forward Progress.
|
||||||
def MustProgress : EnumAttr<"mustprogress">;
|
def MustProgress : EnumAttr<"mustprogress", [FnAttr]>;
|
||||||
|
|
||||||
/// Target-independent string attributes.
|
/// Target-independent string attributes.
|
||||||
def LessPreciseFPMAD : StrBoolAttr<"less-precise-fpmad">;
|
def LessPreciseFPMAD : StrBoolAttr<"less-precise-fpmad">;
|
||||||
|
@ -485,6 +485,35 @@ void Attribute::Profile(FoldingSetNodeID &ID) const {
|
|||||||
ID.AddPointer(pImpl);
|
ID.AddPointer(pImpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AttributeProperty {
|
||||||
|
FnAttr = (1 << 0),
|
||||||
|
ParamAttr = (1 << 1),
|
||||||
|
RetAttr = (1 << 2),
|
||||||
|
};
|
||||||
|
|
||||||
|
#define GET_ATTR_PROP_TABLE
|
||||||
|
#include "llvm/IR/Attributes.inc"
|
||||||
|
|
||||||
|
static bool hasAttributeProperty(Attribute::AttrKind Kind,
|
||||||
|
AttributeProperty Prop) {
|
||||||
|
unsigned Index = Kind - 1;
|
||||||
|
assert(Index < sizeof(AttrPropTable) / sizeof(AttrPropTable[0]) &&
|
||||||
|
"Invalid attribute kind");
|
||||||
|
return AttrPropTable[Index] & Prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Attribute::canUseAsFnAttr(AttrKind Kind) {
|
||||||
|
return hasAttributeProperty(Kind, AttributeProperty::FnAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Attribute::canUseAsParamAttr(AttrKind Kind) {
|
||||||
|
return hasAttributeProperty(Kind, AttributeProperty::ParamAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Attribute::canUseAsRetAttr(AttrKind Kind) {
|
||||||
|
return hasAttributeProperty(Kind, AttributeProperty::RetAttr);
|
||||||
|
}
|
||||||
|
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
// AttributeImpl Definition
|
// AttributeImpl Definition
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
@ -540,8 +540,7 @@ private:
|
|||||||
void verifyTailCCMustTailAttrs(AttrBuilder Attrs, StringRef Context);
|
void verifyTailCCMustTailAttrs(AttrBuilder Attrs, StringRef Context);
|
||||||
void verifyMustTailCall(CallInst &CI);
|
void verifyMustTailCall(CallInst &CI);
|
||||||
bool verifyAttributeCount(AttributeList Attrs, unsigned Params);
|
bool verifyAttributeCount(AttributeList Attrs, unsigned Params);
|
||||||
void verifyAttributeTypes(AttributeSet Attrs, bool IsFunction,
|
void verifyAttributeTypes(AttributeSet Attrs, const Value *V);
|
||||||
const Value *V);
|
|
||||||
void verifyParameterAttrs(AttributeSet Attrs, Type *Ty, const Value *V);
|
void verifyParameterAttrs(AttributeSet Attrs, Type *Ty, const Value *V);
|
||||||
void checkUnsignedBaseTenFuncAttr(AttributeList Attrs, StringRef Attr,
|
void checkUnsignedBaseTenFuncAttr(AttributeList Attrs, StringRef Attr,
|
||||||
const Value *V);
|
const Value *V);
|
||||||
@ -1654,76 +1653,7 @@ void Verifier::visitModuleFlagCGProfileEntry(const MDOperand &MDO) {
|
|||||||
"expected an integer constant", Node->getOperand(2));
|
"expected an integer constant", Node->getOperand(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if this attribute kind only applies to functions.
|
void Verifier::verifyAttributeTypes(AttributeSet Attrs, const Value *V) {
|
||||||
static bool isFuncOnlyAttr(Attribute::AttrKind Kind) {
|
|
||||||
switch (Kind) {
|
|
||||||
case Attribute::NoMerge:
|
|
||||||
case Attribute::NoReturn:
|
|
||||||
case Attribute::NoSync:
|
|
||||||
case Attribute::WillReturn:
|
|
||||||
case Attribute::NoCallback:
|
|
||||||
case Attribute::NoCfCheck:
|
|
||||||
case Attribute::NoUnwind:
|
|
||||||
case Attribute::NoInline:
|
|
||||||
case Attribute::NoSanitizeCoverage:
|
|
||||||
case Attribute::AlwaysInline:
|
|
||||||
case Attribute::OptimizeForSize:
|
|
||||||
case Attribute::StackProtect:
|
|
||||||
case Attribute::StackProtectReq:
|
|
||||||
case Attribute::StackProtectStrong:
|
|
||||||
case Attribute::SafeStack:
|
|
||||||
case Attribute::ShadowCallStack:
|
|
||||||
case Attribute::NoRedZone:
|
|
||||||
case Attribute::NoImplicitFloat:
|
|
||||||
case Attribute::Naked:
|
|
||||||
case Attribute::InlineHint:
|
|
||||||
case Attribute::UWTable:
|
|
||||||
case Attribute::VScaleRange:
|
|
||||||
case Attribute::NonLazyBind:
|
|
||||||
case Attribute::ReturnsTwice:
|
|
||||||
case Attribute::SanitizeAddress:
|
|
||||||
case Attribute::SanitizeHWAddress:
|
|
||||||
case Attribute::SanitizeMemTag:
|
|
||||||
case Attribute::SanitizeThread:
|
|
||||||
case Attribute::SanitizeMemory:
|
|
||||||
case Attribute::MinSize:
|
|
||||||
case Attribute::NoDuplicate:
|
|
||||||
case Attribute::Builtin:
|
|
||||||
case Attribute::NoBuiltin:
|
|
||||||
case Attribute::Cold:
|
|
||||||
case Attribute::Hot:
|
|
||||||
case Attribute::OptForFuzzing:
|
|
||||||
case Attribute::OptimizeNone:
|
|
||||||
case Attribute::JumpTable:
|
|
||||||
case Attribute::Convergent:
|
|
||||||
case Attribute::ArgMemOnly:
|
|
||||||
case Attribute::NoRecurse:
|
|
||||||
case Attribute::InaccessibleMemOnly:
|
|
||||||
case Attribute::InaccessibleMemOrArgMemOnly:
|
|
||||||
case Attribute::AllocSize:
|
|
||||||
case Attribute::SpeculativeLoadHardening:
|
|
||||||
case Attribute::Speculatable:
|
|
||||||
case Attribute::StrictFP:
|
|
||||||
case Attribute::NullPointerIsValid:
|
|
||||||
case Attribute::MustProgress:
|
|
||||||
case Attribute::NoProfile:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return true if this is a function attribute that can also appear on
|
|
||||||
/// arguments.
|
|
||||||
static bool isFuncOrArgAttr(Attribute::AttrKind Kind) {
|
|
||||||
return Kind == Attribute::ReadOnly || Kind == Attribute::WriteOnly ||
|
|
||||||
Kind == Attribute::ReadNone || Kind == Attribute::NoFree ||
|
|
||||||
Kind == Attribute::Preallocated || Kind == Attribute::StackAlignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Verifier::verifyAttributeTypes(AttributeSet Attrs, bool IsFunction,
|
|
||||||
const Value *V) {
|
|
||||||
for (Attribute A : Attrs) {
|
for (Attribute A : Attrs) {
|
||||||
|
|
||||||
if (A.isStringAttribute()) {
|
if (A.isStringAttribute()) {
|
||||||
@ -1746,20 +1676,6 @@ void Verifier::verifyAttributeTypes(AttributeSet Attrs, bool IsFunction,
|
|||||||
V);
|
V);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFuncOnlyAttr(A.getKindAsEnum())) {
|
|
||||||
if (!IsFunction) {
|
|
||||||
CheckFailed("Attribute '" + A.getAsString() +
|
|
||||||
"' only applies to functions!",
|
|
||||||
V);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (IsFunction && !isFuncOrArgAttr(A.getKindAsEnum())) {
|
|
||||||
CheckFailed("Attribute '" + A.getAsString() +
|
|
||||||
"' does not apply to functions!",
|
|
||||||
V);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1770,7 +1686,14 @@ void Verifier::verifyParameterAttrs(AttributeSet Attrs, Type *Ty,
|
|||||||
if (!Attrs.hasAttributes())
|
if (!Attrs.hasAttributes())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
verifyAttributeTypes(Attrs, /*IsFunction=*/false, V);
|
verifyAttributeTypes(Attrs, V);
|
||||||
|
|
||||||
|
for (Attribute Attr : Attrs)
|
||||||
|
Assert(Attr.isStringAttribute() ||
|
||||||
|
Attribute::canUseAsParamAttr(Attr.getKindAsEnum()),
|
||||||
|
"Attribute '" + Attr.getAsString() +
|
||||||
|
"' does not apply to parameters",
|
||||||
|
V);
|
||||||
|
|
||||||
if (Attrs.hasAttribute(Attribute::ImmArg)) {
|
if (Attrs.hasAttribute(Attribute::ImmArg)) {
|
||||||
Assert(Attrs.getNumAttributes() == 1,
|
Assert(Attrs.getNumAttributes() == 1,
|
||||||
@ -1941,29 +1864,13 @@ void Verifier::verifyFunctionAttrs(FunctionType *FT, AttributeList Attrs,
|
|||||||
|
|
||||||
// Verify return value attributes.
|
// Verify return value attributes.
|
||||||
AttributeSet RetAttrs = Attrs.getRetAttributes();
|
AttributeSet RetAttrs = Attrs.getRetAttributes();
|
||||||
Assert((!RetAttrs.hasAttribute(Attribute::ByVal) &&
|
for (Attribute RetAttr : RetAttrs)
|
||||||
!RetAttrs.hasAttribute(Attribute::Nest) &&
|
Assert(RetAttr.isStringAttribute() ||
|
||||||
!RetAttrs.hasAttribute(Attribute::StructRet) &&
|
Attribute::canUseAsRetAttr(RetAttr.getKindAsEnum()),
|
||||||
!RetAttrs.hasAttribute(Attribute::NoCapture) &&
|
"Attribute '" + RetAttr.getAsString() +
|
||||||
!RetAttrs.hasAttribute(Attribute::NoFree) &&
|
"' does not apply to function return values",
|
||||||
!RetAttrs.hasAttribute(Attribute::Returned) &&
|
|
||||||
!RetAttrs.hasAttribute(Attribute::InAlloca) &&
|
|
||||||
!RetAttrs.hasAttribute(Attribute::Preallocated) &&
|
|
||||||
!RetAttrs.hasAttribute(Attribute::ByRef) &&
|
|
||||||
!RetAttrs.hasAttribute(Attribute::SwiftSelf) &&
|
|
||||||
!RetAttrs.hasAttribute(Attribute::SwiftAsync) &&
|
|
||||||
!RetAttrs.hasAttribute(Attribute::SwiftError)),
|
|
||||||
"Attributes 'byval', 'inalloca', 'preallocated', 'byref', "
|
|
||||||
"'nest', 'sret', 'nocapture', 'nofree', "
|
|
||||||
"'returned', 'swiftself', 'swiftasync', and 'swifterror'"
|
|
||||||
" do not apply to return values!",
|
|
||||||
V);
|
|
||||||
Assert((!RetAttrs.hasAttribute(Attribute::ReadOnly) &&
|
|
||||||
!RetAttrs.hasAttribute(Attribute::WriteOnly) &&
|
|
||||||
!RetAttrs.hasAttribute(Attribute::ReadNone)),
|
|
||||||
"Attribute '" + RetAttrs.getAsString() +
|
|
||||||
"' does not apply to function returns",
|
|
||||||
V);
|
V);
|
||||||
|
|
||||||
verifyParameterAttrs(RetAttrs, FT->getReturnType(), V);
|
verifyParameterAttrs(RetAttrs, FT->getReturnType(), V);
|
||||||
|
|
||||||
// Verify parameter attributes.
|
// Verify parameter attributes.
|
||||||
@ -2024,7 +1931,13 @@ void Verifier::verifyFunctionAttrs(FunctionType *FT, AttributeList Attrs,
|
|||||||
if (!Attrs.hasAttributes(AttributeList::FunctionIndex))
|
if (!Attrs.hasAttributes(AttributeList::FunctionIndex))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
verifyAttributeTypes(Attrs.getFnAttributes(), /*IsFunction=*/true, V);
|
verifyAttributeTypes(Attrs.getFnAttributes(), V);
|
||||||
|
for (Attribute FnAttr : Attrs.getFnAttributes())
|
||||||
|
Assert(FnAttr.isStringAttribute() ||
|
||||||
|
Attribute::canUseAsFnAttr(FnAttr.getKindAsEnum()),
|
||||||
|
"Attribute '" + FnAttr.getAsString() +
|
||||||
|
"' does not apply to functions!",
|
||||||
|
V);
|
||||||
|
|
||||||
Assert(!(Attrs.hasFnAttribute(Attribute::ReadNone) &&
|
Assert(!(Attrs.hasFnAttribute(Attribute::ReadNone) &&
|
||||||
Attrs.hasFnAttribute(Attribute::ReadOnly)),
|
Attrs.hasFnAttribute(Attribute::ReadOnly)),
|
||||||
@ -4705,10 +4618,10 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
|
|||||||
Assert(ArgCount == 2, "this attribute should have 2 arguments");
|
Assert(ArgCount == 2, "this attribute should have 2 arguments");
|
||||||
Assert(isa<ConstantInt>(Call.getOperand(Elem.Begin + 1)),
|
Assert(isa<ConstantInt>(Call.getOperand(Elem.Begin + 1)),
|
||||||
"the second argument should be a constant integral value");
|
"the second argument should be a constant integral value");
|
||||||
} else if (isFuncOnlyAttr(Kind)) {
|
} else if (Attribute::canUseAsParamAttr(Kind)) {
|
||||||
Assert((ArgCount) == 0, "this attribute has no argument");
|
|
||||||
} else if (!isFuncOrArgAttr(Kind)) {
|
|
||||||
Assert((ArgCount) == 1, "this attribute should have one argument");
|
Assert((ArgCount) == 1, "this attribute should have one argument");
|
||||||
|
} else if (Attribute::canUseAsFnAttr(Kind)) {
|
||||||
|
Assert((ArgCount) == 0, "this attribute has no argument");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
#include "llvm/Transforms/IPO/ForceFunctionAttrs.h"
|
#include "llvm/Transforms/IPO/ForceFunctionAttrs.h"
|
||||||
#include "llvm/ADT/StringSwitch.h"
|
|
||||||
#include "llvm/IR/Function.h"
|
#include "llvm/IR/Function.h"
|
||||||
#include "llvm/IR/LLVMContext.h"
|
#include "llvm/IR/LLVMContext.h"
|
||||||
#include "llvm/IR/Module.h"
|
#include "llvm/IR/Module.h"
|
||||||
@ -33,51 +32,6 @@ static cl::list<std::string> ForceRemoveAttributes(
|
|||||||
"example -force-remove-attribute=foo:noinline. This "
|
"example -force-remove-attribute=foo:noinline. This "
|
||||||
"option can be specified multiple times."));
|
"option can be specified multiple times."));
|
||||||
|
|
||||||
static Attribute::AttrKind parseAttrKind(StringRef Kind) {
|
|
||||||
return StringSwitch<Attribute::AttrKind>(Kind)
|
|
||||||
.Case("alwaysinline", Attribute::AlwaysInline)
|
|
||||||
.Case("builtin", Attribute::Builtin)
|
|
||||||
.Case("cold", Attribute::Cold)
|
|
||||||
.Case("convergent", Attribute::Convergent)
|
|
||||||
.Case("inlinehint", Attribute::InlineHint)
|
|
||||||
.Case("jumptable", Attribute::JumpTable)
|
|
||||||
.Case("minsize", Attribute::MinSize)
|
|
||||||
.Case("naked", Attribute::Naked)
|
|
||||||
.Case("nobuiltin", Attribute::NoBuiltin)
|
|
||||||
.Case("noduplicate", Attribute::NoDuplicate)
|
|
||||||
.Case("noimplicitfloat", Attribute::NoImplicitFloat)
|
|
||||||
.Case("noinline", Attribute::NoInline)
|
|
||||||
.Case("nonlazybind", Attribute::NonLazyBind)
|
|
||||||
.Case("noredzone", Attribute::NoRedZone)
|
|
||||||
.Case("noreturn", Attribute::NoReturn)
|
|
||||||
.Case("nocf_check", Attribute::NoCfCheck)
|
|
||||||
.Case("norecurse", Attribute::NoRecurse)
|
|
||||||
.Case("nounwind", Attribute::NoUnwind)
|
|
||||||
.Case("nosanitize_coverage", Attribute::NoSanitizeCoverage)
|
|
||||||
.Case("optforfuzzing", Attribute::OptForFuzzing)
|
|
||||||
.Case("optnone", Attribute::OptimizeNone)
|
|
||||||
.Case("optsize", Attribute::OptimizeForSize)
|
|
||||||
.Case("readnone", Attribute::ReadNone)
|
|
||||||
.Case("readonly", Attribute::ReadOnly)
|
|
||||||
.Case("argmemonly", Attribute::ArgMemOnly)
|
|
||||||
.Case("returns_twice", Attribute::ReturnsTwice)
|
|
||||||
.Case("safestack", Attribute::SafeStack)
|
|
||||||
.Case("shadowcallstack", Attribute::ShadowCallStack)
|
|
||||||
.Case("sanitize_address", Attribute::SanitizeAddress)
|
|
||||||
.Case("sanitize_hwaddress", Attribute::SanitizeHWAddress)
|
|
||||||
.Case("sanitize_memory", Attribute::SanitizeMemory)
|
|
||||||
.Case("sanitize_thread", Attribute::SanitizeThread)
|
|
||||||
.Case("sanitize_memtag", Attribute::SanitizeMemTag)
|
|
||||||
.Case("speculative_load_hardening", Attribute::SpeculativeLoadHardening)
|
|
||||||
.Case("ssp", Attribute::StackProtect)
|
|
||||||
.Case("sspreq", Attribute::StackProtectReq)
|
|
||||||
.Case("sspstrong", Attribute::StackProtectStrong)
|
|
||||||
.Case("strictfp", Attribute::StrictFP)
|
|
||||||
.Case("uwtable", Attribute::UWTable)
|
|
||||||
.Case("vscale_range", Attribute::VScaleRange)
|
|
||||||
.Default(Attribute::None);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If F has any forced attributes given on the command line, add them.
|
/// If F has any forced attributes given on the command line, add them.
|
||||||
/// If F has any forced remove attributes given on the command line, remove
|
/// If F has any forced remove attributes given on the command line, remove
|
||||||
/// them. When both force and force-remove are given to a function, the latter
|
/// them. When both force and force-remove are given to a function, the latter
|
||||||
@ -88,10 +42,10 @@ static void forceAttributes(Function &F) {
|
|||||||
auto KV = StringRef(S).split(':');
|
auto KV = StringRef(S).split(':');
|
||||||
if (KV.first != F.getName())
|
if (KV.first != F.getName())
|
||||||
return Kind;
|
return Kind;
|
||||||
Kind = parseAttrKind(KV.second);
|
Kind = Attribute::getAttrKindFromName(KV.second);
|
||||||
if (Kind == Attribute::None) {
|
if (Kind == Attribute::None || !Attribute::canUseAsFnAttr(Kind)) {
|
||||||
LLVM_DEBUG(dbgs() << "ForcedAttribute: " << KV.second
|
LLVM_DEBUG(dbgs() << "ForcedAttribute: " << KV.second
|
||||||
<< " unknown or not handled!\n");
|
<< " unknown or not a function attribute!\n");
|
||||||
}
|
}
|
||||||
return Kind;
|
return Kind;
|
||||||
};
|
};
|
||||||
|
@ -106,7 +106,7 @@ TEST(VerifierTest, InvalidRetAttribute) {
|
|||||||
raw_string_ostream ErrorOS(Error);
|
raw_string_ostream ErrorOS(Error);
|
||||||
EXPECT_TRUE(verifyModule(M, &ErrorOS));
|
EXPECT_TRUE(verifyModule(M, &ErrorOS));
|
||||||
EXPECT_TRUE(StringRef(ErrorOS.str()).startswith(
|
EXPECT_TRUE(StringRef(ErrorOS.str()).startswith(
|
||||||
"Attribute 'uwtable' only applies to functions!"));
|
"Attribute 'uwtable' does not apply to function return values"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(VerifierTest, CrossModuleRef) {
|
TEST(VerifierTest, CrossModuleRef) {
|
||||||
|
@ -25,6 +25,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
void emitTargetIndependentNames(raw_ostream &OS);
|
void emitTargetIndependentNames(raw_ostream &OS);
|
||||||
void emitFnAttrCompatCheck(raw_ostream &OS, bool IsStringAttr);
|
void emitFnAttrCompatCheck(raw_ostream &OS, bool IsStringAttr);
|
||||||
|
void emitAttributeProperties(raw_ostream &OF);
|
||||||
|
|
||||||
RecordKeeper &Records;
|
RecordKeeper &Records;
|
||||||
};
|
};
|
||||||
@ -109,9 +110,26 @@ void Attributes::emitFnAttrCompatCheck(raw_ostream &OS, bool IsStringAttr) {
|
|||||||
OS << "#endif\n";
|
OS << "#endif\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Attributes::emitAttributeProperties(raw_ostream &OS) {
|
||||||
|
OS << "#ifdef GET_ATTR_PROP_TABLE\n";
|
||||||
|
OS << "#undef GET_ATTR_PROP_TABLE\n";
|
||||||
|
OS << "static const uint8_t AttrPropTable[] = {\n";
|
||||||
|
for (StringRef KindName : {"EnumAttr", "TypeAttr", "IntAttr"}) {
|
||||||
|
for (auto A : Records.getAllDerivedDefinitions(KindName)) {
|
||||||
|
OS << "0";
|
||||||
|
for (Init *P : *A->getValueAsListInit("Properties"))
|
||||||
|
OS << " | AttributeProperty::" << cast<DefInit>(P)->getDef()->getName();
|
||||||
|
OS << ",\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OS << "};\n";
|
||||||
|
OS << "#endif\n";
|
||||||
|
}
|
||||||
|
|
||||||
void Attributes::emit(raw_ostream &OS) {
|
void Attributes::emit(raw_ostream &OS) {
|
||||||
emitTargetIndependentNames(OS);
|
emitTargetIndependentNames(OS);
|
||||||
emitFnAttrCompatCheck(OS, false);
|
emitFnAttrCompatCheck(OS, false);
|
||||||
|
emitAttributeProperties(OS);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace llvm {
|
namespace llvm {
|
||||||
|
Loading…
Reference in New Issue
Block a user