[IR] Don't allow values of opaque type (#137625)

Consider opaque types as non-first-class types, i.e. do not allow SSA
values to have opaque type.
This commit is contained in:
Nikita Popov
2025-04-30 15:01:00 +02:00
committed by GitHub
parent f1248d6347
commit 6feb4a8ef4
26 changed files with 87 additions and 114 deletions

View File

@@ -4122,6 +4122,30 @@ except :ref:`label <t_label>` and :ref:`metadata <t_metadata>`.
| ``{i32, i32} (i32)`` | A function taking an ``i32``, returning a :ref:`structure <t_struct>` containing two ``i32`` values |
+---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+
.. _t_opaque:
Opaque Structure Types
----------------------
:Overview:
Opaque structure types are used to represent structure types that
do not have a body specified. This corresponds (for example) to the C
notion of a forward declared structure. They can be named (``%X``) or
unnamed (``%52``).
It is not possible to create SSA values with an opaque structure type. In
practice, this largely limits their use to the value type of external globals.
:Syntax:
::
%X = type opaque
%52 = type opaque
@g = external global %X
.. _t_firstclass:
First Class Types
@@ -4562,31 +4586,6 @@ opaqued and are never uniqued. Identified types must not be recursive.
| ``<{ i8, i32 }>`` | A packed struct known to be 5 bytes in size. |
+------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
.. _t_opaque:
Opaque Structure Types
""""""""""""""""""""""
:Overview:
Opaque structure types are used to represent structure types that
do not have a body specified. This corresponds (for example) to the C
notion of a forward declared structure. They can be named (``%X``) or
unnamed (``%52``).
:Syntax:
::
%X = type opaque
%52 = type opaque
:Examples:
+--------------+-------------------+
| ``opaque`` | An opaque type. |
+--------------+-------------------+
.. _constants:
Constants

View File

@@ -286,9 +286,7 @@ public:
/// Return true if the type is "first class", meaning it is a valid type for a
/// Value.
bool isFirstClassType() const {
return getTypeID() != FunctionTyID && getTypeID() != VoidTyID;
}
bool isFirstClassType() const;
/// Return true if the type is a valid type for a register in codegen. This
/// includes all first-class types except struct and array types.

View File

@@ -247,6 +247,20 @@ int Type::getFPMantissaWidth() const {
return -1;
}
bool Type::isFirstClassType() const {
switch (getTypeID()) {
default:
return true;
case FunctionTyID:
case VoidTyID:
return false;
case StructTyID: {
auto *ST = cast<StructType>(this);
return !ST->isOpaque();
}
}
}
bool Type::isSizedDerivedType(SmallPtrSetImpl<Type*> *Visited) const {
if (auto *ATy = dyn_cast<ArrayType>(this))
return ATy->getElementType()->isSized(Visited);

View File

@@ -1640,6 +1640,8 @@ Error IRLinker::run() {
if (GV.hasAppendingLinkage())
continue;
Value *NewValue = Mapper.mapValue(GV);
if (FoundError)
return std::move(*FoundError);
if (NewValue) {
auto *NewGV = dyn_cast<GlobalVariable>(NewValue->stripPointerCasts());
if (NewGV) {

View File

@@ -1,5 +1,5 @@
; Test for PR463. This program is erroneous, but should not crash llvm-as.
; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
; CHECK: use of undefined type named 'struct.none'
; CHECK: invalid type for null constant
@.FOO = internal global %struct.none zeroinitializer

View File

@@ -1,5 +1,6 @@
; RUN: llvm-as < %s | llvm-dis | llvm-as > /dev/null
; RUN: verify-uselistorder %s
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: error: invalid type for undef constant
%t = type opaque
@x = global %t undef

View File

@@ -1,7 +1,7 @@
; The assembler should catch an undefined argument type .
; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
; CHECK: use of undefined type named 'typedef.bc_struct'
; CHECK: invalid type for function argument
; %typedef.bc_struct = type opaque

View File

@@ -7,4 +7,4 @@ define void @load_extern(%externref %ref) {
ret void
}
; CHECK-ERROR: error: loading unsized types is not allowed
; CHECK-ERROR: error: load operand must be a pointer to a first class type

View File

@@ -7,4 +7,4 @@ define void @store_extern(%externref %ref) {
ret void
}
; CHECK-ERROR: error: storing unsized types is not allowed
; CHECK-ERROR: error: invalid type for undef constant

View File

@@ -3,6 +3,7 @@
; RUN: llc < %s -mtriple=x86_64-w64-windows-gnu | FileCheck %s -check-prefix=X64
; Control Flow Guard is currently only available on Windows
%struct.HVA = type { double, double, double, double }
; Test that Control Flow Guard checks are correctly added for x86_64 vector calls.
define void @func_cf_vector_x64(ptr %0, ptr %1) #0 {
@@ -37,8 +38,6 @@ entry:
}
attributes #0 = { "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" }
%struct.HVA = type { double, double, double, double }
declare void @llvm.memcpy.p0.p0.i64(ptr nocapture writeonly, ptr nocapture readonly, i64, i1 immarg) #1
attributes #1 = { argmemonly nounwind willreturn }

View File

@@ -2,6 +2,7 @@
; RUN: llc < %s -mtriple=i686-w64-windows-gnu | FileCheck %s -check-prefix=X86
; Control Flow Guard is currently only available on Windows
%struct.HVA = type { double, double, double, double }
; Test that Control Flow Guard checks are correctly added for x86 vector calls.
define void @func_cf_vector_x86(ptr %0, ptr %1) #0 {
@@ -32,8 +33,6 @@ entry:
}
attributes #0 = { "target-cpu"="pentium4" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" }
%struct.HVA = type { double, double, double, double }
declare void @llvm.memcpy.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #1
attributes #1 = { argmemonly nounwind willreturn }

View File

@@ -1,6 +0,0 @@
%struct.A = type { %struct.B }
%struct.B = type opaque
define i32 @foo(%struct.A** %A) {
ret i32 0
}

View File

@@ -1,6 +1,4 @@
%struct.A = type { %struct.B }
%struct.B = type opaque
define i32 @bar(%struct.A %A) {
ret i32 0
}
@g = external global %struct.A

View File

@@ -1,6 +1,4 @@
%t = type { i8 }
%t2 = type { %t, i16 }
define %t2 @f() {
ret %t2 { %t { i8 0 }, i16 0 }
}
@g = external global %t2

View File

@@ -7,8 +7,8 @@
; Make sure we can link files with clashing intrinsic names using unnamed types.
;--- f01.ll
%1 = type opaque
%0 = type opaque
%1 = type { i32 }
%0 = type { i64 }
; CHECK-LABEL: @test01(
; CHECK: %c1 = call %0 @llvm.ssa.copy.s_s.0(%0 %arg)
@@ -38,8 +38,8 @@ bb:
}
;--- f02.ll
%1 = type opaque
%2 = type opaque
%1 = type { i8 }
%2 = type { i16 }
; CHECK-LABEL: @test03(
; CHECK: %c1 = call %3 @llvm.ssa.copy.s_s.2(%3 %arg)

View File

@@ -1,10 +1,12 @@
; RUN: not llvm-link -S -o - %p/pr22807.ll %p/Inputs/pr22807-1.ll %p/Inputs/pr22807-2.ll 2>&1 | FileCheck %s
; RUN: not llvm-link -S -o - %p/pr22807.ll %p/Inputs/pr22807.ll 2>&1 | FileCheck %s
; CHECK: error: identified structure type 'struct.A' is recursive
%struct.B = type { %struct.A }
%struct.A = type opaque
define i32 @baz(%struct.B %BB) {
ret i32 0
@g = external global %struct.B
define ptr @test() {
ret ptr @g
}

View File

@@ -4,13 +4,13 @@
; not cause %u and %t to get merged.
; CHECK: %u = type opaque
; CHECK: define %u @g(%u %a) {
; CHECK: external global %u
%u = type opaque
%u2 = type { %u, i8 }
declare %u2 @f()
@g = external global %u
define %u @g(%u %a) {
ret %u %a
define ptr @test() {
ret ptr @g
}

View File

@@ -3,11 +3,4 @@ target triple = "x86_64-apple-macosx10.11.0"
%a = type { i8 }
define void @bar(%a) {
ret void
}
define void @baz() {
call void @bar(%a undef)
ret void
}
@g = external global %a

View File

@@ -3,23 +3,18 @@
; RUN: opt -module-summary %p/Inputs/import_opaque_type.ll -o %t2.bc
; RUN: llvm-lto -thinlto-action=thinlink -o %t3.bc %t.bc %t2.bc
; Check that we import correctly the imported type to replace the opaque one here
; RUN: llvm-lto -thinlto-action=import %t.bc -thinlto-index=%t3.bc -o - | llvm-dis -o - | FileCheck %s
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.11.0"
; CHECK: %a = type { i8 }
; FIXME: It would be better to produce %a = type { i8 } here
; CHECK: %a = type opaque
%a = type opaque
declare void @baz()
define void @foo(%a) {
call void @baz()
ret void
}
@g = external global %a
define i32 @main() {
call void @foo(%a undef)
ret i32 0
define ptr @test() {
ret ptr @g
}

View File

@@ -46,7 +46,7 @@ define i1 @cmp_gv_weak_alloca() {
}
%opaque = type opaque
@gv_unsized = weak global %opaque zeroinitializer, align 16
@gv_unsized = external global %opaque, align 16
define i1 @cmp_gv_unsized_alloca() {
; CHECK-LABEL: define i1 @cmp_gv_unsized_alloca() {

View File

@@ -1,24 +1,22 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -passes=reg2mem -S < %s | FileCheck %s
%opaque = type opaque
declare %opaque @ret_opaque()
declare void @pass_opaque(%opaque)
declare target("opaque") @ret_opaque()
declare void @pass_opaque(target("opaque"))
define void @test() {
; CHECK-LABEL: @test(
; CHECK-NEXT: %"reg2mem alloca point" = bitcast i32 0 to i32
; CHECK-NEXT: [[X:%.*]] = call [[OPAQUE:%.*]] @ret_opaque()
; CHECK-NEXT: [[X:%.*]] = call target("opaque") @ret_opaque()
; CHECK-NEXT: br label [[NEXT:%.*]]
; CHECK: next:
; CHECK-NEXT: call void @pass_opaque([[OPAQUE]] [[X]])
; CHECK-NEXT: call void @pass_opaque(target("opaque") [[X]])
; CHECK-NEXT: ret void
;
%x = call %opaque @ret_opaque()
%x = call target("opaque") @ret_opaque()
br label %next
next:
call void @pass_opaque(%opaque %x)
call void @pass_opaque(target("opaque") %x)
ret void
}

View File

@@ -2,9 +2,7 @@
; CHECK: unsized types cannot be used as memset patterns
%X = type opaque
define void @bar(ptr %P, %X %value) {
call void @llvm.experimental.memset.pattern.p0.s_s.i32.0(ptr %P, %X %value, i32 4, i1 false)
define void @bar(ptr %P, target("foo") %value) {
call void @llvm.experimental.memset.pattern.p0.s_s.i32.0(ptr %P, target("foo") %value, i32 4, i1 false)
ret void
}
declare void @llvm.experimental.memset.pattern.p0.s_s.i32.0(ptr nocapture, %X, i32, i1) nounwind

View File

@@ -45,16 +45,6 @@ define nofpclass(nan) [4 x <8 x i32>] @nofpclass_vector_array_int([4 x <8 x i32>
ret [4 x <8 x i32>] %arg
}
%opaque = type opaque
; CHECK: 'nofpclass(nan)' applied to incompatible type!
; CHECK-NEXT: ptr @nofpclass_opaque_type
; CHECK-NEXT: 'nofpclass(zero)' applied to incompatible type!
; CHECK-NEXT: ptr @nofpclass_opaque_type
define nofpclass(nan) %opaque @nofpclass_opaque_type(%opaque nofpclass(zero) %arg) {
ret %opaque %arg
}
%struct = type { i32, float }
; CHECK: 'nofpclass(nan)' applied to incompatible type!

View File

@@ -1,10 +1,8 @@
; RUN: not opt -passes=verify < %s 2>&1 | FileCheck %s
%X = type opaque
define void @f_0(ptr %ptr) {
%t = load %X, ptr %ptr
%t = load target("foo"), ptr %ptr
ret void
; CHECK: loading unsized types is not allowed
; CHECK-NEXT: %t = load %X, ptr %ptr
; CHECK-NEXT: %t = load target("foo"), ptr %ptr
}

View File

@@ -1,10 +1,8 @@
; RUN: not opt -passes=verify < %s 2>&1 | FileCheck %s
%X = type opaque
define void @f_1(%X %val, ptr %ptr) {
store %X %val, ptr %ptr
define void @f_1(target("foo") %val, ptr %ptr) {
store target("foo") %val, ptr %ptr
ret void
; CHECK: storing unsized types is not allowed
; CHECK-NEXT: store %X %val, ptr %ptr
; CHECK-NEXT: store target("foo") %val, ptr %ptr
}

View File

@@ -380,9 +380,8 @@ TEST(OperationsTest, GEPPointerOperand) {
// Check that we only pick sized pointers for the GEP instructions
LLVMContext Ctx;
const char *SourceCode = "%opaque = type opaque\n"
"declare void @f()\n"
"define void @test(%opaque %o) {\n"
const char *SourceCode = "declare void @f()\n"
"define void @test(target(\"foo\") %o) {\n"
" %a = alloca i64, i32 10\n"
" ret void\n"
"}";