From bee533dcb9ce27208c6cbf0a6ce917931bc38bc0 Mon Sep 17 00:00:00 2001 From: Edoardo Marangoni Date: Wed, 28 May 2025 17:26:47 +0200 Subject: [PATCH] [CHERIoT]: Add `cheriot_sealed` attribute --- clang/include/clang/AST/Type.h | 4 + clang/include/clang/Basic/Attr.td | 8 ++ clang/include/clang/Basic/AttrDocs.td | 16 ++++ .../clang/Basic/DiagnosticSemaKinds.td | 5 ++ clang/lib/AST/Type.cpp | 23 +++++ clang/lib/CodeGen/CodeGenModule.cpp | 87 +++++++++++++++++++ clang/lib/Sema/SemaDecl.cpp | 19 ++++ clang/lib/Sema/SemaDeclAttr.cpp | 27 ++++++ clang/lib/Sema/SemaExpr.cpp | 48 ++++++++++ clang/lib/Sema/SemaExprCXX.cpp | 15 ++++ clang/lib/Sema/SemaExprMember.cpp | 5 ++ clang/lib/Sema/SemaType.cpp | 27 ++++++ .../riscv/cheriot-static-sealed-value-attr.c | 47 ++++++++++ .../cheri/cheriot-static-sealed-value-attr.c | 55 ++++++++++++ .../cheriot-static-sealed-value-attr.cpp | 59 +++++++++++++ llvm/include/llvm/IR/Attributes.h | 10 ++- llvm/lib/Target/RISCV/RISCV.h | 87 ++++++++++++++++--- llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp | 43 +++++---- .../Target/RISCV/RISCVExpandPseudoInsts.cpp | 78 +++++++++++++++-- llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 12 ++- .../RISCV/cheri/cheriot-sealed-attr.ll | 79 +++++++++++++++++ 21 files changed, 715 insertions(+), 39 deletions(-) create mode 100644 clang/test/CodeGen/cheri/riscv/cheriot-static-sealed-value-attr.c create mode 100644 clang/test/Sema/cheri/cheriot-static-sealed-value-attr.c create mode 100644 clang/test/SemaCXX/cheri/cheriot-static-sealed-value-attr.cpp create mode 100644 llvm/test/CodeGen/RISCV/cheri/cheriot-sealed-attr.ll diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 3217d8dfb784d..23dff94d7e762 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1140,6 +1140,10 @@ class QualType { /// Return true if this is a trivially relocatable type. bool isTriviallyRelocatableType(const ASTContext &Context) const; + /// Return true if this QualType has the attribute signaling that it + /// references a sealed type in CHERIoT. + bool hasCHERIoTSealedAttr() const; + /// Returns true if it is a class and it might be dynamic. bool mayBeDynamicClass() const; diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 23ccaf9a8761b..57214bb8854c8 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2906,6 +2906,14 @@ def CHERIOTSharedObject : DeclOrTypeAttr { let Subjects = SubjectList<[GlobalVar], ErrorDiag>; } +def CHERIoTSealedType : TypeAttr { + let Spellings = [C23<"cheriot", "sealed">, CXX11<"cheriot", "sealed">, + GNU<"cheriot_sealed">]; + let Documentation = [CHERIoTSealedTypeDocs]; + let Args = [StringArgument<"CompartmentName">, + StringArgument<"SealingTypeName">]; +} + def CHERINoSubobjectBounds : DeclOrTypeAttr { let Spellings = [GNU<"cheri_no_subobject_bounds">, CXX11<"cheri","no_subobject_bounds">, diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 7743815a15252..61eb592754eda 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1540,6 +1540,22 @@ example, `"RWcm"` and `"mcWR"` are both valid and entail the same permissions. }]; } +def CHERIoTSealedTypeDocs : Documentation { + let Category = DocCatVariable; + let Heading = "cheriot_sealed"; + let Content = [{ +Used to set a type as sealed: this means that values of the attributed type +will be understood as sealed values. The only operation valid on a sealed value +is to take its address. Doing so will result in a `` * +__sealed_capability``, where ```` is the attributed type. + +**Usage**: ``__attribute__((cheriot_sealed("", +"")))`` where `""` is the name of the +compartment that is allowed to unseal the value and `` is the +name of the chosen sealing key exposed from the compartment. The CXX11 and +C23-style attributes syntax (``[[cheriot::sealed]]``) is valid as well. + }]; +} def ObjCMethodFamilyDocs : Documentation { let Category = DocCatFunction; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 67176bcef48f2..06f3df5343969 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -150,6 +150,11 @@ def warn_cheriot_global_cap_import_non_volatile : Warning< "global variable definition '%0' has attribute %1 but is not qualified as `volatile`">; def err_cheriot_global_cap_import_initialized: Error< "global variable definition '%0' with attribute %1 cannot have an initializer">; +def err_cheriot_non_addr_of_expr_on_sealed + : Error< + "the only valid operation on a sealed value is to take its address">; +def err_cheriot_invalid_sealed_declaration + : Error<"cannot declare a sealed variable as %0">; // C99 variable-length arrays def ext_vla : Extension<"variable length arrays are a C99 feature">, diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 1f77ef8c89579..6cb88c9947e0c 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -120,6 +120,29 @@ const IdentifierInfo* QualType::getBaseTypeIdentifier() const { return nullptr; } +bool QualType::hasCHERIoTSealedAttr() const { + if (isNull()) { + return false; + } + + const auto *T = getTypePtr(); + + if (!T) { + return false; + } + + if (T->hasAttr(attr::Kind::CHERIoTSealedType)) { + return true; + } + + if (const auto *TT = T->getAs()) { + auto *Decl = TT->getDecl(); + return Decl && Decl->hasAttr(); + } + + return false; +} + bool QualType::mayBeDynamicClass() const { const auto *ClassDecl = getTypePtr()->getPointeeCXXRecordDecl(); return ClassDecl && ClassDecl->mayBeDynamicClass(); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 6da3eb0113452..95e4a64115f65 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -5749,6 +5749,93 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D, } llvm::Type* InitType = Init->getType(); + if (ASTTy.hasCHERIoTSealedAttr()) { + // In this case, the global variable refers to a to-be-sealed value of type + // . We need to: + // 1. Get (or create) the type of the sealed pointer, which has a fixed + // shape: `__Sealed_ {uint32_t sealing_key_ptr; uint32_t padding; + // body}`; + // 2. Change the type of the global variable to `__Sealed_`; + // 3. Create the initializer for the global variable. + + auto *Module = &getModule(); + auto *Ctxt = &getLLVMContext(); + auto *Int32Ty = llvm::Type::getInt32Ty(*Ctxt); + CHERIoTSealedTypeAttr *Attr = nullptr; + std::string TypeName; + + if (auto *TT = ASTTy->getAsTagDecl(); + TT && TT->hasAttr()) { + Attr = TT->getAttr(); + TypeName = TT->getNameAsString(); + } + + if (const auto *TT = ASTTy->getAs(); + TT && TT->getDecl() && + TT->getDecl()->hasAttr()) { + auto *TD = TT->getDecl(); + Attr = TD->getAttr(); + TypeName = TD->getNameAsString(); + } + + llvm::Comdat *C = Module->getOrInsertComdat(D->getName()); + C->setSelectionKind(llvm::Comdat::Any); + + llvm::Type *SealedStructElements[] = {Int32Ty, Int32Ty, Init->getType()}; + auto *SealedStructType = llvm::StructType::create( + *Ctxt, SealedStructElements, ("struct.__Sealed_" + TypeName)); + + auto SealingKeyName = + ("__export.sealing_type." + Attr->getCompartmentName() + "." + + Attr->getSealingTypeName()) + .str(); + + auto *SealingKeyRef = Module->getGlobalVariable(SealingKeyName); + + if (!SealingKeyRef) { + llvm::GlobalVariable *SealingKeyVarDef = new llvm::GlobalVariable( + *Module, Int32Ty, false, llvm::GlobalValue::ExternalLinkage, nullptr, + SealingKeyName); + SealingKeyVarDef->setDSOLocal(true); + SealingKeyVarDef->setAlignment(llvm::Align(4)); + SealingKeyRef = Module->getGlobalVariable(SealingKeyName); + assert(SealingKeyRef && "is supposed to exist now!"); + } + + auto *CastedPointer = + llvm::ConstantExpr::getPointerCast(SealingKeyRef, Int32Ty); + + llvm::Constant *Values[] = {CastedPointer, + llvm::ConstantInt::get(Int32Ty, 0), Init}; + + // Create the initializer. + auto *Init = llvm::ConstantStruct::get(SealedStructType, Values); + + llvm::Constant *Entry = + GetAddrOfGlobalVar(D, Init->getType(), ForDefinition_t(!IsTentative)); + auto *GV = dyn_cast(Entry); + GV->setSection(".sealed_objects"); + GV->setDSOLocal(true); + GV->setLinkage(llvm::GlobalValue::LinkOnceODRLinkage); + GV->setAlignment(llvm::Align(4)); + GV->setComdat(C); + GV->setInitializer(Init); + GV->addAttribute(llvm::CHERIoTSealedValueAttr::getAttrName()); + + addCompilerUsedGlobal(GV); + + if (emitter) + emitter->finalize(GV); + + SanitizerMD->reportGlobal(GV, *D, NeedsGlobalCtor); + + // Emit global variable debug information. + if (CGDebugInfo *DI = getModuleDebugInfo()) + if (getCodeGenOpts().hasReducedDebugInfo()) + DI->EmitGlobalVariable(GV, D); + return; + } + llvm::Constant *Entry = GetAddrOfGlobalVar(D, InitType, ForDefinition_t(!IsTentative)); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index e93656c8cc5a6..fd310234a4422 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -14947,6 +14947,25 @@ Sema::DeclGroupPtrTy Sema::FinalizeDeclaratorGroup(Scope *S, const DeclSpec &DS, for (Decl *D : Group) { if (!D) continue; + + auto *VD = dyn_cast(D); + + /// CHERIoT-specific check. If a variable is declared with a sealed type, it + /// can't have external storage, since its initializer must be available in + /// the unit. + /// + /// For example + /// ``` + /// typedef int MySealedType __attribute__((cheriot_sealed("MyCompartment", + /// "MySealingKeyType"))); extern MySealedType mySealedObject; + /// ``` + /// would not really make sense. + if (VD && VD->getType().hasCHERIoTSealedAttr() && + VD->hasExternalStorage()) { + Diag(D->getLocation(), diag::err_cheriot_invalid_sealed_declaration) + << "external"; + } + // Check if the Decl has been declared in '#pragma omp declare target' // directive and has static storage duration. if (auto *VD = dyn_cast(D); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index f3ece815e08fe..5d8d5beb941ae 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2227,6 +2227,30 @@ static void handleCHERIOTSharedObject(Sema &S, Decl *D, const ParsedAttr &Attr, S.Context, Attr, ObjectName, OwnedPermissions)); } +static void handleCHERIoTSealedType(Sema &S, Decl *D, const ParsedAttr &Attr, + Sema::DeclAttributeLocation DAL) { + + auto *TDecl = dyn_cast(D); + if (TDecl) { + StringRef CompartmentName; + SourceLocation CompartmentNameLiteralLoc; + if (!S.checkStringLiteralArgumentAttr(Attr, 0, CompartmentName, + &CompartmentNameLiteralLoc)) + return; + StringRef SealingTypeName; + SourceLocation SealingTypeNameLiteralLoc; + + if (!S.checkStringLiteralArgumentAttr(Attr, 1, SealingTypeName, + &SealingTypeNameLiteralLoc)) + return; + + // Here we simply copy the attribute. + TDecl->addAttr(::new (S.Context) CHERIoTSealedTypeAttr( + S.Context, Attr, CompartmentName, SealingTypeName)); + } else // Ignore if it's not a type definition. + S.Diag(Attr.getLoc(), diag::warn_attribute_ignored) << Attr.getAttrName(); +} + static void handleCHERICompartmentName(Sema &S, Decl *D, const ParsedAttr &Attr, Sema::DeclAttributeLocation DAL) { // cheri_compartment is both: @@ -7480,6 +7504,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_CHERIOTSharedObject: handleCHERIOTSharedObject(S, D, AL, DAL); break; + case ParsedAttr::AT_CHERIoTSealedType: + handleCHERIoTSealedType(S, D, AL, DAL); + break; case ParsedAttr::AT_InterruptState: handleInterruptState(S, D, AL); break; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 0bf2c6eccaea7..3c4bcebe9cb1b 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -704,6 +704,12 @@ ExprResult Sema::DefaultLvalueConversion(Expr *E) { QualType T = E->getType(); assert(!T.isNull() && "r-value conversion on typeless expression?"); + // CHERIoT-specific check. + if (T.hasCHERIoTSealedAttr() && !isUnevaluatedContext()) { + return ExprError( + Diag(E->getExprLoc(), diag::err_cheriot_non_addr_of_expr_on_sealed)); + } + // lvalue-to-rvalue conversion cannot be applied to types that decay to // pointers (i.e. function or array types). if (T->canDecayToPointerType()) @@ -4906,6 +4912,11 @@ ExprResult Sema::ActOnArraySubscriptExpr(Scope *S, Expr *base, MultiExprArg ArgExprs, SourceLocation rbLoc) { + // CHERIoT-specific check. + if (base->getType().hasCHERIoTSealedAttr() && !isUnevaluatedContext()) { + return ExprError(Diag(lbLoc, diag::err_cheriot_non_addr_of_expr_on_sealed)); + } + if (base && !base->getType().isNull() && base->hasPlaceholderType(BuiltinType::ArraySection)) { auto *AS = cast(base); @@ -8535,6 +8546,21 @@ QualType Sema::CheckConditionalOperands(ExprResult &Cond, ExprResult &LHS, ExprObjectKind &OK, SourceLocation QuestionLoc) { + // CHERIoT-specific check. + if (!isUnevaluatedContext()) { + if (LHS.get()->getType().hasCHERIoTSealedAttr()) { + Diag(LHS.get()->getExprLoc(), + diag::err_cheriot_non_addr_of_expr_on_sealed); + return QualType(); + } + + if (RHS.get()->getType().hasCHERIoTSealedAttr()) { + Diag(RHS.get()->getExprLoc(), + diag::err_cheriot_non_addr_of_expr_on_sealed); + return QualType(); + } + } + ExprResult LHSResult = CheckPlaceholderExpr(LHS.get()); if (!LHSResult.isUsable()) return QualType(); LHS = LHSResult; @@ -14717,6 +14743,9 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) { CheckAddressOfPackedMember(op); PointerInterpretationKind PIK = PointerInterpretationForBaseExpr(op); + if (op->getType().hasCHERIoTSealedAttr()) { + PIK = PIK_SealedCapability; + } return Context.getPointerType(op->getType(), PIK); } @@ -15659,6 +15688,19 @@ ExprResult Sema::BuildBinOp(Scope *S, SourceLocation OpLoc, LHSExpr = LHS.get(); RHSExpr = RHS.get(); + // CHERIoT-specific check. + if (!isUnevaluatedContext()) { + if (LHSExpr->getType().hasCHERIoTSealedAttr()) { + return ExprError(Diag(LHS.get()->getExprLoc(), + diag::err_cheriot_non_addr_of_expr_on_sealed)); + } + + if (RHSExpr->getType().hasCHERIoTSealedAttr()) { + return ExprError(Diag(RHS.get()->getExprLoc(), + diag::err_cheriot_non_addr_of_expr_on_sealed)); + } + } + // We want to end up calling one of SemaPseudoObject::checkAssignment // (if the LHS is a pseudo-object), BuildOverloadedBinOp (if // both expressions are overloadable or either is type-dependent), @@ -16097,6 +16139,12 @@ bool Sema::isQualifiedMemberAccess(Expr *E) { ExprResult Sema::BuildUnaryOp(Scope *S, SourceLocation OpLoc, UnaryOperatorKind Opc, Expr *Input, bool IsAfterAmp) { + // CHERIoT-specific check. + if (Input->getType().hasCHERIoTSealedAttr() && (Opc != UO_AddrOf) && + !isUnevaluatedContext()) { + return ExprError(Diag(OpLoc, diag::err_cheriot_non_addr_of_expr_on_sealed)); + } + // First things first: handle placeholders so that the // overloaded-operator check considers the right type. if (const BuiltinType *pty = Input->getType()->getAsPlaceholderType()) { diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 2d1d0ff33f67d..adc1bf4576610 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -6999,6 +6999,21 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS, ExprResult &RHS, ExprValueKind &VK, ExprObjectKind &OK, SourceLocation QuestionLoc) { + // CHERIoT-specific check. + if (!isUnevaluatedContext()) { + if (LHS.get()->getType().hasCHERIoTSealedAttr()) { + Diag(LHS.get()->getExprLoc(), + diag::err_cheriot_non_addr_of_expr_on_sealed); + return QualType(); + } + + if (RHS.get()->getType().hasCHERIoTSealedAttr()) { + Diag(RHS.get()->getExprLoc(), + diag::err_cheriot_non_addr_of_expr_on_sealed); + return QualType(); + } + } + // FIXME: Handle C99's complex types, block pointers and Obj-C++ interface // pointers. diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp index aa4d346608e5f..46aecd5b0c0e9 100644 --- a/clang/lib/Sema/SemaExprMember.cpp +++ b/clang/lib/Sema/SemaExprMember.cpp @@ -999,6 +999,11 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType, R.getLookupNameInfo(), TemplateArgs); QualType BaseType = BaseExprType; + // CHERIoT-specific check. + if (BaseType.hasCHERIoTSealedAttr() && !isUnevaluatedContext()) { + return ExprError(Diag(OpLoc, diag::err_cheriot_non_addr_of_expr_on_sealed)); + } + if (IsArrow) { assert(BaseType->isPointerType()); BaseType = BaseType->castAs()->getPointeeType(); diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 812ae8fcafdbb..044cd2c28d248 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -8913,6 +8913,28 @@ static bool HandleCHERIPointerQualifier(QualType &CurType, return true; } +static Attr *HandleCHERIoTSealedType(QualType &T, TypeProcessingState &State, + TypeAttrLocation TAL, ParsedAttr &attr) { + auto *S = &State.getSema(); + + StringRef CompartmentName; + SourceLocation CompartmentNameLiteralLoc; + if (!S->checkStringLiteralArgumentAttr(attr, 0, CompartmentName, + &CompartmentNameLiteralLoc)) + return nullptr; + + StringRef SealingTypeName; + SourceLocation SealingTypeNameLiteralLoc; + + if (!S->checkStringLiteralArgumentAttr(attr, 1, SealingTypeName, + &SealingTypeNameLiteralLoc)) + return nullptr; + + attr.setUsedAsTypeAttr(); + return ::new (S->Context) + CHERIoTSealedTypeAttr(S->Context, attr, CompartmentName, SealingTypeName); +} + static void handleCheriNoProvenanceAttr(QualType &T, TypeProcessingState &State, TypeAttrLocation TAL, ParsedAttr &Attr) { @@ -9328,6 +9350,11 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type, << type; } break; + case ParsedAttr::AT_CHERIoTSealedType: + if (auto *Attr = HandleCHERIoTSealedType(type, state, TAL, attr)) { + type = state.getAttributedType(Attr, type, type); + } + break; case ParsedAttr::AT_CHERICapability: attr.setUsedAsTypeAttr(); if (HandleCHERIPointerQualifier(type, state, TAL, attr)) { diff --git a/clang/test/CodeGen/cheri/riscv/cheriot-static-sealed-value-attr.c b/clang/test/CodeGen/cheri/riscv/cheriot-static-sealed-value-attr.c new file mode 100644 index 0000000000000..576626e0d52ee --- /dev/null +++ b/clang/test/CodeGen/cheri/riscv/cheriot-static-sealed-value-attr.c @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 %s -o - "-triple" "riscv32cheriot-unknown-unknown" "-emit-llvm" "-mframe-pointer=none" "-mcmodel=small" "-target-abi" "cheriot" "-Oz" "-Werror" -std=c2x | FileCheck %s + +// CHECK: %struct.__Sealed_SealedStructObj = type { i32, i32, %struct.SealedStructObj } +// CHECK: %struct.SealedStructObj = type { i32 } +struct __attribute__((cheriot_sealed("MyCompartment", "MyKeyName"))) SealedStructObj { int val; }; + +// CHECK: %struct.__Sealed_SealedInt = type { i32, i32, i32 } +typedef int SealedInt __attribute((cheriot_sealed("MyCompartment", "MyKeyName"))); + +// Objects are marked as used. +// +// CHECK: $Obj1 = comdat any +// CHECK: $Obj2 = comdat any +// +// There's only an exported sealing key for each combination. +// +// CHECK: @__export.sealing_type.MyCompartment.MyKeyName = external dso_local addrspace(200) global i32, align 4 +// +// CHECK: @Obj1 = linkonce_odr dso_local addrspace(200) global %struct.__Sealed_SealedStructObj { i32 ptrtoint (ptr addrspace(200) @__export.sealing_type.MyCompartment.MyKeyName to i32), i32 0, %struct.SealedStructObj { i32 10 } }, section ".sealed_objects", comdat, align 4 #0 +struct SealedStructObj Obj1 = {10}; + +// CHECK: @Obj2 = linkonce_odr dso_local addrspace(200) global %struct.__Sealed_SealedInt { i32 ptrtoint (ptr addrspace(200) @__export.sealing_type.MyCompartment.MyKeyName to i32), i32 0, i32 10 }, section ".sealed_objects", comdat, align 4 #0 +SealedInt Obj2 = 10; + +// CHECK: @llvm.compiler.used = appending addrspace(200) global [2 x ptr addrspace(200)] [ptr addrspace(200) @Obj1, ptr addrspace(200) @Obj2], section "llvm.metadata" + +void doSomething(struct SealedStructObj *__sealed_capability obj); +void doSomething2(SealedInt *__sealed_capability obj); + +// CHECK: ; Function Attrs: minsize nounwind optsize +// CHECK: define dso_local void @func() local_unnamed_addr addrspace(200) #1 { +void func() { +// CHECK: entry: +// CHECK: tail call void @doSomething(ptr addrspace(200) noundef nonnull @Obj1) #3 + doSomething(&Obj1); + +// CHECK: tail call void @doSomething2(ptr addrspace(200) noundef nonnull @Obj2) #3 + doSomething2(&Obj2); +} + +// CHECK: declare void @doSomething(ptr addrspace(200) noundef) local_unnamed_addr addrspace(200) #2 +// CHECK: declare void @doSomething2(ptr addrspace(200) noundef) local_unnamed_addr addrspace(200) #2 + +// CHECK: attributes #0 = { "cheriot_sealed_value" } +// CHECK: attributes #1 = { minsize nounwind optsize "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+32bit,+c,+cap-mode,+e,+m,+xcheri" } +// CHECK: attributes #2 = { minsize optsize "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+32bit,+c,+cap-mode,+e,+m,+xcheri" } +// CHECK: attributes #3 = { minsize nounwind optsize } diff --git a/clang/test/Sema/cheri/cheriot-static-sealed-value-attr.c b/clang/test/Sema/cheri/cheriot-static-sealed-value-attr.c new file mode 100644 index 0000000000000..8ff49b69a95f2 --- /dev/null +++ b/clang/test/Sema/cheri/cheriot-static-sealed-value-attr.c @@ -0,0 +1,55 @@ +// RUN: %riscv32_cheri_cc1 "-triple" "riscv32cheriot-unknown-unknown" "-target-abi" "cheriot" -verify %s + +struct PlainObj {int val;}; +struct __attribute__((cheriot_sealed("MyCompartment", "MyKeyName"))) MyObj { int val; }; + +struct MyObj Obj = {10}; + +typedef int MyObj2 [[cheriot::sealed("MyCompartment", "MyKeyName")]]; +typedef int MyObj3 __attribute__((cheriot_sealed("MyCompartment", "MyKeyName"))); +MyObj2 Obj2 = 10; +MyObj3 Obj3 = 10; + + +typedef int MyObj4[4] __attribute__((cheriot_sealed("MyCompartment", "MyKeyName"))); +typedef struct PlainObj *MyObj5 __attribute__((cheriot_sealed("MyCompartment", "MyKeyName"))); + +MyObj4 Obj4 = {0, 0, 0, 0}; + +struct PlainObj plainObj = {10}; +MyObj5 Obj5 = &plainObj; +extern MyObj5 Obj6; // expected-error{{cannot declare a sealed variable as external}} + +void useStruct(struct MyObj x); +void useStructSealedCap(struct MyObj *__sealed_capability x); +void useInt(int x); +void useIntSealedCap(int *__sealed_capability x); + +void func() { + struct MyObj* __sealed_capability ptr = &Obj; + int eq = (&Obj == &Obj); + int _eq = (Obj == Obj); // expected-error{{the only valid operation on a sealed value is to take its address}} + int leq = (&Obj < &Obj); + int or = ((int) &Obj | (int) &Obj); + int lor = ((int) &Obj || (int) &Obj); + Obj.val; // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj2 + 1); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj3 - 1); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj2 * 10); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj3 * 10); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj4[0]); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj5->val); // expected-error{{the only valid operation on a sealed value is to take its address}} + useStruct(Obj); // expected-error{{the only valid operation on a sealed value is to take its address}} + useInt(Obj2); // expected-error{{the only valid operation on a sealed value is to take its address}} + useStructSealedCap(&Obj); + useIntSealedCap(&Obj2); + int _ = sizeof(*(&Obj)); + int size = sizeof(*(&Obj5->val)); + Obj2++; // expected-error{{the only valid operation on a sealed value is to take its address}} + -Obj2; // expected-error{{the only valid operation on a sealed value is to take its address}} + Obj2 += 1; // expected-error{{the only valid operation on a sealed value is to take its address}} + float x = (float) Obj2; // expected-error{{the only valid operation on a sealed value is to take its address}} + + int k1 = Obj3 ; // expected-error{{the only valid operation on a sealed value is to take its address}} + int y = 1 ? Obj3 : Obj2; // expected-error{{the only valid operation on a sealed value is to take its address}} +} diff --git a/clang/test/SemaCXX/cheri/cheriot-static-sealed-value-attr.cpp b/clang/test/SemaCXX/cheri/cheriot-static-sealed-value-attr.cpp new file mode 100644 index 0000000000000..c8f657c932c6b --- /dev/null +++ b/clang/test/SemaCXX/cheri/cheriot-static-sealed-value-attr.cpp @@ -0,0 +1,59 @@ +// RUN: %riscv32_cheri_cc1 "-triple" "riscv32cheriot-unknown-unknown" "-target-abi" "cheriot" -verify %s + +struct PlainObj {int val;}; +struct __attribute__((cheriot_sealed("MyCompartment", "MyKeyName"))) MyObj { int val; }; // expected-error{{the only valid operation on a sealed value is to take its address}} + +struct MyObj Obj = {10}; + +typedef int MyObj2 [[cheriot::sealed("MyCompartment", "MyKeyName")]]; +typedef int MyObj3 __attribute__((cheriot_sealed("MyCompartment", "MyKeyName"))); +MyObj2 Obj2 = 10; +MyObj3 Obj3 = 10; + +typedef int MyObj4[4] __attribute__((cheriot_sealed("MyCompartment", "MyKeyName"))); +typedef struct PlainObj *MyObj5 __attribute__((cheriot_sealed("MyCompartment", "MyKeyName"))); + +MyObj4 Obj4 = {0, 0, 0, 0}; + +struct PlainObj plainObj = {10}; +MyObj5 Obj5 = &plainObj; +extern MyObj5 Obj6; // expected-error{{cannot declare a sealed variable as external}} + +void useStruct(struct MyObj x); +void useStructSealedCap(struct MyObj *__sealed_capability x); +void useInt(int x); +void useIntSealedCap(int *__sealed_capability x); + +void func() { + struct MyObj* __sealed_capability ptr = &Obj; + int eq = (&Obj == &Obj); + int _eq = (Obj == Obj); // expected-error{{the only valid operation on a sealed value is to take its address}} + int leq = (&Obj < &Obj); + int _or = ((int) &Obj | (int) &Obj); + int lor = ((int) &Obj || (int) &Obj); + Obj.val; // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj2 + 1); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj3 - 1); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj2 * 10); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj3 * 10); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj4[0]); // expected-error{{the only valid operation on a sealed value is to take its address}} + (Obj5->val); // expected-error{{the only valid operation on a sealed value is to take its address}} + useStruct(Obj); // expected-note{{in implicit copy constructor for 'MyObj' first required here}} + useInt(Obj2); // expected-error{{the only valid operation on a sealed value is to take its address}} + useStructSealedCap(&Obj); + useIntSealedCap(&Obj2); + auto _ = sizeof(*(&Obj)); + int x = 10; + decltype(*(&Obj2)) y = x; + + // Valid in unevaluated contexts. + auto size = sizeof(Obj5->val); + + Obj2++; // expected-error{{the only valid operation on a sealed value is to take its address}} + -Obj2; // expected-error{{the only valid operation on a sealed value is to take its address}} + Obj2 += 1; // expected-error{{the only valid operation on a sealed value is to take its address}} + float f = (float) Obj2; // expected-error{{the only valid operation on a sealed value is to take its address}} + + int k1 = Obj3 ; // expected-error{{the only valid operation on a sealed value is to take its address}} + int k = 1 ? Obj3 : Obj2; // expected-error{{the only valid operation on a sealed value is to take its address}} +} diff --git a/llvm/include/llvm/IR/Attributes.h b/llvm/include/llvm/IR/Attributes.h index c960988db58b4..84aca51bcd9f0 100644 --- a/llvm/include/llvm/IR/Attributes.h +++ b/llvm/include/llvm/IR/Attributes.h @@ -1458,7 +1458,7 @@ class CHERIoTGlobalCapabilityImportAttr { static auto getValidPermissionSymbols() { return &ValidSymbols; } /// Converts the permissions into an integer. - int64_t encodePermissions() { + uint32_t encodePermissions() { return (((Permissions[0] == *ReadPermissionSymbol) ? (1 << 31) : 0) + ((Permissions[1] == *WritePermissionSymbol) ? (1 << 30) : 0) + ((Permissions[2] == *CapPermissionSymbol) ? (1 << 29) : 0) + @@ -1604,6 +1604,14 @@ class CHERIoTGlobalCapabilityImportAttr { static constexpr const char *SharedObjectImportKindStr = "cheriot_shared_object"; }; + +/// Represents the LLVM-level attribute that is used to signal +/// that a global (variable) is a sealed value. +class CHERIoTSealedValueAttr { +public: + /// Get the name of the attribute. + static const std::string getAttrName() { return "cheriot_sealed_value"; } +}; } // end namespace llvm #endif // LLVM_IR_ATTRIBUTES_H diff --git a/llvm/lib/Target/RISCV/RISCV.h b/llvm/lib/Target/RISCV/RISCV.h index 3f17ac0ba1d1a..09f73a169aa4d 100644 --- a/llvm/lib/Target/RISCV/RISCV.h +++ b/llvm/lib/Target/RISCV/RISCV.h @@ -18,6 +18,7 @@ #include "llvm/ADT/SetVector.h" #include "llvm/IR/Function.h" #include "llvm/Target/TargetMachine.h" +#include namespace llvm { class FunctionPass; @@ -52,16 +53,58 @@ struct CHERIoTImportedObject { std::string ExportName; /// The name of the used symbol. std::string Name; + + enum class LibraryFlagValue : bool { IsNotLibrary = 0, IsLibrary = 1 }; + /// Flag indicating whether this is a library or compartment import. - bool IsLibrary; - /// Flag indicating that the entry should be public and a COMDAT. - bool IsPublic; + LibraryFlagValue LibraryFlag; + + enum class PublicFlagValue : bool { IsNotPublic = 0, IsPublic = 1 }; + + /// Flag indicating that the entry should be public. + PublicFlagValue PublicFlag; + + enum class GlobalFlagValue : bool { IsNotGlobal = 0, IsGlobal = 1 }; + /// Flag indicating that the entry is a global symbol. - bool IsGlobal; - /// May hold, if the import needs it, the value of the encoded permissions - /// that will be used when computing the value of the second word of the - /// generated entry. - std::optional MaybeSecondWordPermissionsEncoding; + GlobalFlagValue GlobalFlag; + + enum class COMDATFlagValue : bool { IsNotCOMDAT = 0, IsCOMDAT = 1 }; + + /// Flag indicating that the symbol is a COMDAT. + COMDATFlagValue COMDATFlag; + + enum class WeakFlagValue : bool { IsNotWeak = 0, IsWeak = 1 }; + + /// Flag indicating that the symbol has weak linking. + WeakFlagValue WeakFlag; + + enum class GroupedFlagValue : bool { IsNotGrouped = 0, IsGrouped = 1 }; + + /// Flag indicating that the entry is grouped. + GroupedFlagValue GroupedFlag; + + enum class WritableFlagValue : bool { IsNotWritable = 0, IsWritable = 1 }; + + /// Flag indicating that the entry needs the write flag set. + WritableFlagValue WritableFlag; + + enum class SecondWordKind { + /// The second word is zero. + EmptySecondWord, + /// The second word uses the `SecondWordValue` interpreted as the encoded + /// value of permissions of the import. + DiffAndPermsSecondWord, + /// The second word uses the `SecondWordValue` interpreted as the size of + /// the imported type. + SizeOfTypeSecondWord + }; + + /// The kind of the second word. + SecondWordKind SecondWord; + + /// The value of the second word. + uint32_t SecondWordValue; }; /** @@ -71,13 +114,35 @@ struct CHERIoTImportedObject { struct CHERIoTImportedObjectDenseMapInfo { /// Anything with an empty string is invalid, use a canonical zero value. static CHERIoTImportedObject getEmptyKey() { - return {"", "", "", false, false, false, std::nullopt}; + return {"", + "", + "", + CHERIoTImportedObject::LibraryFlagValue::IsNotLibrary, + CHERIoTImportedObject::PublicFlagValue::IsNotPublic, + CHERIoTImportedObject::GlobalFlagValue::IsNotGlobal, + CHERIoTImportedObject::COMDATFlagValue::IsNotCOMDAT, + CHERIoTImportedObject::WeakFlagValue::IsNotWeak, + CHERIoTImportedObject::GroupedFlagValue::IsNotGrouped, + CHERIoTImportedObject::WritableFlagValue::IsNotWritable, + CHERIoTImportedObject::SecondWordKind::EmptySecondWord, + 0}; } /// Anything with an empty string is invalid, use the IsPublic field to /// differentiate from the canonical zero value. static CHERIoTImportedObject getTombstoneKey() { - return {"", "", "", true, false, false, std::nullopt}; + return {"", + "", + "", + CHERIoTImportedObject::LibraryFlagValue::IsLibrary, + CHERIoTImportedObject::PublicFlagValue::IsNotPublic, + CHERIoTImportedObject::GlobalFlagValue::IsNotGlobal, + CHERIoTImportedObject::COMDATFlagValue::IsNotCOMDAT, + CHERIoTImportedObject::WeakFlagValue::IsNotWeak, + CHERIoTImportedObject::GroupedFlagValue::IsNotGrouped, + CHERIoTImportedObject::WritableFlagValue::IsNotWritable, + CHERIoTImportedObject::SecondWordKind::EmptySecondWord, + 0}; } /// The import name is unique within a compilation unit, use it for the hash. @@ -92,7 +157,7 @@ struct CHERIoTImportedObjectDenseMapInfo { // with mismatched export names (two different imports referring to the // same export may be permitted). Similarly, IsPublic depends on the // export and so may not differ. - return (LHS.IsLibrary == RHS.IsLibrary) && + return (LHS.LibraryFlag == RHS.LibraryFlag) && (LHS.ImportName == RHS.ImportName); } }; diff --git a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp index e8393e1749abc..98329b543a876 100644 --- a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp +++ b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp @@ -762,40 +762,46 @@ void RISCVAsmPrinter::emitEndOfAsmFile(Module &M) { // compilation units. Private ones must not be. MCSectionELF *Section; - if (Entry.IsPublic) { - Section = C.getELFSection(".compartment_imports." + Entry.Name, - ELF::SHT_PROGBITS, - ELF::SHF_ALLOC | ELF::SHF_GROUP | - (Entry.IsGlobal ? ELF::SHF_WRITE : 0), - 0, Entry.ImportName, true); - } else { - Section = C.getELFSection(".compartment_imports." + Entry.Name, - ELF::SHT_PROGBITS, ELF::SHF_ALLOC); - } + unsigned Flags = ELF::SHF_ALLOC; + if ((bool)Entry.GroupedFlag) + Flags |= ELF::SHF_GROUP; + if ((bool)Entry.WritableFlag) + Flags |= ELF::SHF_WRITE; + + Section = C.getELFSection( + ".compartment_imports." + Entry.Name, ELF::SHT_PROGBITS, Flags, 0, + Entry.ImportName, + Entry.COMDATFlag == CHERIoTImportedObject::COMDATFlagValue::IsCOMDAT + ? true + : false); OutStreamer->switchSection(Section); auto *Sym = C.getOrCreateSymbol(Entry.ImportName); auto *ExportSym = C.getOrCreateSymbol(Entry.ExportName); OutStreamer->emitSymbolAttribute(Sym, MCSA_ELF_TypeObject); - if (Entry.IsPublic && !Entry.IsGlobal) + if (Entry.WeakFlag == CHERIoTImportedObject::WeakFlagValue::IsWeak) OutStreamer->emitSymbolAttribute(Sym, MCSA_Weak); - if (Entry.IsGlobal) + if (Entry.GlobalFlag == CHERIoTImportedObject::GlobalFlagValue::IsGlobal) OutStreamer->emitSymbolAttribute(Sym, llvm::MCSA_Global); OutStreamer->emitValueToAlignment(Align(8)); OutStreamer->emitLabel(Sym); // Library imports have their low bit set. - if (Entry.IsLibrary) + if ((bool)Entry.LibraryFlag) OutStreamer->emitValue( MCBinaryExpr::createAdd(MCSymbolRefExpr::create(ExportSym, C), MCConstantExpr::create(1, C), C), 4); else OutStreamer->emitValue(MCSymbolRefExpr::create(ExportSym, C), 4); - if (!Entry.MaybeSecondWordPermissionsEncoding.has_value()) + + uint32_t SecondWord = Entry.SecondWordValue; + + if (Entry.SecondWord == + CHERIoTImportedObject::SecondWordKind::EmptySecondWord) OutStreamer->emitIntValue(0, 4); - else { - auto PermissionsEncoding = - Entry.MaybeSecondWordPermissionsEncoding.value(); + else if (Entry.SecondWord == + CHERIoTImportedObject::SecondWordKind::DiffAndPermsSecondWord) { + auto PermissionsEncoding = (int32_t)SecondWord; auto MangledExportNameEnd = Entry.ExportName + "_end"; auto *EncodedPermissionsExpr = MCConstantExpr::create(PermissionsEncoding, C); @@ -808,6 +814,9 @@ void RISCVAsmPrinter::emitEndOfAsmFile(Module &M) { MCBinaryExpr::createAdd(ExportLength, EncodedPermissionsExpr, C); OutStreamer->emitValue(SecondWordValue, 4); + } else { + auto *SecondWordValue = MCConstantExpr::create(SecondWord, C); + OutStreamer->emitValue(SecondWordValue, 4); } OutStreamer->emitELFSize(Sym, MCConstantExpr::create(8, C)); } diff --git a/llvm/lib/Target/RISCV/RISCVExpandPseudoInsts.cpp b/llvm/lib/Target/RISCV/RISCVExpandPseudoInsts.cpp index a57699238bc7f..76c3e1fded99e 100644 --- a/llvm/lib/Target/RISCV/RISCVExpandPseudoInsts.cpp +++ b/llvm/lib/Target/RISCV/RISCVExpandPseudoInsts.cpp @@ -332,9 +332,23 @@ MachineBasicBlock *RISCVExpandPseudo::insertLoadOfImportTable( // Make the original basic block fall-through to the new. MBB.addSuccessor(NewMBB); - ImportedObjects.insert({ImportSymbol->getName().str(), - ExportSymbol->getName().str(), ImportName.str(), - IsLibrary, IsPublic, false, std::nullopt}); + ImportedObjects.insert( + {ImportSymbol->getName().str(), ExportSymbol->getName().str(), + ImportName.str(), + IsLibrary ? CHERIoTImportedObject::LibraryFlagValue::IsLibrary + : CHERIoTImportedObject::LibraryFlagValue::IsNotLibrary, + IsPublic ? CHERIoTImportedObject::PublicFlagValue::IsPublic + : CHERIoTImportedObject::PublicFlagValue::IsNotPublic, + CHERIoTImportedObject::GlobalFlagValue::IsNotGlobal, + IsPublic ? CHERIoTImportedObject::COMDATFlagValue::IsCOMDAT + : CHERIoTImportedObject::COMDATFlagValue::IsNotCOMDAT, + IsPublic ? CHERIoTImportedObject::WeakFlagValue::IsWeak + : CHERIoTImportedObject::WeakFlagValue::IsNotWeak, + IsPublic ? CHERIoTImportedObject::GroupedFlagValue::IsGrouped + : CHERIoTImportedObject::GroupedFlagValue::IsNotGrouped, + CHERIoTImportedObject::WritableFlagValue::IsNotWritable, + CHERIoTImportedObject::SecondWordKind::EmptySecondWord, 0}); + LivePhysRegs LiveRegs; computeAndAddLiveIns(LiveRegs, *NewMBB); return NewMBB; @@ -554,12 +568,18 @@ bool RISCVExpandPseudo::expandAuipccInstPair( auto CheriotCapImportAttrName = llvm::CHERIoTGlobalCapabilityImportAttr::getAttrName(); std::optional CheriotCapImportAttr = std::nullopt; + auto CheriotSealedValueAttrName = llvm::CHERIoTSealedValueAttr::getAttrName(); + std::optional CheriotSealedValueAttr = std::nullopt; if (Symbol.isGlobal()) { auto *GV = llvm::dyn_cast(Symbol.getGlobal()); if (GV && GV->hasAttribute(CheriotCapImportAttrName)) { CheriotCapImportAttr.emplace(GV->getAttribute(CheriotCapImportAttrName)); } + if (GV && GV->hasAttribute(CheriotSealedValueAttrName)) { + CheriotSealedValueAttr.emplace( + GV->getAttribute(CheriotSealedValueAttrName)); + } } if (CheriotCapImportAttr.has_value()) { @@ -598,8 +618,47 @@ bool RISCVExpandPseudo::expandAuipccInstPair( auto EncodedPermissions = CapAttr.encodePermissions(); - ImportedObjects.insert({MangledImportName, MangledExportName, ImportName, - false, true, true, EncodedPermissions}); + ImportedObjects.insert( + {MangledImportName, MangledExportName, ImportName, + CHERIoTImportedObject::LibraryFlagValue::IsNotLibrary, + CHERIoTImportedObject::PublicFlagValue::IsPublic, + CHERIoTImportedObject::GlobalFlagValue::IsGlobal, + CHERIoTImportedObject::COMDATFlagValue::IsCOMDAT, + CHERIoTImportedObject::WeakFlagValue::IsNotWeak, + CHERIoTImportedObject::GroupedFlagValue::IsGrouped, + CHERIoTImportedObject::WritableFlagValue::IsWritable, + CHERIoTImportedObject::SecondWordKind::DiffAndPermsSecondWord, + EncodedPermissions}); + } else if (CheriotSealedValueAttr.has_value()) { + // We can safely assume, here, that GV is not null. + auto *GV = llvm::dyn_cast(Symbol.getGlobal()); + + MCContext &Ctxt = MF->getContext(); + auto SealedObjectName = std::string(GV->getName()); + auto MangledImportName = "__import.sealed_object." + SealedObjectName; + MCSymbol *MangledImportSymbol = Ctxt.getOrCreateSymbol(MangledImportName); + + BuildMI(NewMBB, DL, TII->get(RISCV::AUIPCC), TmpReg) + .addSym(MangledImportSymbol, FlagsHi); + + auto DL = MBB.getParent()->getDataLayout(); + + assert(DL.getTypeStoreSize(GV->getValueType()) < + std::numeric_limits::max() && + "Size of type should be less than uint32_t::max()"); + + uint32_t TypeSize = DL.getTypeStoreSize(GV->getValueType()); + ImportedObjects.insert( + {MangledImportName, SealedObjectName, SealedObjectName, + CHERIoTImportedObject::LibraryFlagValue::IsNotLibrary, + CHERIoTImportedObject::PublicFlagValue::IsPublic, + CHERIoTImportedObject::GlobalFlagValue::IsNotGlobal, + CHERIoTImportedObject::COMDATFlagValue::IsNotCOMDAT, + CHERIoTImportedObject::WeakFlagValue::IsWeak, + CHERIoTImportedObject::GroupedFlagValue::IsGrouped, + CHERIoTImportedObject::WritableFlagValue::IsWritable, + CHERIoTImportedObject::SecondWordKind::SizeOfTypeSecondWord, + TypeSize}); } else { BuildMI(NewMBB, DL, TII->get(RISCV::AUIPCC), TmpReg) .addDisp(Symbol, 0, FlagsHi); @@ -609,7 +668,8 @@ bool RISCVExpandPseudo::expandAuipccInstPair( .addReg(TmpReg) .addMBB(NewMBB, IsCheriot ? RISCVII::MO_CHERIOT_COMPARTMENT_LO_I : RISCVII::MO_PCREL_LO); - if (!CheriotCapImportAttr.has_value() && !InBounds && + if (!CheriotSealedValueAttr.has_value() && + !CheriotCapImportAttr.has_value() && !InBounds && MF->getSubtarget().isRV32E() && Symbol.isGlobal() && isa(Symbol.getGlobal()) && (cast(Symbol.getGlobal())->getSection() != @@ -649,8 +709,10 @@ bool RISCVExpandPseudo::expandCapLoadLocalCap( const GlobalValue *GV = Symbol.getGlobal(); auto *GVar = llvm::dyn_cast(GV); - if (GVar && GVar->hasAttribute( - llvm::CHERIoTGlobalCapabilityImportAttr::getAttrName())) { + if (GVar && + (GVar->hasAttribute( + llvm::CHERIoTGlobalCapabilityImportAttr::getAttrName()) || + GVar->hasAttribute(llvm::CHERIoTSealedValueAttr::getAttrName()))) { return expandAuipccInstPair(MBB, MBBI, NextMBBI, RISCVII::MO_CHERIOT_COMPARTMENT_HI, RISCV::CLC_64); diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp index ae683e018411c..f3fc67b3dd72c 100644 --- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp +++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp @@ -8234,8 +8234,16 @@ SDValue RISCVTargetLowering::lowerGlobalAddress(SDValue Op, const GlobalValue *GV = N->getGlobal(); if (auto *GVar = llvm::dyn_cast(GV)) { - auto AttrName = llvm::CHERIoTGlobalCapabilityImportAttr::getAttrName(); - if (GVar->hasAttribute(AttrName)) { + + // Some CHERIoT-specific tests, in particular check whether the global + // variable refers to a global capability import or an imported (or, rather, + // must-be-imported) sealed value. + auto GlobalCapImportAttrName = + llvm::CHERIoTGlobalCapabilityImportAttr::getAttrName(); + auto GlobalSealedValueAttrName = + llvm::CHERIoTSealedValueAttr::getAttrName(); + if (GVar->hasAttribute(GlobalCapImportAttrName) || + GVar->hasAttribute(GlobalSealedValueAttrName)) { SDLoc DL(N); SDValue Addr = getTargetNode(N, DL, Ty, DAG, 0); // Force it to be lowered to a `CLLC` regardless what `getAddr` would diff --git a/llvm/test/CodeGen/RISCV/cheri/cheriot-sealed-attr.ll b/llvm/test/CodeGen/RISCV/cheri/cheriot-sealed-attr.ll new file mode 100644 index 0000000000000..4324ddfb609a5 --- /dev/null +++ b/llvm/test/CodeGen/RISCV/cheri/cheriot-sealed-attr.ll @@ -0,0 +1,79 @@ +; RUN: llc -O0 --filetype=asm --mcpu=cheriot --mtriple=riscv32-unknown-unknown-cheriotrtos -target-abi cheriot -mattr=+xcheri,+cap-mode < %s | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32-S128-pf200:64:64:64:32-A200-P200-G200" +target triple = "riscv32cheriot-unknown-cheriotrtos" + +;; The type of the to-be-sealed value. +%struct.TestType = type { i32 } + +;; The shape of sealed `TestType`s. +%struct.SealedTestType = type { i32, i32, %struct.TestType } + +; Function Attrs: minsize mustprogress nounwind optsize +define dso_local chericcallcce noundef i32 @test_static_sealing() local_unnamed_addr addrspace(200) #0 { +entry: + +;; CHECK: .LBB0_1: # %entry +;; CHECK-NEXT: # Label of block must be emitted +;; CHECK-NEXT: auipcc ca0, %cheriot_compartment_hi(__import.sealed_object.test) +;; CHECK-NEXT: clc ca0, %cheriot_compartment_lo_i(.LBB0_1)(ca0) +;; CHECK-NEXT: .LBB0_3: # %entry +;; CHECK-NEXT: # Label of block must be emitted +;; CHECK-NEXT: auipcc ct1, %cheriot_compartment_hi(__import_static_sealing_inner_test_static_sealed_object) +;; CHECK-NEXT: clc ct1, %cheriot_compartment_lo_i(.LBB0_3)(ct1) +;; CHECK-NEXT: .LBB0_2: # %entry +;; CHECK-NEXT: # Label of block must be emitted +;; CHECK-NEXT: auipcc ct2, %cheriot_compartment_hi(.compartment_switcher) +;; CHECK-NEXT: clc ct2, %cheriot_compartment_lo_i(.LBB0_2)(ct2) +;; CHECK-NEXT: cjalr ct2 + %call = notail call chericcallcc noundef i32 @test_static_sealed_object(ptr addrspace(200) @test) #2 + ret i32 %call +} + +; Function Attrs: minsize optsize +declare dso_local chericcallcc noundef i32 @test_static_sealed_object(ptr addrspace(200)) local_unnamed_addr addrspace(200) #1 + +attributes #0 = { minsize mustprogress nounwind optsize "cheri-compartment"="static_sealing_test" "interrupt-state"="enabled" "no-builtin-longjmp" "no-builtin-printf" "no-builtin-setjmp" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="cheriot" "target-features"="+32bit,+c,+e,+m,+relax,+unaligned-scalar-mem,+xcheri,+xcheriot,+zmmul,-a,-b,-d,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-f,-h,-i,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcheri-norvc,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zacas,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b" } +attributes #1 = { minsize optsize "cheri-compartment"="static_sealing_inner" "interrupt-state"="enabled" "no-builtin-longjmp" "no-builtin-printf" "no-builtin-setjmp" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="cheriot" "target-features"="+32bit,+c,+e,+m,+relax,+unaligned-scalar-mem,+xcheri,+xcheriot,+zmmul,-a,-b,-d,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-f,-h,-i,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcheri-norvc,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zacas,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b" } +attributes #2 = { minsize nounwind optsize "no-builtin-longjmp" "no-builtin-printf" "no-builtin-setjmp" } + +!llvm.linker.options = !{} +!llvm.module.flags = !{!0, !1, !2, !4} + +@__export.sealing_type.static_sealing_inner.SealingType = external dso_local addrspace(200) global i32, align 4 + +$test = comdat any +;; CHECK: .type test,@object # @test +;; CHECK-NEXT: .section .sealed_objects,"awG",@progbits,test,comdat +;; CHECK-NEXT: .weak test +;; CHECK-NEXT: .p2align 2, 0x0 +;; CHECK-NEXT: test: +;; CHECK-NEXT: .word __export.sealing_type.static_sealing_inner.SealingType +;; CHECK-NEXT: .word 0 # 0x0 +;; CHECK-NEXT: .word 42 # 0x2a +;; CHECK-NEXT: .size test, 12 +@test = linkonce_odr dso_local addrspace(200) global %struct.SealedTestType { +i32 ptrtoint (ptr addrspace(200) +@__export.sealing_type.static_sealing_inner.SealingType to i32), i32 0, +%struct.TestType {i32 42}}, section ".sealed_objects", comdat, align 4 "cheriot_sealed_value" + +@llvm.compiler.used = appending addrspace(200) global [1 x ptr addrspace(200)] [ptr addrspace(200) @test], section "llvm.metadata" + + +;; The use of @test above must generate this import as well. + +;; CHECK: .section .compartment_imports.test,"awG",@progbits,__import.sealed_object.test +;; CHECK-NEXT: .type __import.sealed_object.test,@object +;; CHECK-NEXT: .weak __import.sealed_object.test +;; CHECK-NEXT: .p2align 3, 0x0 +;; CHECK-NEXT: __import.sealed_object.test: +;; CHECK-NEXT: .word test +;; CHECK-NEXT: .word 12 +;; CHECK-NEXT: .size __import.sealed_object.test, 8 + + +!0 = !{i32 1, !"wchar_size", i32 2} +!1 = !{i32 1, !"target-abi", !"cheriot"} +!2 = !{i32 6, !"riscv-isa", !3} +!3 = !{!"rv32e2p0_m2p0_c2p0_zmmul1p0_xcheri0p0_xcheriot1p0"} +!4 = !{i32 8, !"SmallDataLimit", i32 0}