#include "gen/uda.h" #include "dmd/aggregate.h" #include "dmd/attrib.h" #include "dmd/declaration.h" #include "dmd/errors.h" #include "dmd/expression.h" #include "dmd/id.h" #include "dmd/identifier.h" #include "dmd/module.h" #include "driver/cl_options_sanitizers.h" #include "gen/irstate.h" #include "gen/llvm.h" #include "gen/llvmhelpers.h" #include "ir/irfunction.h" #include "ir/irvar.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" namespace { /// Checks whether `moduleDecl` is in the ldc package and it's identifier is /// `id`. bool isMagicModule(const ModuleDeclaration *moduleDecl, const Identifier *id) { if (!moduleDecl) return false; if (moduleDecl->id != id) { return false; } if (moduleDecl->packages.length != 1 || moduleDecl->packages.ptr[0] != Id::ldc) { return false; } return true; } /// Checks whether the type of `e` is a struct from an ldc recognised module, /// i.e. ldc.attributes or ldc.dcompute. bool isFromMagicModule(const StructLiteralExp *e, const Identifier *id) { auto moduleDecl = e->sd->getModule()->md; return isMagicModule(moduleDecl, id); } StructLiteralExp *getLdcAttributesStruct(Expression *attr) { // See whether we can evaluate the attribute at compile-time. All the LDC // attributes are struct literals that may be constructed using a CTFE // function. unsigned prevErrors = global.startGagging(); auto e = ctfeInterpret(attr); if (global.endGagging(prevErrors)) { return nullptr; } if (auto sle = e->isStructLiteralExp()) { if (isFromMagicModule(sle, Id::attributes)) return sle; } return nullptr; } void checkStructElems(StructLiteralExp *sle, ArrayParam elemTypes) { if (sle->elements->length != elemTypes.size()) { error(sle->loc, "unexpected field count in `%s`; does druntime not match compiler " "version?", sle->sd->toPrettyChars()); fatal(); } for (size_t i = 0; i < sle->elements->length; ++i) { if ((*sle->elements)[i]->type->toBasetype() != elemTypes[i]) { error(sle->loc, "invalid field type in `%s`; does druntime not match compiler " "version?", sle->sd->toPrettyChars()); fatal(); } } } /// Returns the StructLiteralExp magic attribute with identifier `id` from /// the ldc magic module with identifier `from` (attributes or dcompute) /// if it is applied to `sym`, otherwise returns nullptr. StructLiteralExp *getMagicAttribute(Dsymbol *sym, const Identifier *id, const Identifier *from) { if (!sym->userAttribDecl()) return nullptr; // Loop over all UDAs and early return the expression if a match was found. Expressions *attrs = sym->userAttribDecl()->getAttributes(); expandTuples(attrs); for (auto attr : *attrs) { if (auto sle = attr->isStructLiteralExp()) if (isFromMagicModule(sle, from) && id == sle->sd->ident) return sle; } return nullptr; } /// Returns the _last_ StructLiteralExp magic attribute with identifier `id` /// from the ldc magic module with identifier `from` (attributes or dcompute) if /// it is applied to `sym`, otherwise returns nullptr. StructLiteralExp *getLastMagicAttribute(Dsymbol *sym, const Identifier *id, const Identifier *from) { if (!sym->userAttribDecl()) return nullptr; // Loop over all UDAs and find the last match StructLiteralExp *lastMatch = nullptr; Expressions *attrs = sym->userAttribDecl()->getAttributes(); expandTuples(attrs); for (auto attr : *attrs) { if (auto sle = attr->isStructLiteralExp()) if (isFromMagicModule(sle, from) && id == sle->sd->ident) lastMatch = sle; } return lastMatch; } /// Calls `action` for each magic attribute with identifier `id` from /// the ldc magic module with identifier `from` (attributes or dcompute) /// applied to `sym`. void callForEachMagicAttribute(Dsymbol &sym, const Identifier *id, const Identifier *from, std::function action) { if (!sym.userAttribDecl()) return; // Loop over all UDAs and call `action` if a match was found. Expressions *attrs = sym.userAttribDecl()->getAttributes(); expandTuples(attrs); for (auto attr : *attrs) { if (auto sle = attr->isStructLiteralExp()) if (isFromMagicModule(sle, from) && id == sle->sd->ident) action(sle); } } sinteger_t getIntElem(StructLiteralExp *sle, size_t idx) { auto arg = (*sle->elements)[idx]; return arg->toInteger(); } llvm::StringRef getStringElem(StructLiteralExp *sle, size_t idx) { if (auto arg = (*sle->elements)[idx]) { if (auto strexp = arg->isStringExp()) { DString str = strexp->peekString(); return {str.ptr, str.length}; } } // Default initialized element (arg->op == TOKnull) return {}; } llvm::StringRef getFirstElemString(StructLiteralExp *sle) { return getStringElem(sle, 0); } // @allocSize(1) // @allocSize(0,2) void applyAttrAllocSize(StructLiteralExp *sle, IrFunction *irFunc) { checkStructElems(sle, {Type::tint32, Type::tint32}); auto sizeArgIdx = getIntElem(sle, 0); auto numArgIdx = getIntElem(sle, 1); // Get the number of parameters that the user specified (excluding the // implicit `this` parameter) auto numUserParams = irFunc->irFty.args.size(); // Verify that the index values are valid bool hasErrors = false; if (sizeArgIdx + 1 > sinteger_t(numUserParams)) { error(sle->loc, "`@ldc.attributes.allocSize.sizeArgIdx=%d` too large for " "function `%s` with %d arguments.", (int)sizeArgIdx, irFunc->decl->toChars(), (int)numUserParams); hasErrors = true; } if (numArgIdx + 1 > sinteger_t(numUserParams)) { error(sle->loc, "`@ldc.attributes.allocSize.numArgIdx=%d` too large for " "function `%s` with %d arguments.", (int)numArgIdx, irFunc->decl->toChars(), (int)numUserParams); hasErrors = true; } if (hasErrors) return; // Get the number of parameters of the function in LLVM IR. This includes // the `this` and sret parameters. const auto llvmNumParams = irFunc->irFty.funcType->getNumParams(); // Offset to correct indices for sret and this parameters. // These parameters can never be used for allocsize, and the user-specified // index does not account for these. unsigned offset = llvmNumParams - numUserParams; // Calculate the param indices for the function as defined in LLVM IR const auto llvmSizeIdx = sizeArgIdx + offset; const auto llvmNumIdx = numArgIdx + offset; #if LDC_LLVM_VER >= 1400 llvm::AttrBuilder builder(getGlobalContext()); #else llvm::AttrBuilder builder; #endif if (numArgIdx >= 0) { builder.addAllocSizeAttr(llvmSizeIdx, llvmNumIdx); } else { #if LDC_LLVM_VER < 1600 builder.addAllocSizeAttr(llvmSizeIdx, llvm::Optional()); #else builder.addAllocSizeAttr(llvmSizeIdx, std::optional()); #endif } llvm::Function *func = irFunc->getLLVMFunc(); #if LDC_LLVM_VER >= 1400 func->addFnAttrs(builder); #else func->addAttributes(LLAttributeList::FunctionIndex, builder); #endif } // @llvmAttr("key", "value") // @llvmAttr("key") void applyAttrLLVMAttr(StructLiteralExp *sle, llvm::AttrBuilder &attrs) { checkStructElems(sle, {Type::tstring, Type::tstring}); llvm::StringRef key = getStringElem(sle, 0); llvm::StringRef value = getStringElem(sle, 1); if (value.empty()) { const auto kind = llvm::Attribute::getAttrKindFromName(key); if (kind != llvm::Attribute::None) { attrs.addAttribute(kind); } else { attrs.addAttribute(key); } } else { attrs.addAttribute(key, value); } } // @llvmFastMathFlag("flag") void applyAttrLLVMFastMathFlag(StructLiteralExp *sle, IrFunction *irFunc) { checkStructElems(sle, {Type::tstring}); llvm::StringRef value = getStringElem(sle, 0); if (value == "clear") { irFunc->FMF.clear(); } else if (value == "fast") { irFunc->FMF.setFast(); } else if (value == "contract") { irFunc->FMF.setAllowContract(true); } else if (value == "nnan") { irFunc->FMF.setNoNaNs(); } else if (value == "ninf") { irFunc->FMF.setNoInfs(); } else if (value == "nsz") { irFunc->FMF.setNoSignedZeros(); } else if (value == "arcp") { irFunc->FMF.setAllowReciprocal(); } else { warning(sle->loc, "ignoring unrecognized flag parameter `%.*s` for `@ldc.attributes.%s`", static_cast(value.size()), value.data(), sle->sd->ident->toChars()); } } void applyAttrOptStrategy(StructLiteralExp *sle, IrFunction *irFunc) { checkStructElems(sle, {Type::tstring}); llvm::StringRef value = getStringElem(sle, 0); llvm::Function *func = irFunc->getLLVMFunc(); if (value == "none") { if (irFunc->decl->inlining == PINLINE::always) { error(sle->loc, "cannot combine `@ldc.attributes.%s(\"none\")` with " "`pragma(inline, true)`", sle->sd->ident->toChars()); return; } irFunc->decl->inlining = PINLINE::never; func->addFnAttr(llvm::Attribute::OptimizeNone); } else if (value == "optsize") { func->addFnAttr(llvm::Attribute::OptimizeForSize); } else if (value == "minsize") { func->addFnAttr(llvm::Attribute::MinSize); } else { warning(sle->loc, "ignoring unrecognized parameter `%.*s` for `@ldc.attributes.%s`", static_cast(value.size()), value.data(), sle->sd->ident->toChars()); } } void applyAttrSection(StructLiteralExp *sle, llvm::GlobalObject *globj) { checkStructElems(sle, {Type::tstring}); globj->setSection(getFirstElemString(sle)); } void applyAttrTarget(StructLiteralExp *sle, llvm::Function *func, IrFunction *irFunc) { // TODO: this is a rudimentary implementation for @target. Many more // target-related attributes could be applied to functions (not just for // @target): clang applies many attributes that LDC does not. // The current implementation here does not do any checking of the specified // string and simply passes all to llvm. checkStructElems(sle, {Type::tstring}); llvm::StringRef targetspec = getFirstElemString(sle); if (targetspec.empty() || targetspec == "default") return; llvm::StringRef CPU; std::vector features; if (func->hasFnAttribute("target-features")) { auto attr = func->getFnAttribute("target-features"); features.push_back(std::string(attr.getValueAsString())); } llvm::SmallVector fragments; llvm::SplitString(targetspec, fragments, ","); // special strings: "arch=", "tune=<...>", "fpmath=<...>" // if string starts with "no-", strip "no" // otherwise add "+" for (auto s : fragments) { s = s.trim(); if (s.empty()) continue; if (s.startswith("arch=")) { // TODO: be smarter than overwriting the previous arch= setting CPU = s.drop_front(5); continue; } if (s.startswith("tune=")) { // clang 3.8 ignores tune= too continue; } if (s.startswith("fpmath=")) { // TODO: implementation; clang 3.8 ignores fpmath= too continue; } if (s.startswith("no-")) { std::string f = (std::string("-") + s.drop_front(3)).str(); features.emplace_back(std::move(f)); continue; } std::string f = (std::string("+") + s).str(); features.emplace_back(std::move(f)); } if (!CPU.empty()) { func->addFnAttr("target-cpu", CPU); irFunc->targetCpuOverridden = true; } if (!features.empty()) { // Sorting the features puts negative features ("-") after positive features // ("+"). This provides the desired behavior of negative features overriding // positive features regardless of their order in the source code. sort(features.begin(), features.end()); func->addFnAttr("target-features", llvm::join(features.begin(), features.end(), ",")); irFunc->targetFeaturesOverridden = true; } } void applyAttrAssumeUsed(IRState &irs, StructLiteralExp *sle, llvm::Constant *symbol) { checkStructElems(sle, {}); irs.usedArray.push_back(symbol); } /// Tries to recognize the calling convention, /// TODO: Add support "cc " /// Returns true if succesful, with the calling convention in 'callconv'. /// Returns false if unsuccesful. bool parseCallingConvention(llvm::StringRef name, llvm::CallingConv::ID *callconv) { llvm::CallingConv::ID conv_id = llvm::StringSwitch(name) // Names recognized by Clang (see Clang's // CodeGenTypes::ClangCallConvToLLVMCallConv): .Case("stdcall", llvm::CallingConv::X86_StdCall) .Case("fastcall", llvm::CallingConv::X86_FastCall) .Case("regcall", llvm::CallingConv::X86_RegCall) .Case("thiscall", llvm::CallingConv::X86_ThisCall) .Case("ms_abi", llvm::CallingConv::Win64) .Case("sysv_abi", llvm::CallingConv::X86_64_SysV) .Case("pcs(\"aapcs\")", llvm::CallingConv::ARM_AAPCS) .Case("pcs(\"aapcs-vfp\")", llvm::CallingConv::ARM_AAPCS_VFP) .Case("intel_ocl_bicc", llvm::CallingConv::Intel_OCL_BI) .Case("pascal", llvm::CallingConv::C) // Check Clang, this may change in future .Case("vectorcall", llvm::CallingConv::X86_VectorCall) .Case("aarch64_vector_pcs", llvm::CallingConv::AArch64_VectorCall) .Case("preserve_most", llvm::CallingConv::PreserveMost) .Case("preserve_all", llvm::CallingConv::PreserveAll) .Case("swiftcall", llvm::CallingConv::Swift) #if LDC_LLVM_VER >= 1300 .Case("swiftasynccall", llvm::CallingConv::SwiftTail) #endif // Names recognized in LLVM IR (see LLVM's // LLParser::parseOptionalCallingConv): .Case("ccc", llvm::CallingConv::C) .Case("fastcc", llvm::CallingConv::Fast) .Case("coldcc", llvm::CallingConv::Cold) .Case("cfguard_checkcc", llvm::CallingConv::CFGuard_Check) .Case("x86_stdcallcc", llvm::CallingConv::X86_StdCall) .Case("x86_fastcallcc", llvm::CallingConv::X86_FastCall) .Case("x86_regcallcc", llvm::CallingConv::X86_RegCall) .Case("x86_thiscallcc", llvm::CallingConv::X86_ThisCall) .Case("x86_vectorcallcc", llvm::CallingConv::X86_VectorCall) .Case("arm_apcscc", llvm::CallingConv::ARM_APCS) .Case("arm_aapcscc", llvm::CallingConv::ARM_AAPCS) .Case("arm_aapcs_vfpcc", llvm::CallingConv::ARM_AAPCS_VFP) .Case("aarch64_vector_pcs", llvm::CallingConv::AArch64_VectorCall) .Case("aarch64_sve_vector_pcs", llvm::CallingConv::AArch64_SVE_VectorCall) .Case("msp430_intrcc", llvm::CallingConv::MSP430_INTR) .Case("avr_intrcc", llvm::CallingConv::AVR_INTR) .Case("avr_signalcc", llvm::CallingConv::AVR_SIGNAL) .Case("ptx_kernel", llvm::CallingConv::PTX_Kernel) .Case("ptx_device", llvm::CallingConv::PTX_Device) .Case("spir_kernel", llvm::CallingConv::SPIR_KERNEL) .Case("spir_func", llvm::CallingConv::SPIR_FUNC) .Case("intel_ocl_bicc", llvm::CallingConv::Intel_OCL_BI) .Case("x86_64_sysvcc", llvm::CallingConv::X86_64_SysV) .Case("win64cc", llvm::CallingConv::Win64) .Case("webkit_jscc", llvm::CallingConv::WebKit_JS) .Case("anyregcc", llvm::CallingConv::AnyReg) .Case("preserve_mostcc", llvm::CallingConv::PreserveMost) .Case("preserve_allcc", llvm::CallingConv::PreserveAll) .Case("ghccc", llvm::CallingConv::GHC) .Case("swiftcc", llvm::CallingConv::Swift) #if LDC_LLVM_VER >= 1300 .Case("swifttailcc", llvm::CallingConv::SwiftTail) #endif .Case("x86_intrcc", llvm::CallingConv::X86_INTR) #if LDC_LLVM_VER >= 1700 .Case("hhvmcc", llvm::CallingConv::DUMMY_HHVM) .Case("hhvm_ccc", llvm::CallingConv::DUMMY_HHVM_C) #else .Case("hhvmcc", llvm::CallingConv::HHVM) .Case("hhvm_ccc", llvm::CallingConv::HHVM_C) #endif .Case("cxx_fast_tlscc", llvm::CallingConv::CXX_FAST_TLS) .Case("amdgpu_vs", llvm::CallingConv::AMDGPU_VS) #if LDC_LLVM_VER >= 1200 .Case("amdgpu_gfx", llvm::CallingConv::AMDGPU_Gfx) #endif .Case("amdgpu_ls", llvm::CallingConv::AMDGPU_LS) .Case("amdgpu_hs", llvm::CallingConv::AMDGPU_HS) .Case("amdgpu_es", llvm::CallingConv::AMDGPU_ES) .Case("amdgpu_gs", llvm::CallingConv::AMDGPU_GS) .Case("amdgpu_ps", llvm::CallingConv::AMDGPU_PS) .Case("amdgpu_cs", llvm::CallingConv::AMDGPU_CS) .Case("amdgpu_kernel", llvm::CallingConv::AMDGPU_KERNEL) .Case("tailcc", llvm::CallingConv::Tail) .Case("default", llvm::CallingConv::MaxID - 1) .Default(llvm::CallingConv::MaxID); bool success = (conv_id != llvm::CallingConv::MaxID); if (success && callconv) *callconv = conv_id; return success; } } // anonymous namespace void applyVarDeclUDAs(VarDeclaration *decl, llvm::GlobalVariable *gvar) { if (!decl->userAttribDecl()) return; Expressions *attrs = decl->userAttribDecl()->getAttributes(); expandTuples(attrs); for (auto &attr : *attrs) { auto sle = getLdcAttributesStruct(attr); if (!sle) continue; auto ident = sle->sd->ident; if (ident == Id::udaSection) { applyAttrSection(sle, gvar); } else if (ident == Id::udaHidden) { if (!decl->isExport()) // export visibility is stronger gvar->setVisibility(LLGlobalValue::HiddenVisibility); } else if (ident == Id::udaOptStrategy || ident == Id::udaTarget) { error(sle->loc, "special attribute `ldc.attributes.%s` is only valid for functions", ident->toChars()); } else if (ident == Id::udaAssumeUsed) { applyAttrAssumeUsed(*gIR, sle, gvar); } else if (ident == Id::udaWeak) { // @weak is applied elsewhere } else if (ident == Id::udaDynamicCompile || ident == Id::udaDynamicCompileEmit || ident == Id::udaCallingConvention) { error(sle->loc, "special attribute `ldc.attributes.%s` is only valid for functions", ident->toChars()); } else if (ident == Id::udaDynamicCompileConst) { getIrGlobal(decl)->dynamicCompileConst = true; } else { warning(sle->loc, "ignoring unrecognized special attribute `ldc.attributes.%s`", ident->toChars()); } } } void applyFuncDeclUDAs(FuncDeclaration *decl, IrFunction *irFunc) { // function UDAs if (decl->userAttribDecl()) { llvm::Function *func = irFunc->getLLVMFunc(); assert(func); Expressions *attrs = decl->userAttribDecl()->getAttributes(); expandTuples(attrs); for (auto &attr : *attrs) { auto sle = getLdcAttributesStruct(attr); if (!sle) continue; auto ident = sle->sd->ident; if (ident == Id::udaAllocSize) { applyAttrAllocSize(sle, irFunc); } else if (ident == Id::udaLLVMAttr) { #if LDC_LLVM_VER >= 1400 llvm::AttrBuilder attrs(getGlobalContext()); #else llvm::AttrBuilder attrs; #endif applyAttrLLVMAttr(sle, attrs); #if LDC_LLVM_VER >= 1400 func->addFnAttrs(attrs); #else func->addAttributes(LLAttributeList::FunctionIndex, attrs); #endif } else if (ident == Id::udaHidden) { if (!decl->isExport()) // export visibility is stronger func->setVisibility(LLGlobalValue::HiddenVisibility); } else if (ident == Id::udaLLVMFastMathFlag) { applyAttrLLVMFastMathFlag(sle, irFunc); } else if (ident == Id::udaOptStrategy) { applyAttrOptStrategy(sle, irFunc); } else if (ident == Id::udaSection) { applyAttrSection(sle, func); } else if (ident == Id::udaTarget) { applyAttrTarget(sle, func, irFunc); } else if (ident == Id::udaAssumeUsed) { applyAttrAssumeUsed(*gIR, sle, func); } else if (ident == Id::udaWeak || ident == Id::udaKernel || ident == Id::udaNoSanitize || ident == Id::udaCallingConvention || ident == Id::udaNoSplitStack) { // These UDAs are applied elsewhere, thus should silently be ignored here. } else if (ident == Id::udaDynamicCompile) { irFunc->dynamicCompile = true; } else if (ident == Id::udaDynamicCompileEmit) { irFunc->dynamicCompileEmit = true; } else if (ident == Id::udaDynamicCompileConst) { error( sle->loc, "special attribute `ldc.attributes.%s` is only valid for variables", ident->toChars()); } else { warning(sle->loc, "ignoring unrecognized special attribute `ldc.attributes.%s`", ident->toChars()); } } } // parameter UDAs auto parameterList = irFunc->type->parameterList; for (auto arg : irFunc->irFty.args) { if (arg->parametersIdx >= parameterList.length()) continue; auto param = Parameter::getNth(parameterList.parameters, arg->parametersIdx); if (!param->userAttribDecl) continue; Expressions *attrs = param->userAttribDecl->getAttributes(); expandTuples(attrs); for (auto &attr : *attrs) { auto sle = getLdcAttributesStruct(attr); if (!sle) continue; auto ident = sle->sd->ident; if (ident == Id::udaLLVMAttr) { applyAttrLLVMAttr(sle, arg->attrs); } else { warning(sle->loc, "ignoring unrecognized special parameter attribute " "`ldc.attributes.%s`", ident->toChars()); } } } } /// Checks whether 'fd' has the @ldc.attributes.callingConvention("...") UDA applied. /// If so, it returns the calling convention in 'callconv'. bool hasCallingConventionUDA(FuncDeclaration *fd, llvm::CallingConv::ID *callconv) { auto sle = getLastMagicAttribute(fd, Id::udaCallingConvention, Id::attributes); if (!sle) return false; checkStructElems(sle, {Type::tstring}); auto name = getFirstElemString(sle); bool success = parseCallingConvention(name, callconv); if (!success) warning(sle->loc, "ignoring unrecognized calling convention name '%s' for " "`@ldc.attributes.callingConvention`", name.str().c_str()); return success; } /// Checks whether 'sym' has the @ldc.attributes._weak() UDA applied. bool hasWeakUDA(Dsymbol *sym) { auto sle = getMagicAttribute(sym, Id::udaWeak, Id::attributes); if (!sle) return false; checkStructElems(sle, {}); auto vd = sym->isVarDeclaration(); if (!(vd && vd->isDataseg()) && !sym->isFuncDeclaration()) error(sym->loc, "`@ldc.attributes.weak` can only be applied to functions or " "global variables"); return true; } /// Returns 0 if 'sym' does not have the @ldc.dcompute.compute() UDA applied. /// Returns 1 + n if 'sym' does and is @compute(n). extern "C" DComputeCompileFor hasComputeAttr(Dsymbol *sym) { auto sle = getMagicAttribute(sym, Id::udaCompute, Id::dcompute); if (!sle) return DComputeCompileFor::hostOnly; checkStructElems(sle, {Type::tint32}); return static_cast(1 + (*sle->elements)[0]->toInteger()); } /// Checks whether 'sym' has the @ldc.dcompute._kernel() UDA applied. bool hasKernelAttr(Dsymbol *sym) { auto sle = getMagicAttribute(sym, Id::udaKernel, Id::dcompute); if (!sle) return false; checkStructElems(sle, {}); if (!sym->isFuncDeclaration() && hasComputeAttr(sym->getModule()) != DComputeCompileFor::hostOnly) { error(sym->loc, "`@ldc.dcompute.kernel` can only be applied to functions" " in modules marked `@ldc.dcompute.compute`"); } return true; } /// Check whether `fd` has the `@ldc.attributes.noSplitStack` UDA applied. bool hasNoSplitStackUDA(FuncDeclaration *fd) { auto sle = getMagicAttribute(fd, Id::udaNoSplitStack, Id::attributes); return sle != nullptr; } /// Creates a mask (for &) of @ldc.attributes.noSanitize UDA applied to the /// function. /// If a bit is set in the mask, then the sanitizer is enabled. /// If a bit is not set in the mask, then the sanitizer is explicitly disabled /// by @noSanitize. unsigned getMaskFromNoSanitizeUDA(FuncDeclaration &fd) { opts::SanitizerBits inverse_mask = opts::NoneSanitizer; callForEachMagicAttribute(fd, Id::udaNoSanitize, Id::attributes, [&inverse_mask](StructLiteralExp *sle) { checkStructElems(sle, {Type::tstring}); auto name = getFirstElemString(sle); inverse_mask |= opts::parseSanitizerName(name, [&] { warning(sle->loc, "unrecognized sanitizer name '%s' for `@ldc.attributes.noSanitize`.", name.str().c_str()); }); }); return ~inverse_mask; }