Skip to content

Conversation

@mahmood82
Copy link
Contributor

Implement handling for zero-initialization casts to OpenCL opaque types in CIR. This covers cases like event_t e = async_work_group_copy(..., 0).

  • VisitCastExpr: CK_ZeroToOCLOpaqueType now returns a null pointer of the appropriate opaque type instead of llvm_unreachable.
  • CIRGenTypes::convertType: Added proper CIR type conversions for OpenCL opaque types including event, queue, and reserve_id types.
  • Provides consistent CIR representation for OpenCL opaque objects.

Implement handling for zero-initialization casts to OpenCL opaque types
in CIR. This covers cases like `event_t e = async_work_group_copy(..., 0)`.

- `VisitCastExpr`: CK_ZeroToOCLOpaqueType now returns a null pointer of the
  appropriate opaque type instead of `llvm_unreachable`.
- `CIRGenTypes::convertType`: Added proper CIR type conversions for OpenCL
  opaque types including event, queue, and reserve_id types.
- Provides consistent CIR representation for OpenCL opaque objects.
Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a testcase

@mahmood82 mahmood82 force-pushed the users/mahmood/ocl_opaque_types branch from a85d27e to e808d7f Compare November 25, 2025 11:24
@mahmood82
Copy link
Contributor Author

mahmood82 commented Nov 25, 2025

Please add a testcase

I added llvm-project/clang/test/CIR/CodeGen/OpenCL/async_copy.cl. However, it require changes in my PR #2019

In addition I commented out some logic as I keep hitting this assert:

llvm_unreachable("Requires address space cast which is NYI");

@RiverDave claimed it will be fixed in his PR: #1986

Have said this, async_copy.cl as is is enough for the changes I made in this PR.

@mahmood82 mahmood82 force-pushed the users/mahmood/ocl_opaque_types branch from e808d7f to 0eb9550 Compare November 25, 2025 13:40
Copy link
Collaborator

@seven-mile seven-mile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: There's also an issue #802 tracking the design of OpenCL opaque types.

It's not necessary to give a final design for them, but we'd better avoid potential miscompilation here!😉


// CIR: cir.call @_Z21async_work_group_copyPU3AS3iPU3AS1Kim9ocl_event(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) : (!cir.ptr<!s32i, addrspace(offload_local)>, !cir.ptr<!s32i, addrspace(offload_global)>, !u64i, !cir.ptr<!void>) -> !cir.ptr<!void>
// LLVM: call spir_func ptr @_Z21async_work_group_copyPU3AS3iPU3AS1Kim9ocl_event(ptr addrspace(3) %{{.*}}, ptr addrspace(1) %{{.*}}, i64 %{{.*}}, ptr null)
// OG-LLVM: call spir_func target("spirv.Event") @_Z21async_work_group_copyPU3AS3iPU3AS1Kim9ocl_event(ptr addrspace(3) noundef %{{.*}}, ptr addrspace(1) noundef %{{.*}}, i64 noundef %{{.*}}, target("spirv.Event") zeroinitializer No newline at end of file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a difference between ours and the original LLVM (target("spirv.Event") vs ptr addrspace(0)). Is this expected? (I believe the assertion about address space cast you encountered is also a little alarm for us.)

Simply using ptr addrspace(0) here usually works. But the original RFC of LLVM opaque types mentions that it's actually undefined behavior in LLVM IR.

@RiverDave
Copy link
Collaborator

Please add a testcase

I added llvm-project/clang/test/CIR/CodeGen/OpenCL/async_copy.cl. However, it require changes in my PR #2019

In addition I commented out some logic as I keep hitting this assert:

llvm_unreachable("Requires address space cast which is NYI");

@RiverDave claimed it will be fixed in his PR: #1986

Have said this, async_copy.cl as is, is enough for the changes I made in this PR.

If this ever becomes a huge blocker, I believe it can be implemented using the existing code in the incubator. I'll try to land my PR's from upstream by the end of the week/early next (hopefully).

@bcardosolopes
Copy link
Member

@mahmood82 can you implement this as if #1986 isn't happening? Whenever @RiverDave works on it he'd have to change it to conform to his work. Even if slightly not ideal CIR/CodeGen/OpenCL/async_copy.cl should emit something and have CIR, LLVM and OGCG tests. You can also use assert on missing feature to improve certain aspects while that PR doesn't land

Simply using ptr addrspace(0) here usually works. But the original RFC of LLVM opaque types mentions that it's actually undefined behavior in LLVM IR.

maybe you could do this until it lands?

@mahmood82
Copy link
Contributor Author

#1986 is not a blocker here, and it’s not restricting the main feature of this PR (defining the OCL event type and passing zero to async_work_group_copy). The related assertion also happens on something as simple as int gid = ..., so it is orthogonal. Bottom line: it does not block this PR.

When I generate LLVM IR without CIR in the pipeline, my non-SPIRV64 target produces:

%call = call ptr @_Z21async_work_group_copyPU3AS3iPU3AS1Kij9ocl_event(ptr addrspace(3) noundef %0, ptr addrspace(1) noundef %1, i32 noundef %2, ptr null) #2

