Skip to content

Map types.NoneType return type to LLVM void instead of i8*#847

Open
isVoid wants to merge 5 commits into
NVIDIA:mainfrom
isVoid:fix-void-type-model-845
Open

Map types.NoneType return type to LLVM void instead of i8*#847
isVoid wants to merge 5 commits into
NVIDIA:mainfrom
isVoid:fix-void-type-model-845

Conversation

@isVoid
Copy link
Copy Markdown
Contributor

@isVoid isVoid commented Apr 3, 2026

Summary

Fixes #845: types.void / types.NoneType was mapped to OpaqueModel, which used i8* for all LLVM type queries including the return type. This caused C-ABI device functions returning void to emit i8* foo(...) instead of void foo(...), breaking ABI compatibility and LTO linking with external C/C++ device code.

NoneTypeModel (numba_cuda/numba/cuda/models.py):

  • Registered in cuda_data_manager, which shadows the upstream OpaqueModel for types.NoneType in default_manager via ChainMap priority.
  • get_return_type() returns ir.VoidType() — used when building ir.FunctionType for function signatures.
  • get_value_type() returns i8* (opaque pointer) — LLVM void cannot represent values, so None still needs a concrete type for variable assignment, alloca, store/load, and boxing/unboxing.

Calling convention updates (numba_cuda/numba/cuda/core/callconv.py):

  • Numba ABI (CUDACallConv.get_return_type): uses a dummy i8** return slot when the data model return type is void, preserving the existing i32 func(retslot*, args...) signature.
  • C ABI (CUDACABICallConv.return_value): emits ret_void when the function return type is void.
  • C ABI (CUDACABICallConv.call_function): synthesizes a null i8* value for void call results instead of using the void instruction as a value.

Cleanup (numba_cuda/numba/cuda/datamodel/cuda_models.py):

  • Removed the types.NoneType registration from OpaqueModel so there is no stale fallback in default_manager for the standalone path.

Test plan

  • test_nonetype_model_return_type_is_void — data model get_return_type() returns VoidType
  • test_nonetype_model_value_type_is_opaque_ptrget_value_type() returns PointerType
  • test_cabi_void_device_function_signature — C-ABI void function links and runs correctly
  • test_void_device_function_numba_abi — Numba-ABI void device function compiles and runs
  • test_void_device_function_with_side_effect — void device function with array mutation
  • 160 existing tests across device_func, compiler, inspect, extending, overload, dispatcher pass with no regressions

@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented Apr 3, 2026

Auto-sync is disabled for ready for review pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@isVoid isVoid force-pushed the fix-void-type-model-845 branch 3 times, most recently from b4296c1 to d195553 Compare April 3, 2026 19:54
Add NoneTypeModel for types.NoneType that returns ir.VoidType() from
get_return_type() while keeping i8* for get_value_type() (since LLVM
void cannot represent values). This ensures CABI device functions
returning void have correct LLVM IR signatures without unnecessary
return pointer arguments.

Also update the Numba and CABI calling conventions to handle void
return types:
- Numba ABI: use a dummy i8** return slot for void functions
- CABI: emit ret_void and synthesize null value for void returns

Add tests verifying the data model, Numba ABI, and CABI void returns.

Closes NVIDIA#845

Made-with: Cursor
@isVoid isVoid force-pushed the fix-void-type-model-845 branch from d195553 to 81cb6d2 Compare April 3, 2026 20:44
isVoid added 2 commits April 3, 2026 13:56
Add a note to the NoneTypeModel docstring explaining that it shadows
the OpaqueModel registration for types.NoneType in default_manager
via ChainMap priority in cuda_data_manager.

Made-with: Cursor
NoneTypeModel in models.py (cuda_data_manager) is the single source
of truth for the NoneType data model. Remove the OpaqueModel
registration for types.NoneType in cuda_models.py so there is no
incorrect fallback in default_manager for the standalone path.

Made-with: Cursor
Comment on lines +107 to +108
if isinstance(restype, ir.VoidType):
return ir.IntType(8).as_pointer().as_pointer()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need to return a placeholder value type because void type cannot be used in declaration for argument type. This also aligns with how the rest of Numba internally passes arguments around.

@isVoid isVoid changed the title Map types.NoneType to LLVM void instead of i8* Map types.NoneType return type to LLVM void instead of i8* Apr 3, 2026
@isVoid
Copy link
Copy Markdown
Contributor Author

isVoid commented Apr 3, 2026

/ok to test 2fffff5


def test_cabi_void_device_function_signature(self):
consume = cuda.declare_device(
"consume", "void(int32)", link=consume_cabi_cu, abi="c"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This by itself does not guarantee that it was inlined. In fact that code used to work properly before. Could we add tests that verify ir signature both for callee and caller

@isVoid isVoid self-assigned this Apr 6, 2026
Copy link
Copy Markdown
Contributor

@gmarkall gmarkall left a comment

Choose a reason for hiding this comment

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

It's not clear to me that this is a fix that should be made - see #845 (comment)

@isVoid isVoid force-pushed the fix-void-type-model-845 branch from a919957 to 62e1220 Compare April 8, 2026 17:24
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.

[BUG] types.void is translated to i8* instead of LLVM void type, causing ABI and LTO mismatches

3 participants