Skip to content

@cppvtable: classes implementing C++ methods (C++ calls them through their vtable) - #554

Merged
ASDAlexander77 merged 1 commit into
mainfrom
cppvtable-implement
Oct 11, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
cppvtable-implement

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

@cppvtable classes so far only described C++ objects that C++ creates, so tslang could call them. With this PR, a @cppvtable class whose methods have bodies implements a C++ interface: tslang creates the object and C++ calls it. The first use is nvrhi::IMessageCallback, NVRHI's message sink:

@cppvtable
class IMessageCallback {               // nvrhi::IMessageCallback (MSVC)
    _destructor(): void;               // slot 0
    message(severity: u8, text: string): void;
}

@cppvtable
class MessageLog extends IMessageCallback {
    errors = 0;
    message(severity: u8, text: string): void {
        if (severity >= 2) { this.errors++; }
    }
}

const log = new MessageLog();          // hand <Opaque>log to C++

How it works

  • ClassInfo::isCppVTableImplementation: set for a @cppvtable class with a method that has a body. Such a class gets:

    • a default constructor;
    • .new;
    • the .size static;
    • a vtable definition.

    It still gets no .instanceOf or RTTI, so the vtable is exactly the C++ slots.

  • Vtable slots: a method with a body overrides the base's slot of the same name, and a constructor never takes a slot. A slot left to a method of the declared interface (no body, such as a destructor) is null, as a pure virtual's would be.

  • Object layout: new stores the vtable's address in the object's first word, where C++ expects its vtable pointer. The interface's fields and then the class's own fields follow.

  • Narrow results: methods with bodies are marked ts.cpp_method. LowerToLLVM gives their narrow results (boolean, u8, ...) zeroext/signext, because C++ callers rely on the callee widening them. Other definitions are unchanged.

  • Errors:

    • a method with a body returning a struct (not supported yet: the body would have to write through the hidden pointer);
    • an exported implementation (an importer would get the methods but not the vtable).

    Statics, accessors, implements, generics and rc/own stay errors as before. cppvtable/method_body.ts, whose error a body no longer triggers, now tests the struct-return error.

Lifetime and limits

These are documented in docs/cppvtable-classes.md, "Implementing C++ methods":

  • C++ keeps a pointer the collector can't see, so the object must be held in tslang, for example in a global, while C++ may call it.
  • A string argument is C++'s buffer, valid during the call only.
  • There is no RTTI: C++ must not dynamic_cast or typeid the object.
  • Don't throw from a method C++ calls.

Tests

  • test-jit-cppvtable-abi: cppvtable/abi_host.cpp gains a C++ Callback interface and functions that call it. Counter in abi_msvc.ts and abi_itanium.ts implements it, with a constructor and fields. C++ calls message() twice (checking the text), count(), and isEven() for a bool result; tslang then calls it through the interface type too.
    • Passes under MSVC (local).
    • Under Itanium, abi_itanium.ts linked with a g++-built abi_host.cpp passes in WSL Ubuntu.
  • test-compile-cppvtable-itanium-implementation-<triple> (x86-64 Linux, AArch64 Android): checks the vtable is { null, null, message, count, isEven } and that isEven is zeroext i1.
  • Error tests: cppvtable-method_body (struct return) and cppvtable-export_implementation.
  • Full suite (Windows): 4012/4012.
  • Real NVRHI: native_ts_graphics' nvrhi_direct implements nvrhi::IMessageCallback this way.
    • On Windows with -debug, D3D12 and Vulkan both receive a provoked validation error and pass.
    • On the Android x86_64 emulator, NVRHI holds the object, but message() isn't exercised there, since there's no validation layer.

🤖 Generated with Claude Code

… their vtable

A @cppvtable class whose methods have bodies implements C++ methods, for
example nvrhi::IMessageCallback:
- it extends the declared C++ interface and overrides its slots by name;
- its objects are allocated by tslang and carry a vtable with the C++ layout:
  the slots and nothing else;
- a slot left to a method of the interface (one with no body) is null.

It may have a constructor and field initializers, and still has no RTTI. A
method with a body is marked ts.cpp_method, and LowerToLLVM extends its narrow
results as C++ callers expect.

Not yet supported (both errors): returning a struct, and exporting the class.

Tests:
- abi_host.cpp's Callback, implemented by Counter in abi_msvc.ts and
  abi_itanium.ts and called by C++ (MSVC locally, g++ in WSL);
- the Itanium vtable and the bool result's zeroext, checked in IR for x86-64
  Linux and AArch64 Android;
- errors for a struct return from a body and for an exported
  implementation. cppvtable/method_body.ts now tests the struct-return error,
  since a body is no longer an error.

Full suite: 4012/4012.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 759e0ef into main Oct 11, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the cppvtable-implement branch October 11, 2026 22:07
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