Notice that the OpenCL event becomes a plain ptr with no extra attributes.

SPIR-V uses a target-specific path:
CGOpenCLRuntime::convertOpenCLSpecificType ==> CommonSPIRTargetCodeGenInfo::getOpenCLType.

A robust solution for CIR therefore seems to require preserving the OCL type identity through AST->CIR, and letting the CIR->LLVM lowering eventually call into getOpenCLType. To enable that, I need to replace the AST->CIR lowering with something like:

ResultType = cir::OCLEventType::get(Builder.getContext());

(and do the same for the other OpenCL builtin types).

I initially attempted to define such a type in clang/include/clang/CIR/Dialect/IR/CIRTypes.td:

def CIR_OCLEventType : CIR_Type<"OCLEvent", "ocl.event"> {
  let summary = "OpenCL opaque event type";
}

Got this CIR result:

    %7 = "cir.const"() <{value = #cir.ptr<null> : !cir.ptr<!cir.ocl.event, addrspace(offload_private)>}> : () -> !cir.ptr<!cir.ocl.event, addrspace(offload_private)>
    %8 = "cir.cast"(%7) <{kind = 1 : i32}> : (!cir.ptr<!cir.ocl.event, addrspace(offload_private)>) -> !cir.ocl.event
	%9 = "cir.call"(%3, %4, %6, %8) <{ast = #cir.call.expr.ast, callee = @_Z21async_work_group_copyPU3AS3iPU3AS1Kim9ocl_event, calling_conv = 3 : i32, extra_attrs = #cir<extra({convergent 

But that fails verification with:

error: 'cir.cast' op result #0 must be Integer type with arbitrary precision up to a fixed limit or CIR pointer type or CIR type that represents pointer-to-data-member type in C++ or CIR type that represents C++ pointer-to-member-function type or CIR bool type or CIR array type or CIR vector type or CIR function type or CIR void type or CIR record type or CIR exception info or single float type or double float type or f16 type or bf16 type or f80 type or f128 type or long double type or CIR complex type or CIR type that is used for the vptr member of C++ objects, but got '!cir.ocl.event'

I would appreciate guidance on whether this direction is correct, and on how best to complete the addition of the new OCLEventType so that it fits into the existing CIR type system and cast semantics. Thank you.

@seven-mile
Copy link
Collaborator

We usually think of it by investigating AST and LLVM IR. In this case, we have

AST:

CStyleCastExpr <line:10:9, col:18> 'event_t' <ZeroToOCLOpaqueType>
  `-IntegerLiteral <col:18> 'int' 0

LLVM IR:

target("spirv.Event") zeroinitializer

This basically indicates two approaches: extend CastOp or just add the type and use a special value (an MLIR attribute).

In general practice, we align with LLVM IR when it does not lose actual information, which makes code simpler. You just need to extend ZeroInitAttr to get the special value.

mlir::TypedAttr CIRGenModule::emitNullConstant(QualType T) {
if (T->getAs<PointerType>()) {
return builder.getConstNullPtrAttr(getTypes().convertTypeForMem(T));
}
if (getTypes().isZeroInitializable(T))
return builder.getZeroInitAttr(getTypes().convertTypeForMem(T));


But I believe it's more worthwhile to discuss how we design the type itself. As you may see, we generally do not know anything about the internals of the ocl.event type — only target intrinsics understand them. That's why they are represented as opaque types in LLVM IR. So, I guess the construct !cir.ptr<!cir.ocl.event, addrspace(offload_private)> in your very first attempt may not fit well in this case.

Introducing a full equivalent to llvm::TargetExtType sounds okay, but it provides quite a number of customizations to cover various potential use cases:

enum Property {
/// zeroinitializer is valid for this target extension type.
HasZeroInit = 1U << 0,
/// This type may be used as the value type of a global variable.
CanBeGlobal = 1U << 1,
/// This type may be allocated on the stack, either as the allocated type
/// of an alloca instruction or as a byval function parameter.
CanBeLocal = 1U << 2,
/// This type may be used as an element in a vector.
CanBeVectorElement = 1U << 3,
// This type can only be used in intrinsic arguments and return values.
/// In particular, it cannot be used in select and phi instructions.
IsTokenLike = 1U << 4,
};

In order to make this patch not too complicated to land, my suggestion would be to introduce a minimal opaque type that fits OpenCL's use, but name it generally, e.g., !cir.opaque. Leave the advanced customizable features for future work. We could take LLVM dialect as a reference:

def LLVMTargetExtType : LLVMType<"LLVMTargetExt", "target"> {


And a debatable part is the parameterization of the opaque type. LLVM opaque types mainly accept a name tag, many subtypes, and many magic integers, which is quite frightening:

opaque("spirv.Image", void, i32 0, i32 0, i32 0, i32 0, i32 0, i32 0, i32 0)

NOTE: We don't have any information loss here. It's just a readability issue.

When we really want to improve readability, we could somehow insert a type alias automatically, just like what we already did for !s32i = !cir.int<s, 32>. Detailed alias rules could be inferred from here.

My suggestion is for your reference; you may want to wait for further input from @bcardosolopes 😉.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants