diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 7714750a53d44..43832b7c88063 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -601,6 +601,63 @@ def CIR_VTableAttr : CIR_Attr<"VTable", "vtable", [TypedAttrInterface]> { }]; } +//===----------------------------------------------------------------------===// +// DynamicCastInfoAttr +//===----------------------------------------------------------------------===// + +def CIR_DynamicCastInfoAttr : CIR_Attr<"DynamicCastInfo", "dyn_cast_info"> { + let summary = "ABI specific information about a dynamic cast"; + let description = [{ + Provide ABI specific information about a dynamic cast operation. + + The `src_rtti` and the `dest_rtti` parameters give the RTTI of the source + record type and the destination record type, respectively. + + The `runtime_func` parameter gives the `__dynamic_cast` function which is + provided by the runtime. The `bad_cast_func` parameter gives the + `__cxa_bad_cast` function which is also provided by the runtime. + + The `offset_hint` parameter gives the hint value that should be passed to + the `__dynamic_cast` runtime function. + }]; + + let parameters = (ins + CIR_GlobalViewAttr:$src_rtti, + CIR_GlobalViewAttr:$dest_rtti, + "mlir::FlatSymbolRefAttr":$runtime_func, + "mlir::FlatSymbolRefAttr":$bad_cast_func, + CIR_IntAttr:$offset_hint + ); + + let builders = [ + AttrBuilderWithInferredContext<(ins + "GlobalViewAttr":$src_rtti, + "GlobalViewAttr":$dest_rtti, + "mlir::FlatSymbolRefAttr":$runtime_func, + "mlir::FlatSymbolRefAttr":$bad_cast_func, + "IntAttr":$offset_hint), [{ + return $_get(src_rtti.getContext(), src_rtti, dest_rtti, runtime_func, + bad_cast_func, offset_hint); + }]>, + ]; + + let genVerifyDecl = 1; + let assemblyFormat = [{ + `<` + struct(qualified($src_rtti), + qualified($dest_rtti), + $runtime_func, + $bad_cast_func, + qualified($offset_hint)) + `>` + }]; + + let extraClassDeclaration = [{ + /// Get attribute alias name for this attribute. + std::string getAlias() const; + }]; +} + //===----------------------------------------------------------------------===// // TargetAddressSpaceAttr //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index c81f64deff7bc..ce7019f9e21f7 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -232,6 +232,100 @@ def CIR_CastOp : CIR_Op<"cast", [ }]; } +//===----------------------------------------------------------------------===// +// DynamicCastOp +//===----------------------------------------------------------------------===// + +def CIR_DynamicCastKind : CIR_I32EnumAttr< + "DynamicCastKind", "dynamic cast kind", [ + I32EnumAttrCase<"Ptr", 0, "ptr">, + I32EnumAttrCase<"Ref", 1, "ref"> +]>; + +def CIR_DynamicCastOp : CIR_Op<"dyn_cast"> { + let summary = "Perform dynamic cast on record pointers"; + let description = [{ + The `cir.dyn_cast` operation models part of the semantics of the + `dynamic_cast` operator in C++. It can be used to perform 3 kinds of casts + on record pointers: + + - Down-cast, which casts a base class pointer to a derived class pointer; + - Side-cast, which casts a class pointer to a sibling class pointer; + - Cast-to-complete, which casts a class pointer to a void pointer. + + The input of the operation must be a record pointer. The result of the + operation is either a record pointer or a void pointer. + + The parameter `kind` specifies the semantics of this operation. If its value + is `ptr`, then the operation models dynamic casts on pointers. Otherwise, if + its value is `ref`, the operation models dynamic casts on references. + Specifically: + + - When the input pointer is a null pointer value: + - If `kind` is `ref`, the operation will invoke undefined behavior. A + sanitizer check will be emitted if sanitizer is on. + - Otherwise, the operation will return a null pointer value as its result. + - When the runtime type check fails: + - If `kind` is `ref`, the operation will throw a `bad_cast` exception. + - Otherwise, the operation will return a null pointer value as its result. + + The `info` argument gives detailed information about the requested dynamic + cast operation. It is an optional `#cir.dyn_cast_info` attribute that is + only present when the operation models a down-cast or a side-cast. + + The `relative_layout` argument specifies whether the Itanium C++ ABI vtable + uses relative layout. It is only meaningful when the operation models a + cast-to-complete operation. + + Examples: + + ```mlir + %0 = cir.dyn_cast ptr %p : !cir.ptr -> !cir.ptr + %1 = cir.dyn_cast ptr relative_layout %p : !cir.ptr + -> !cir.ptr + %2 = cir.dyn_cast ref %r : !cir.ptr -> !cir.ptr + #cir.dyn_cast_info< + srcRtti = #cir.global_view<@_ZTI4Base> : !cir.ptr, + destRtti = #cir.global_view<@_ZTI7Derived> : !cir.ptr, + runtimeFunc = @__dynamic_cast, + badCastFunc = @__cxa_bad_cast, + offsetHint = #cir.int<0> : !s64i + > + ``` + }]; + + let arguments = (ins + CIR_DynamicCastKind:$kind, + CIR_PtrToRecordType:$src, + OptionalAttr:$info, + UnitAttr:$relative_layout + ); + + let results = (outs + CIR_PtrToAnyOf<[CIR_VoidType, CIR_RecordType]>:$result + ); + + let assemblyFormat = [{ + $kind (`relative_layout` $relative_layout^)? $src + `:` qualified(type($src)) `->` qualified(type($result)) + (qualified($info)^)? attr-dict + }]; + + let extraClassDeclaration = [{ + /// Determine whether this operation models reference casting in C++. + bool isRefCast() { + return getKind() == ::cir::DynamicCastKind::Ref; + } + + /// Determine whether this operation represents a dynamic cast to a void + /// pointer. + bool isCastToVoid() { + return getType().isVoidPtr(); + } + }]; + + let hasLLVMLowering = false; +} //===----------------------------------------------------------------------===// // PtrStrideOp diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td index da03a291a7690..9e8e1298308a4 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td @@ -171,6 +171,12 @@ def CIR_AnyComplexOrIntOrFloatType : AnyTypeOf<[ let cppFunctionName = "isComplexOrIntegerOrFloatingPointType"; } +//===----------------------------------------------------------------------===// +// Record Type predicates +//===----------------------------------------------------------------------===// + +def CIR_AnyRecordType : CIR_TypeBase<"::cir::RecordType", "record type">; + //===----------------------------------------------------------------------===// // Array Type predicates //===----------------------------------------------------------------------===// @@ -228,6 +234,8 @@ def CIR_PtrToIntOrFloatType : CIR_PtrToType; def CIR_PtrToComplexType : CIR_PtrToType; +def CIR_PtrToRecordType : CIR_PtrToType; + def CIR_PtrToArray : CIR_PtrToType; //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp index 3484c59df4ece..64ac97025e7c7 100644 --- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp @@ -472,6 +472,49 @@ LogicalResult cir::VTableAttr::verify( return success(); } +//===----------------------------------------------------------------------===// +// DynamicCastInfoAtttr definitions +//===----------------------------------------------------------------------===// + +std::string DynamicCastInfoAttr::getAlias() const { + // The alias looks like: `dyn_cast_info__` + + std::string alias = "dyn_cast_info_"; + + alias.append(getSrcRtti().getSymbol().getValue()); + alias.push_back('_'); + alias.append(getDestRtti().getSymbol().getValue()); + + return alias; +} + +LogicalResult DynamicCastInfoAttr::verify( + function_ref emitError, cir::GlobalViewAttr srcRtti, + cir::GlobalViewAttr destRtti, mlir::FlatSymbolRefAttr runtimeFunc, + mlir::FlatSymbolRefAttr badCastFunc, cir::IntAttr offsetHint) { + auto isRttiPtr = [](mlir::Type ty) { + // RTTI pointers are !cir.ptr. + + auto ptrTy = mlir::dyn_cast(ty); + if (!ptrTy) + return false; + + auto pointeeIntTy = mlir::dyn_cast(ptrTy.getPointee()); + if (!pointeeIntTy) + return false; + + return pointeeIntTy.isUnsigned() && pointeeIntTy.getWidth() == 8; + }; + + if (!isRttiPtr(srcRtti.getType())) + return emitError() << "srcRtti must be an RTTI pointer"; + + if (!isRttiPtr(destRtti.getType())) + return emitError() << "destRtti must be an RTTI pointer"; + + return success(); +} + //===----------------------------------------------------------------------===// // CIR Dialect //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index cdd4e3c96ca98..5f88590c48d30 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -71,6 +71,10 @@ struct CIROpAsmDialectInterface : public OpAsmDialectInterface { os << "bfi_" << bitfield.getName().str(); return AliasResult::FinalAlias; } + if (auto dynCastInfoAttr = mlir::dyn_cast(attr)) { + os << dynCastInfoAttr.getAlias(); + return AliasResult::FinalAlias; + } return AliasResult::NoAlias; } }; diff --git a/clang/test/CIR/IR/dynamic-cast.cir b/clang/test/CIR/IR/dynamic-cast.cir new file mode 100644 index 0000000000000..283f11e25b82b --- /dev/null +++ b/clang/test/CIR/IR/dynamic-cast.cir @@ -0,0 +1,59 @@ +// RUN: cir-opt --verify-roundtrip %s | FileCheck %s + +!s64i = !cir.int +!u8i = !cir.int +!void = !cir.void + +!rec_Base = !cir.record +!rec_Derived = !cir.record + +#dyn_cast_info__ZTI4Base__ZTI7Derived = #cir.dyn_cast_info : !cir.ptr, dest_rtti = #cir.global_view<@_ZTI7Derived> : !cir.ptr, runtime_func = @__dynamic_cast, bad_cast_func = @__cxa_bad_cast, offset_hint = #cir.int<0> : !s64i> + +// CHECK: #dyn_cast_info__ZTI4Base__ZTI7Derived = #cir.dyn_cast_info : !cir.ptr, dest_rtti = #cir.global_view<@_ZTI7Derived> : !cir.ptr, runtime_func = @__dynamic_cast, bad_cast_func = @__cxa_bad_cast, offset_hint = #cir.int<0> : !s64i> + +module { + cir.global "private" constant external @_ZTI4Base : !cir.ptr + cir.global "private" constant external @_ZTI7Derived : !cir.ptr + cir.func private @__dynamic_cast(!cir.ptr, !cir.ptr, !cir.ptr, !s64i) -> !cir.ptr + cir.func private @__cxa_bad_cast() + + cir.func @test_ptr_cast(%arg0: !cir.ptr) -> !cir.ptr { + %0 = cir.dyn_cast ptr %arg0 : !cir.ptr -> !cir.ptr #dyn_cast_info__ZTI4Base__ZTI7Derived + cir.return %0 : !cir.ptr + } + + // CHECK: cir.func @test_ptr_cast(%arg0: !cir.ptr) -> !cir.ptr { + // CHECK: %0 = cir.dyn_cast ptr %arg0 : !cir.ptr -> !cir.ptr #dyn_cast_info__ZTI4Base__ZTI7Derived + // CHECK: cir.return %0 : !cir.ptr + // CHECK: } + + cir.func @test_ref_cast(%arg0: !cir.ptr) -> !cir.ptr { + %0 = cir.dyn_cast ref %arg0 : !cir.ptr -> !cir.ptr #dyn_cast_info__ZTI4Base__ZTI7Derived + cir.return %0 : !cir.ptr + } + + // CHECK: cir.func @test_ref_cast(%arg0: !cir.ptr) -> !cir.ptr { + // CHECK: %0 = cir.dyn_cast ref %arg0 : !cir.ptr -> !cir.ptr #dyn_cast_info__ZTI4Base__ZTI7Derived + // CHECK: cir.return %0 : !cir.ptr + // CHECK: } + + cir.func dso_local @test_cast_to_void(%arg0: !cir.ptr) -> !cir.ptr { + %0 = cir.dyn_cast ptr %arg0 : !cir.ptr -> !cir.ptr + cir.return %0 : !cir.ptr + } + + // CHECK: cir.func dso_local @test_cast_to_void(%arg0: !cir.ptr) -> !cir.ptr { + // CHECK: %0 = cir.dyn_cast ptr %arg0 : !cir.ptr -> !cir.ptr + // CHECK: cir.return %0 : !cir.ptr + // CHECK: } + + cir.func dso_local @test_relative_layout_cast(%arg0: !cir.ptr) -> !cir.ptr { + %0 = cir.dyn_cast ptr relative_layout %arg0 : !cir.ptr -> !cir.ptr + cir.return %0 : !cir.ptr + } + + // CHECK: cir.func dso_local @test_relative_layout_cast(%arg0: !cir.ptr) -> !cir.ptr { + // CHECK: %0 = cir.dyn_cast ptr relative_layout %arg0 : !cir.ptr -> !cir.ptr + // CHECK: cir.return %0 : !cir.ptr + // CHECK: } +} diff --git a/clang/test/CIR/IR/invalid-dyn-cast.cir b/clang/test/CIR/IR/invalid-dyn-cast.cir new file mode 100644 index 0000000000000..65c7fc1e5594b --- /dev/null +++ b/clang/test/CIR/IR/invalid-dyn-cast.cir @@ -0,0 +1,43 @@ +// RUN: cir-opt %s -verify-diagnostics -split-input-file + +!s64i = !cir.int +!s8i = !cir.int +!u32i = !cir.int +!u8i = !cir.int +!void = !cir.void + +!Base = !cir.record !cir.int>>>}> +!Derived = !cir.record !cir.int>>>}>}> + +module { + cir.global "private" constant external @_ZTI4Base : !cir.ptr + cir.global "private" constant external @_ZTI7Derived : !cir.ptr + cir.func private @__dynamic_cast(!cir.ptr, !cir.ptr, !cir.ptr, !s64i) -> !cir.ptr + cir.func private @__cxa_bad_cast() + cir.func @test(%arg0 : !cir.ptr) { + // expected-error@+1 {{srcRtti must be an RTTI pointer}} + %0 = cir.dyn_cast ptr %arg0 : !cir.ptr -> !cir.ptr #cir.dyn_cast_info : !cir.ptr, dest_rtti = #cir.global_view<@_ZTI7Derived> : !cir.ptr, runtime_func = @__dynamic_cast, bad_cast_func = @__cxa_bad_cast, offset_hint = #cir.int<0> : !s64i> + } +} + +// ----- + +!s64i = !cir.int +!s8i = !cir.int +!u32i = !cir.int +!u8i = !cir.int +!void = !cir.void + +!Base = !cir.record !cir.int>>>}> +!Derived = !cir.record !cir.int>>>}>}> + +module { + cir.global "private" constant external @_ZTI4Base : !cir.ptr + cir.global "private" constant external @_ZTI7Derived : !cir.ptr + cir.func private @__dynamic_cast(!cir.ptr, !cir.ptr, !cir.ptr, !s64i) -> !cir.ptr + cir.func private @__cxa_bad_cast() + cir.func @test(%arg0 : !cir.ptr) { + // expected-error@+1 {{destRtti must be an RTTI pointer}} + %0 = cir.dyn_cast ptr %arg0 : !cir.ptr -> !cir.ptr #cir.dyn_cast_info : !cir.ptr, dest_rtti = #cir.global_view<@_ZTI7Derived> : !cir.ptr, runtime_func = @__dynamic_cast, bad_cast_func = @__cxa_bad_cast, offset_hint = #cir.int<0> : !s64i> + } +}