Skip to content

@cppvtable: methods returning a record through MSVC's hidden pointer - #551

Merged
ASDAlexander77 merged 3 commits into
mainfrom
cppvtable-hidden-return
Oct 11, 2026
Merged

ASDAlexander77 merged 3 commits into
mainfrom
cppvtable-hidden-return

Conversation

@ASDAlexander77

@ASDAlexander77 ASDAlexander77 commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

A @cppvtable method can now be declared to return a struct by value, such as NVRHI's RefCountPtr<T> or nvrhi::Object. You declare the return type as a tuple with the struct's layout:

type RefCountPtr = [pointer: Opaque | null];       // nvrhi::RefCountPtr<T>
createBuffer(desc: Ref<BufferDesc>): RefCountPtr;  // const handle = device.createBuffer(Ref(desc));

MSVC returns any struct or class from a C++ instance method through a hidden pointer. The pointer is passed after this, and the method returns it as well (clang's MicrosoftCXXABI::classifyReturnType). Until now, tslang code had to declare that pointer as a parameter itself: createBuffer(ret: Ref<Opaque | null>, desc: Ref<BufferDesc>): Opaque.

How it works

  • The method's slot type: mlirGenClassCppVTableHiddenReturn changes the slot type to (this, Ref<T>, args...) -> Ref<T>. The method's info still records the declared type, so DeclarationPrinter prints that and an importing module lowers it again.
  • The call: a call creates a local of T and passes it after this. The call's value is that local, read back. The arguments' contextual types are shifted by one.
  • Non-MSVC targets: the Itanium ABI passes the pointer before this, and only for some structs. On a target without TargetInfo::hiddenReturnPointerAfterThis (any non-MSVC target), such a method is a compile error.
  • Method stored in a variable: if the method is stored in a variable and called later, the hidden pointer is not added for you; it is the first argument. Call the method directly instead. The docs say so.

Tests

  • 00cppvtable_hidden_return.ts (compiled and JIT): returns an 8-byte and a 24-byte struct, with a plain scalar method in the same vtable. It also calls a base class's struct-returning method through a derived @cppvtable class.

  • export_cppvtable_hidden_return.ts / import_cppvtable_hidden_return.ts (compiled and JIT): a method declared in the exporting module returns a struct, called from both modules.

  • These tests run on Windows only (if (WIN32)), because on any other target such a method is an error.

  • cppvtable/hidden_return_target.ts: checks the compile error on x86_64-unknown-linux-gnu.

  • Full suite: 3999/3999 pass (ctest -C Release, windows-msbuild-2026-release).

  • Real NVRHI: in a temporary copy of native_ts_graphics' core/nvrhi.ts, I switched these to the new form:

    • IDevice::createBuffer and IDevice::createCommandList, returning RefCountPtr;
    • IResource::getNativeObject, returning nvrhi::Object.

    examples/nvrhi_direct.ts passed on D3D12 and on Vulkan, with the debug layers on (-debug).

After this is in a release

native_ts_graphics' core/nvrhi.ts can drop the explicit ret parameters: createBuffer, createTexture, createCommandList, createComputePipeline, getNativeObject and getNativeQueue can return RefCountPtr or NativeObject tuples.

🤖 Generated with Claude Code

ASDAlexander77 and others added 3 commits October 11, 2026 20:10
MSVC returns a record (struct or class, RefCountPtr<T> included) from a C++
instance method through a hidden pointer. The pointer is passed after `this`
and returned as well (clang's MicrosoftCXXABI::classifyReturnType). A
@cppvtable method declared to return a tuple now uses that convention:

- its slot is called as (this, Ref<T>, args...) -> Ref<T>;
- a call passes a local of T and reads it back, so the call's value is the
  record;
- DeclarationPrinter keeps the declared type, so an importing module lowers
  it again.

Before this, the hidden pointer had to be declared as a parameter.

The Itanium ABI passes the pointer before `this`, and only for some records,
so on a non-MSVC target such a method is an error
(TargetInfo::hiddenReturnPointerAfterThis).

Tests:
- 00cppvtable_hidden_return.ts covers 8- and 24-byte records and a scalar slot
  next to them, compiled and JIT;
- export/import_cppvtable_class cover a record returned across modules;
- cppvtable/hidden_return_target.ts checks the non-MSVC error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ved class

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant