Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,13 @@ declare function dist(p: Reference<Point> | null): f64;

A pointer parameter or struct field is nullable (`| null`), as C lets it be null; a C++ reference (`const Point &`) and a result aren't.

A struct is a named tuple with its fields where C puts them; tslang lays a nested tuple out in place, as C does a struct member:

- **an array** `T x[N]` is a tuple of its elements, `x: [_0: T, _1: T, ...]` (tslang has no fixed-size array type; one of more than 1024 elements, or of unknown size, is skipped);
- **a union** is its first member when that alone has the union's size and alignment (`union { void *p; uint64_t n; }` is `p: Opaque | null`), raw words otherwise (`union { char buf[16]; char *ptr; }` is `[_0: u64, _1: u64]`); an anonymous one is named after its first member, an anonymous struct's fields are the struct's own;
- **a run of bitfields** is raw words over the bytes it uses, named after its first bitfield (`unsigned a : 1; unsigned b : 7;` is `a: [_0: u32]`): set and read the bits yourself;
- a struct with an `alignas` alignment beyond its fields' (`DECLSPEC_ALIGN(16)`), or packed, is skipped.

Include the result with `/// <reference path="simple.ts" />`, not `import`. Name it `.ts`, not `.d.ts`: a `.d.ts` file is read as declarations only, so its constants would have no value.

### When nothing is emitted
Expand Down Expand Up @@ -675,7 +682,7 @@ declare function add(a: s32, b: s32): s32;
declare function dist(p: Reference<Point> | null): f64;
```

Structs, enums and `extern "C"` functions are emitted, and polymorphic classes (below). A struct or a class without virtual methods, in any namespace or class, is a tuple when its fields are where a tuple's are: constructors, methods and member initializers don't move them, so `struct Color { float r, g, b, a; Color(); }` is `type Color = [r: f32, g: f32, b: f32, a: f32];`. It is declared under its own name, and `--filter` matches that or the qualified name (`--filter 'nvrhi::*'`); of two of one name in different namespaces, the next is named with its namespaces (`rt_Desc` for `nvrhi::rt::Desc` under `--namespace nvrhi`). One that isn't a tuple is `type Name = Opaque;`, with the reason in a `// skipped:` line: a derived class, or a field tsbindgen can't spell (a template's specialization such as `std::string` or `std::vector<T>`, an array, a bitfield, a union). Namespace functions (`lib::helper`) and functions with C++ linkage (`cxx_only`) are left out, without a warning. To bind those, expose them through `extern "C"` wrapper functions (with an opaque handle for each class) and run tsbindgen on the header that declares them.
Structs, enums and `extern "C"` functions are emitted, and polymorphic classes (below). A struct or a class without virtual methods, in any namespace or class, is a tuple when its fields are where a tuple's are: constructors, methods and member initializers don't move them, so `struct Color { float r, g, b, a; Color(); }` is `type Color = [r: f32, g: f32, b: f32, a: f32];`. It is declared under its own name, and `--filter` matches that or the qualified name (`--filter 'nvrhi::*'`); of two of one name in different namespaces, the next is named with its namespaces (`rt_Desc` for `nvrhi::rt::Desc` under `--namespace nvrhi`). A derived struct has its bases' fields first, as C++ lays them out (an empty base has none; MSVC's tail padding after a base is a `_pad` field). A field holding a template's specialization by value declares the specialization, named by its arguments, so a library's types are laid out as that library has them: `std::string` is `basic_string_char` (MSVC's `[_Mypair: ...]` down to its `_Bx` buffer words, size and capacity; libc++'s `[__rep_: [_0: u64, _1: u64, _2: u64]]`), `nvrhi::static_vector<IBindingSet *, 8>` is `static_vector_IBindingSet_8` (its private `std::array`'s elements, then `current_size`), `RefCountPtr<IShader>` is `RefCountPtr_IShader = [ptr_: IShader | null]`. Their member names are the C++ library's own, which differ between MSVC's and libc++; a pointer to a specialization alone stays `Opaque`. One that isn't a tuple is `type Name = Opaque;`, with the reason in a `// skipped:` line: a polymorphic class, a virtual base, a packed or over-aligned layout, or a field tsbindgen can't spell (`long double`, a huge array). Namespace functions (`lib::helper`) and functions with C++ linkage (`cxx_only`) are left out, without a warning. To bind those, expose them through `extern "C"` wrapper functions (with an opaque handle for each class) and run tsbindgen on the header that declares them.

#### C++ classes

Expand Down
44 changes: 38 additions & 6 deletions tslang/include/TsClangImporter/TypeMapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace clang
class Sema;
}
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/StringSet.h"

#include <utility>
Expand Down Expand Up @@ -43,13 +44,16 @@ class TypeMapper
// itself through a pointer counts as mappable while it is being checked.
std::string recordSkipReason(const clang::RecordDecl *record);

// A complete struct's fields. A pointer field that leads back to this struct is Opaque:
// tslang cannot declare a type that refers to itself.
// A complete struct's fields, as spellRecord lays them out. A pointer field that leads back to
// this struct is Opaque: tslang cannot declare a type that refers to itself.
std::vector<Field> mapFields(const clang::RecordDecl *definition);

// The struct/enum's own name, or the typedef that names it when it has none ("" for neither).
// The struct/enum's own name, or the typedef that names it when it has none ("" for neither). A
// template's specialization is named by its arguments: `static_vector<IBindingSet *, 8>` is
// `static_vector_IBindingSet_8`.
static std::string tagName(const clang::TagDecl *tag);
// tagName with its namespaces and classes for a C++ struct out of the file scope (`nvrhi::Color`)
// tagName with its namespaces and classes for a C++ struct out of the file scope (`nvrhi::Color`);
// a specialization's has its arguments (`nvrhi::static_vector<nvrhi::IBindingSet *, 8>`)
static std::string qualifiedTagName(const clang::TagDecl *tag);
static std::string recordKey(const clang::RecordDecl *record);
static std::string enumKey(const clang::EnumDecl *enumDecl);
Expand All @@ -63,8 +67,10 @@ class TypeMapper
bool isPlainEnum(const clang::EnumDecl *enumDecl);

// Whether a struct can have a TS declaration of its own: in C, any; in C++, one in a namespace or
// a class too (declared under its own name), but not a template, a template specialization
// (std::cmatch) or a class inside one, nor a class local to a function.
// a class too (declared under its own name), but not a template, nor a class local to a
// function. A template's specialization (std::string, nvrhi::static_vector<T, N>) or a class
// inside one is declared once a field holds one by value, whose layout needs it; a pointer to
// one alone stays Opaque.
bool isDeclarable(const clang::RecordDecl *record);

// Why a C++ class can't be a @cpp class ("" if it can): it has to be polymorphic, not a
Expand Down Expand Up @@ -95,6 +101,31 @@ class TypeMapper
}

private:
// One field of a struct's tuple: its TS name and type, and where C++ puts it (bits).
struct Slot
{
std::string name;
TsType type;
uint64_t offset;
uint64_t size;
uint64_t align;
};

// The tuple a struct is, in layout order: the fields of its bases (non-virtual; an empty one has
// none), then its own. An array is a tuple of its elements; an anonymous struct's fields are
// the struct's; a union is its first member when that has the union's size and alignment, raw
// words otherwise; a run of bitfields raw words (named after the first); an empty
// [[no_unique_address]] field nothing. Returns why it isn't a tuple, or "".
std::string spellRecord(const clang::RecordDecl *definition, std::vector<Field> *fields);
// `baseEnds`: where each base's storage ends (bits), tail padding included
std::string collectSlots(const clang::RecordDecl *definition, uint64_t base, std::vector<Slot> &slots,
std::vector<uint64_t> &baseEnds);
Slot unionSlot(std::string name, const clang::RecordDecl *unionRecord, uint64_t offset);
// `[_0: u64, _1: u64, ...]`: `bytes` as words of the largest size up to `align` that fits
static TsType rawWords(uint64_t bytes, uint64_t align);
// a template's specialization, or a class inside one, complete: declarable when a field holds it
bool isInstantiated(const clang::RecordDecl *record);

TsType mapType(clang::QualType type, Use use);
std::string referTo(const clang::TagDecl *tag);
TsType mapPointer(clang::QualType pointee, Use use);
Expand All @@ -111,6 +142,7 @@ class TypeMapper
std::vector<std::pair<std::string, const clang::TagDecl *>> referenced;
llvm::StringSet<> referencedKeys;
std::vector<const clang::RecordDecl *> owners; // structs whose fields are being mapped
llvm::SmallPtrSet<const clang::RecordDecl *, 16> heldByValue; // instantiated records a field holds
};

} // namespace tsbindgen
Expand Down
3 changes: 2 additions & 1 deletion tslang/lib/TsClangImporter/BindingModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ std::string nameFromKey(const std::string &key)
{
auto colon = key.find(':');
auto name = colon == std::string::npos ? key : key.substr(colon + 1);
auto scope = name.rfind("::");
// the scope before the template arguments: `nvrhi::static_vector<nvrhi::IBindingSet *, 8>`
auto scope = name.rfind("::", name.find('<'));
return scope == std::string::npos ? name : name.substr(scope + 2);
}

Expand Down
5 changes: 3 additions & 2 deletions tslang/lib/TsClangImporter/HeaderParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,9 @@ class Collector : public clang::RecursiveASTVisitor<Collector>
return true;
}

// C++ proper is v2: only what has C linkage has a C symbol
if (context.getLangOpts().CPlusPlus && !function->isExternC())
// C++ proper is v2: only what has C linkage has a C symbol. An operator has no name to
// declare, C linkage or not (guiddef.h's operator== for GUID).
if (context.getLangOpts().CPlusPlus && (!function->isExternC() || !function->getIdentifier()))
{
return true;
}
Expand Down
Loading
Loading