diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e4f3772ad..c4e0af4d0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,7 +16,9 @@ jobs: steps: - uses: actions/checkout@v7 - name: Install mdbook - run: cargo install mdbook + run: cargo install mdbook mdbook-mermaid + - name: Install mermaid assets + run: mdbook-mermaid install docs - name: Build book run: mdbook build docs - uses: actions/upload-pages-artifact@v5 diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index aae2337f7..916cc4a1c 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1513,24 +1513,6 @@ const clang::Expr *Converter::GetParentExpr(const clang::Expr *expr) { return nullptr; } -bool Converter::IsSubExprOf(const clang::Expr *sub_expr, - const clang::Expr *parent_expr) { - if (sub_expr == nullptr || parent_expr == nullptr) - return false; - - if (parent_expr == sub_expr) - return true; - - for (auto *child : parent_expr->children()) { - if (auto *child_expr = llvm::dyn_cast(child)) { - if (IsSubExprOf(sub_expr, child_expr)) - return true; - } - } - - return false; -} - bool Converter::GetFmtArg(clang::Expr *arg, std::string &fmt, std::string &fmt_args, const char *&fmt_trait, std::string &fmt_width) { diff --git a/cpp2rust/converter/converter.h b/cpp2rust/converter/converter.h index a5117fc7b..95acac89a 100644 --- a/cpp2rust/converter/converter.h +++ b/cpp2rust/converter/converter.h @@ -441,7 +441,6 @@ class Converter : public clang::RecursiveASTVisitor { protected: const clang::Expr *GetParentExpr(const clang::Expr *expr); - bool IsSubExprOf(const clang::Expr *sub_expr, const clang::Expr *parent_expr); #define StrCat(...) _StrCat(__FUNCTION__, __LINE__, __VA_ARGS__) diff --git a/docs/.gitignore b/docs/.gitignore index 7585238ef..91ca57cbb 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,3 @@ book +mermaid.min.js +mermaid-init.js diff --git a/docs/book.toml b/docs/book.toml index 80f20dcf8..117c22a76 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -10,7 +10,11 @@ create-missing = false [output.html] git-repository-url = "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/cpp2rust/cpp2rust" edit-url-template = "/cpp2rust/cpp2rust/edit/master/docs/{path}" +additional-js = ["mermaid.min.js", "mermaid-init.js"] [output.html.fold] enable = true level = 1 + +[preprocessor.mermaid] +command = "mdbook-mermaid" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 3d714784d..690d9dca8 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -40,8 +40,18 @@ # Code Generation - [Overview](./codegen/overview.md) -- [Pointers and References](./codegen/pointers.md) -- [Unions](./codegen/unions.md) -- [Global Variables](./codegen/globals.md) -- [Temporary Materialization](./codegen/temporaries.md) -- [Translation Plugins](./codegen/plugins.md) +- [The Translation Pipeline](./codegen/pipeline.md) +- [Types](./codegen/types.md) + - [Type Mappings](./codegen/types/mappings.md) + - [Boxing](./codegen/types/boxing.md) + - [Naming](./codegen/types/naming.md) + - [Classes and Structs](./codegen/types/classes.md) + - [Traits](./codegen/types/traits.md) + - [Enums](./codegen/types/enums.md) + - [Unions](./codegen/types/unions.md) + - [Bit-fields](./codegen/types/bitfields.md) + - [Pointers and References](./codegen/types/pointers.md) + - [Casts](./codegen/types/casts.md) + - [Function Pointers](./codegen/types/fn-pointers.md) + - [Lambdas](./codegen/types/lambdas.md) + - [Special-cased Library Types](./codegen/types/special-types.md) diff --git a/docs/src/codegen/pipeline.md b/docs/src/codegen/pipeline.md new file mode 100644 index 000000000..ac93a4c6a --- /dev/null +++ b/docs/src/codegen/pipeline.md @@ -0,0 +1,55 @@ +# The Translation Pipeline + + + +```mermaid +flowchart TD + driver["cpp2rust
cpp2rust/cpp2rust.cpp"] + lib["TranspileSrc / TranspileDir
cpp2rust/cpp2rust_lib.cpp"] + action["FrontendAction, ASTConsumer
cpp2rust/ast_consumer.cpp"] + factory["CreateConverter
cpp2rust/converter/factory.cpp"] + mapper["Mapper::LoadTranslationRules
cpp2rust/converter/mapper.cpp"] + conv["Converter / ConverterRefCount
cpp2rust/converter/"] + out["output file
rustfmt"] + driver -->|"source or compilation database"| lib + lib -->|"one per translation unit"| action + action --> factory + factory -.->|"first call only"| mapper + factory --> conv + conv -->|"rs_code"| out +``` + +## The stages + +1. `cpp2rust` (`cpp2rust/cpp2rust.cpp`) parses the flags, resolves the rules + directory (see + [Loading and Matching](../rules/loading.md#finding-the-rules-directory)), and + calls `TranspileSrc` for `--file` or `TranspileDir` for `--dir`. +2. `TranspileSrc` / `TranspileDir` (`cpp2rust/cpp2rust_lib.cpp`) run clang + tooling over the source or over every file in `compile_commands.json`, with + one `FrontendAction` per translation unit. +3. `ASTConsumer::HandleTranslationUnit` calls `CreateConverter` + (`cpp2rust/converter/factory.cpp`), which loads the translation rules on its + first call and constructs a `Converter` (`--model=unsafe`) or a + `ConverterRefCount` (`--model=refcount`). +4. The converter emits the file preamble if this is the first unit, then + traverses the unit and appends Rust text to `rs_code`. +5. The driver writes `rs_code` to the `-o` path and runs `rustfmt` on it. + +## Gotchas + +- Every unit is parsed with the platform flags from + `cpp2rust/compat/platform_flags.h`, which put the + [compat headers](../rules/compat.md) ahead of the system headers and set + `-D_FORTIFY_SOURCE=0`, so macro-heavy libc APIs reach the converter as plain + function calls. +- In `--dir` mode `__FILE__` is redefined to the file's basename, so the + generated code does not embed the absolute paths of the build machine. +- Rules are loaded once per process, and the file preamble is emitted once, by + the first unit; the bookkeeping that spans units (which declarations and + records have already been emitted) is kept in `static` members of `Converter`. +- After the last unit, `Converter::EmitOpaqueRecords` appends `pub struct Name;` + for every record type that was referenced but never defined, so types only + used behind pointers still compile. +- A failing `rustfmt` is reported as an error, but the unformatted file stays on + disk for inspection. diff --git a/docs/src/codegen/types.md b/docs/src/codegen/types.md new file mode 100644 index 000000000..c56b1711e --- /dev/null +++ b/docs/src/codegen/types.md @@ -0,0 +1,52 @@ +# Types + +Every place the converter prints a type goes through `Convert(QualType)`. It +first asks the [type rules](../rules/writing-rules.md) for a mapping, so library +types and typedef names such as `size_t` are resolved by rules, and only falls +back to the `Visit*Type` methods for the built-in and user-defined types +described here. + +Given + +```cpp +struct Item { + int id; + char name[8]; + std::vector refs; +}; + +int count(Item item) { return item.id; } +``` + +the unsafe model produces (attributes and trait impls omitted) + +```rust +pub struct Item { + pub id: i32, + pub name: [libc::c_char; 8], + pub refs: Vec, +} +pub unsafe fn count_0(mut item: Item) -> i32 { + return item.id; +} +``` + +and the refcount model produces + +```rust +pub struct Item { + pub id: Value, + pub name: Value>, + pub refs: Value>, +} +pub fn count_0(item: Item) -> i32 { + let item: Value = Rc::new(RefCell::new(item)); + return *(*item.borrow()).id.borrow(); +} +``` + +Every struct field is boxed in its own `Value` so that a pointer can be taken +to it. This is set to change: field writes and field addresses through a +reinterpreted struct pointer go to a temporary and are lost, so fields will +become plain `T` and `Ptr` will gain a kind that stores the parent struct plus +an offset ([#309](https://github.com/Cpp2Rust/cpp2rust/issues/309)). diff --git a/docs/src/codegen/types/bitfields.md b/docs/src/codegen/types/bitfields.md new file mode 100644 index 000000000..a45f09a31 --- /dev/null +++ b/docs/src/codegen/types/bitfields.md @@ -0,0 +1,13 @@ +# Bit-fields + +Bit-fields are not implemented. `VisitFieldDecl` ignores the declared width, so +a field such as `unsigned flags : 3;` is emitted as a plain `u32` field: the +struct compiles, but its layout, size, and the wrap-around of the field's value +differ from C. + +> [!WARNING] +> +> Code that relies on bit-field layout or width (packing several fields into one +> word, `sizeof` on such a struct, storing a value wider than the field) +> translates silently to something else +> ([#312](https://github.com/Cpp2Rust/cpp2rust/issues/312)). diff --git a/docs/src/codegen/types/boxing.md b/docs/src/codegen/types/boxing.md new file mode 100644 index 000000000..a94ece81e --- /dev/null +++ b/docs/src/codegen/types/boxing.md @@ -0,0 +1,52 @@ +# Boxing + +In the refcount model a variable is boxed: its type `T` is wrapped in +`Value`, an alias for `Rc>` (see +[Reference Counting](../../runtime/rc.md)). Without the box, taking the address +of a variable would need a Rust reference, and arbitrary C++ aliasing cannot be +expressed with references. + +Not every type position is boxed. `ConverterRefCount` keeps a stack of +conversion kinds, [`conversion_kind_`](../internals/state.md), and the construct +that owns the type pushes one before printing it: + +- `FullRefCount`: pushed by variable and field declarations; `Convert(QualType)` + wraps the result in `Value<...>`. +- `Unboxed`: pushed by parameter lists, return types, and record names; the bare + type is printed. +- `Ptr`: pushed by a pointer type for its pointee; also printed bare. + +The result by position: + +| Position | `int` | `Item` | `int[3]` | +| ------------------------------------------- | ------------ | ------------- | -------------------- | +| local variable, struct field, global | `Value` | `Value` | `Value>` | +| function parameter, return type | `i32` | `Item` | decays to `Ptr` | +| pointee of `Ptr`, element of a container | `i32` | `Item` | `Box<[i32]>` | + +Parameters arrive unboxed and are re-boxed by the function preamble; return +values are unboxed: + +```cpp +int add(int a, Item item) { return a + item.id; } +``` + +```rust +pub fn add_0(a: i32, item: Item) -> i32 { + let a: Value = Rc::new(RefCell::new(a)); + let item: Value = Rc::new(RefCell::new(item)); + return *a.borrow() + *(*item.borrow()).id.borrow(); +} +``` + +C++ passes arguments to functions by copy, so signatures stay unboxed; boxing +the copy on entry then lets the body treat parameters exactly like local +variables. The preamble skips reference parameters, which are a `Ptr` and +never boxed. + +Nested containers, library ones and arrays alike, box each level except the +innermost, so that every inner container can be borrowed and mutated on its own, +and a pointer can be taken to it. The boxing is written into the type rules +themselves: `std::vector>` maps to `Vec>>`, and +the `carray` rules map `int a[2][2]` to `Box<[Value>]>`, both before +the outer `Value<...>` of the declaration is added. diff --git a/docs/src/codegen/types/casts.md b/docs/src/codegen/types/casts.md new file mode 100644 index 000000000..c7a9e69c1 --- /dev/null +++ b/docs/src/codegen/types/casts.md @@ -0,0 +1,138 @@ +# Casts + +Casts are of two kinds: scalar casts, which both models spell with Rust's `as` +or with a small expression, and pointer casts, where the models diverge. Most +casts in the input are implicit, inserted by clang, and are translated the same +way as explicit ones. + +## Scalar casts + +An integer conversion becomes `expr as T` (an integer literal is instead +re-typed in place: `1` cast to `unsigned char` prints as `1_u8`), and is dropped +when source and target map to the same Rust type, so `int` to `long` on a +platform where both are `i32` prints nothing. Floating conversions are `as` as +well. The other scalar casts have their own spellings: + +- Integer to `bool`: `x != 0`; a comparison or logical operator that already + yields `bool` is left alone. Enum to `bool` compares against `::from(0)`. +- Pointer to `bool`: `!p.is_null()`. +- Integer to enum: `::from(x)`, the `From` impl from the + [Enums](./enums.md) page. When the operand is itself a constant of that same + enum, which C++ sees as an integer being converted back to the enum, the cast + is dropped and the constant is printed directly (`Color::RED`, not + `::from(Color::RED as i32)`). Enum to integer is `as`. +- A cast to `void`, used to silence an unused-variable warning, becomes a + statement that only mentions the operand: `&x;` in the unsafe model, + `(*x.borrow()).clone();` in the refcount model. + +Explicit `static_cast`, C-style, and `reinterpret_cast` between scalars follow +the same rules; a cast to the operand's own type is elided. + +### Implicit conversions to `usize` and `isize` + +`size_t`, `size_type`, and `ssize_t` are translated as `usize` and `isize` +rather than as the `u64`/`i64` of the `unsigned long`/`long` they are typedefs +of (built-in type rules, looked up on the sugared type before it is desugared). +This keeps rules and output free of `as usize` casts on lengths and indexes, but +it splits one C type in two: clang inserts no conversion between `size_t` and +`unsigned long`, while `usize` and `u64` do not mix in Rust. Given + +```cpp +unsigned long take_ulong(unsigned long x); + +size_t sz = 20; +unsigned long r = take_ulong(sz); +``` + +the refcount model produces + +```rust +let sz: Value = Rc::new(RefCell::new(20_usize)); +let r: Value = Rc::new(RefCell::new(take_ulong_0(*sz.borrow() as u64))); +``` + +`Convert(expr, implicit_convert_to)` is the single place where such a cast is +added: the caller passes the type the context expects, `NeedsImplicitScalarCast` +checks that it is the same C type as the expression's but maps to a different +Rust type, and if so the expression is wrapped in `(...) as `. Callers +that pass a target are assignments and initializations (the variable's type), +call arguments (the parameter type of the callee or rule, +`GetParamImplicitConvertTarget`, as in the example), and binary operators, which +pick one Rust type for both operands (`GetOperandImplicitConversionTarget`). + +## Pointer casts + +Given + +```cpp +uint32_t value = 0x04030201; +uint8_t *bytes = (uint8_t *)&value; +void *any = bytes; +uint8_t *back = (uint8_t *)any; +``` + +the unsafe model produces + +```rust +let mut value: u32 = 67305985_u32; +let mut bytes: *mut u8 = (&mut value as *mut u32) as *mut u8; +let mut any: *mut ::libc::c_void = bytes as *mut ::libc::c_void; +let mut back: *mut u8 = any as *mut u8; +``` + +and the refcount model produces + +```rust +let value: Value = Rc::new(RefCell::new(67305985_u32)); +let bytes: Value> = + Rc::new(RefCell::new(value.as_pointer().reinterpret_cast::())); +let any: Value = Rc::new(RefCell::new((*bytes.borrow()).to_any())); +let back: Value> = + Rc::new(RefCell::new((*any.borrow()).reinterpret_cast::())); +``` + +### Unsafe model + +Every pointer cast, whether written as a C cast, `static_cast`, or +`reinterpret_cast`, is a Rust `as` between raw pointer types. A cast that only +adds or removes `const` changes the Rust type too, since `T *` is `*mut T` and +`const T *` is `*const T`, and becomes `.cast_const()` or `.cast_mut()`. A cast +that changes nothing in Rust, such as a `typedef` to its underlying type, is not +emitted. Casts between pointers and integers are also `as`. + +### Refcount model + +A `Ptr` is a weak reference to a `RefCell`, so it cannot simply be +relabeled as a `Ptr`: the cell it points to holds a `T`. A cast to another +pointee type therefore produces a different kind of pointer, one that views the +allocation as bytes. Three helpers from the runtime cover the cases: + +- `p.reinterpret_cast::()` produces a `Ptr` of the + [`Reinterpreted` kind](../../runtime/reinterpret.md#views-over-the-original-allocation): + a byte-level view over the original allocation, with the offset counted in + bytes. Reads and writes through it go through + [`ByteRepr`](../../runtime/reinterpret.md#byterepr), which is why every record + type gets a `ByteRepr` impl. +- `p.to_any()` erases the type into an [`AnyPtr`](../../runtime/void.md), the + translation of `void *`, remembering the original type. +- `any.reinterpret_cast::()` recovers a `Ptr` from an `AnyPtr`: the + original pointer if `T` is the type it was erased from, a byte view otherwise + (see [AnyPtr casts](../../runtime/reinterpret.md#anyptr-casts)). + +Two casts do not use these helpers. An array decaying to a pointer is spelled +`arr.as_pointer() as Ptr`, where the `as` only names the pointer type. An +upcast from a derived class to an abstract base becomes +`(p.to_strong() as Value).as_pointer_dyn()`, which is the ordinary +Rust unsizing coercion applied to the owning cell (see +[Virtual Classes](../../runtime/ptr-dyn.md)). Casts between pointers and +integers use the [integer cast](../../runtime/rc.md#integer-casts) API of `Ptr`. + +Constness is dropped in a cast as everywhere else, so `const_cast` is a no-op. +`dynamic_cast` is not supported. + +### Function pointers + +Casting a function pointer to a different signature, which C code does to call +through a generic type, wraps the function in an adapter closure that converts +the arguments; see [Casts](../../runtime/fn-ptr.md#casts) on the runtime page. +Storing a function pointer in a `void *` uses `to_any()` like any other pointer. diff --git a/docs/src/codegen/types/classes.md b/docs/src/codegen/types/classes.md new file mode 100644 index 000000000..3b93182c6 --- /dev/null +++ b/docs/src/codegen/types/classes.md @@ -0,0 +1,180 @@ +# Classes and Structs + +A class becomes a struct with one field per data member, an `impl` block holding +its constructors and methods, and trait implementations after it. Given + +```cpp +class Counter { + int count_; + +public: + Counter(int start) : count_(start) {} + ~Counter() { count_ = 0; } + int get() const { return count_; } + void set(int v) { count_ = v; } +}; +``` + +the unsafe model produces + +```rust +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct Counter { + count_: i32, +} +impl Counter { + pub unsafe fn Counter(mut start: i32) -> Self { + let mut this = Self { count_: start }; + this + } + pub unsafe fn get(&self) -> i32 { + return self.count_; + } + pub unsafe fn set(&mut self, mut v: i32) { + self.count_ = v; + } +} +``` + +and the refcount model produces + +```rust +#[derive(Default)] +pub struct Counter { + count_: Value, +} +impl Counter { + pub fn Counter(start: i32) -> Self { + let start: Value = Rc::new(RefCell::new(start)); + let mut this = Self { + count_: Rc::new(RefCell::new(*start.borrow())), + }; + this + } + pub fn get(&self) -> i32 { + return *self.count_.borrow(); + } + pub fn set(&self, v: i32) { + let v: Value = Rc::new(RefCell::new(v)); + *self.count_.borrow_mut() = *v.borrow(); + } +} +impl Drop for Counter { + fn drop(&mut self) { + *self.count_.borrow_mut() = 0; + } +} +impl Clone for Counter { + fn clone(&self) -> Self { + let mut this = Self { + count_: Rc::new(RefCell::new(*self.count_.borrow())), + }; + this + } +} +impl ByteRepr for Counter { /* byte_size, to_bytes, from_bytes */ } +``` + +The unsafe model adds `#[repr(C)]` and derives what it can; the refcount model +writes most impls by hand. Which traits are emitted, and when they are derived +rather than written, is on the [Traits](./traits.md) page. + +Fields keep their C++ access: `pub` for public members, nothing for private +ones. A class nested in another class is emitted as its own top-level struct, +named `Outer_Inner` (see [Naming](./naming.md)); Rust has no nested types, and +the outer struct refers to it by that name. A record that is only +forward-declared, or whose definition is never converted because it is only used +behind pointers, is emitted at the end of the file as an empty +`pub struct Name;` (see [The Translation Pipeline](../pipeline.md)). + +A constructor becomes an associated function named after the class. It opens +with `let mut this = Self { ... }`, one field per member initializer, then runs +the C++ body and returns `this`. Only implicit or defaulted copy and move +constructors are supported: a user-defined one stops the translation, and the +implicit copy is what the `Clone` impl represents. + +Methods take `&self` when `const` and `&mut self` otherwise in the unsafe model. +In the refcount model they always take `&self`, since mutation goes through the +fields' `RefCell`s. Inside a method, `this` is `self`. + +A destructor with a body becomes `impl Drop` in the refcount model. + +> [!WARNING] +> +> The unsafe model does not emit destructors at all; a user-defined destructor +> is silently dropped ([#310](https://github.com/Cpp2Rust/cpp2rust/issues/310)). + +## Inheritance + +An abstract class becomes a trait with one method per pure virtual function, and +a class deriving from it implements the trait with its overrides. Given + +```cpp +class Animal { +public: + virtual bool bark() const = 0; +}; + +class Dog : public Animal { + bool bark() const override { return true; } +}; +``` + +the unsafe model produces (attributes omitted) + +```rust +pub unsafe trait Animal { + unsafe fn bark(&self) -> bool; +} +pub struct Dog {} +unsafe impl Animal for Dog { + unsafe fn bark(&self) -> bool { + return true; + } +} +``` + +and the refcount model produces (attributes and the `Clone` and `ByteRepr` impls +omitted) + +```rust +pub trait Animal { + fn bark(&self) -> bool; +} +pub struct Dog {} +impl Animal for Dog { + fn bark(&self) -> bool { + return true; + } +} +``` + +Non-virtual methods of the derived class go into its own `impl Dog` block as +usual. Because the base is a trait, pointers to it are `*mut dyn Animal` in the +unsafe model and [`PtrDyn`](../../runtime/ptr-dyn.md) in the +refcount model, and a `Dog *` is upcast at the call site. Only the first base +class is considered, and only virtual methods go through the trait; bases with +data members or non-virtual methods, and multiple inheritance, are outside the +supported subset. + +## Templates + +Class templates are translated by full instantiation: each instantiation used by +the program becomes its own struct and `impl` block, named after the template +arguments (see [Naming](./naming.md)). `MyContainer` and +`MyContainer` become `MyContainer_int_` and `MyContainer_char_`, each with +a complete copy of the methods specialized for its element type. Nothing is +shared between instantiations, and Rust generics are not used. + +## Flexible array members + +A trailing array member of size 0, 1, or `[]` that C code over-indexes into +memory allocated past the struct is detected with clang's +`isFlexibleArrayMemberLike`. In the unsafe model an access to such a member is +not an array index, which Rust would bounds-check against the declared length, +but pointer arithmetic from the array's start: `s.bytes[i]` becomes +`*s.bytes.as_mut_ptr().add(i as usize)`, and `&s.bytes[i]` the same without the +leading `*`. The refcount model has no dedicated handling; the pattern works +when the array is a union member, because the union accessor returns a `Ptr` +over the whole allocation that can be offset freely. diff --git a/docs/src/codegen/types/enums.md b/docs/src/codegen/types/enums.md new file mode 100644 index 000000000..97fbc489b --- /dev/null +++ b/docs/src/codegen/types/enums.md @@ -0,0 +1,46 @@ +# Enums + +An enum becomes a Rust `enum` with one variant per enumerator and explicit +discriminants, followed by the impls that give it C semantics. Given + +```cpp +enum Color { RED, GREEN, BLUE }; +``` + +both models produce + +```rust +#[derive(Clone, Copy, PartialEq, Debug, Default)] +enum Color { + #[default] + RED = 0, + GREEN = 1, + BLUE = 2, +} +impl From for Color { + fn from(n: i32) -> Color { + match n { + 0 => Color::RED, + 1 => Color::GREEN, + 2 => Color::BLUE, + _ => panic!("invalid Color value: {}", n), + } + } +} +libcc2rs::impl_enum_inc_dec!(Color); +``` + +and the refcount model adds an `impl ByteRepr for Color` that converts through +the `i32` value. + +The first enumerator is the `#[default]`, which is what a zero-initialized or +default-constructed enum variable holds. `From` is the integer-to-enum +cast; a value that matches no enumerator panics, where C would silently keep the +integer. [`impl_enum_inc_dec!`](../../runtime/inc-dec.md) implements the four +`++`/`--` forms by stepping through the enumerators. Enum-to-integer casts need +no impl and become `as i32`. + +`enum class` is translated the same way; enumerators are always spelled +`Color::RED` on the Rust side, scoped or not. In C, an anonymous enum named only +through a typedef (`typedef enum { ... } Tag;`) is emitted as `Tag_enum`, and an +anonymous enum with no name at all as `anon_N` (see [Naming](./naming.md)). diff --git a/docs/src/codegen/types/fn-pointers.md b/docs/src/codegen/types/fn-pointers.md new file mode 100644 index 000000000..5d9880fd9 --- /dev/null +++ b/docs/src/codegen/types/fn-pointers.md @@ -0,0 +1,74 @@ +# Function Pointers + +Given + +```cpp +typedef int (*op_t)(int); + +int inc(int x) { return x + 1; } + +op_t pick() { return inc; } + +int apply(op_t f) { + if (f == nullptr) { + return 0; + } + return f(10); +} +``` + +the unsafe model produces + +```rust +pub unsafe fn inc_0(mut x: i32) -> i32 { + return x + 1; +} +pub unsafe fn pick_1() -> Option i32> { + return Some(inc_0); +} +pub unsafe fn apply_2(mut f: Option i32>) -> i32 { + if f.is_none() { + return 0; + } + return f.unwrap()(10); +} +``` + +and the refcount model produces + +```rust +pub fn inc_0(x: i32) -> i32 { + let x: Value = Rc::new(RefCell::new(x)); + return *x.borrow() + 1; +} +pub fn pick_1() -> FnPtr i32> { + return FnPtr:: i32>::new(inc_0); +} +pub fn apply_2(f: FnPtr i32>) -> i32 { + let f: Value i32>> = Rc::new(RefCell::new(f)); + if (*f.borrow()).is_null() { + return 0; + } + return (*(*f.borrow()))(10); +} +``` + +## Unsafe model + +A function pointer is `Option R>`: `Option` because it can be +null, `unsafe fn` because every translated function is `unsafe`. Naming a +function where a pointer is expected wraps it in `Some(...)`, the null pointer +is `None`, a null check is `is_none()`, and a call is `f.unwrap()(args)`. +Function pointers are `Copy` and compare with `==` on the function's address. + +## Refcount model + +A function pointer is [`FnPtr R>`](../../runtime/fn-ptr.md), built with +`FnPtr:: R>::new(f)`. `FnPtr` dereferences to the function, so a call +is `(*f)(args)`; the null pointer is `FnPtr::null()` and the check is +`is_null()`. `FnPtr` is not `Copy`, so storing or passing one that already lives +in a variable clones it, and equality compares the address of the wrapped +function, so a pointer stays equal to itself after being cast. + +A capture-less lambda assigned to a function pointer becomes +`FnPtr::new(|...| ...)` with the closure inline (see [Lambdas](./lambdas.md)). diff --git a/docs/src/codegen/types/lambdas.md b/docs/src/codegen/types/lambdas.md new file mode 100644 index 000000000..1f3743c9f --- /dev/null +++ b/docs/src/codegen/types/lambdas.md @@ -0,0 +1,91 @@ +# Lambdas + +A lambda becomes a Rust closure with the same parameters and a translated body. +Given + +```cpp +template int apply(F fn, int x) { return fn(x); } + +int main() { + int base = 10; + auto add_base = [&base](int x) { return x + base; }; + return apply(add_base, 5); +} +``` + +the unsafe model produces + +```rust +pub unsafe fn apply_0(mut fn_: impl Fn(i32) -> i32, mut x: i32) -> i32 { + return fn_(x); +} +unsafe fn main_0() -> i32 { + let mut base: i32 = 10; + return apply_0( + (|x: i32| { + return x + base; + }) + .clone(), + 5, + ); +} +``` + +and the refcount model produces + +```rust +pub fn apply_0(fn_: impl Fn(i32) -> i32, x: i32) -> i32 { + let fn_: Value<_> = Rc::new(RefCell::new(fn_)); + let x: Value = Rc::new(RefCell::new(x)); + return (*fn_.borrow_mut())(*x.borrow()); +} +fn main_0() -> i32 { + let base: Value = Rc::new(RefCell::new(10)); + let add_base: Value<_> = Rc::new(RefCell::new( + (|x: i32| { + let x: Value = Rc::new(RefCell::new(x)); + return *x.borrow() + *base.borrow(); + }), + )); + return apply_0((*add_base.borrow()).clone(), 5); +} +``` + +## Closure and type + +The closure lists the lambda's parameters with their translated types and +contains the body converted like a function body, including, in the refcount +model, the preamble that boxes each parameter. The lambda's own type is never +spelled: a variable holding one is `Value<_>` in the refcount model and the type +is inferred, and a function template parameter that receives one is +`impl Fn(A) -> R`, as `apply` shows. A call through such a parameter is a plain +call, `fn_(x)`, with the refcount model borrowing the boxed closure first. + +## Captures + +The C++ capture list is not translated. A Rust closure captures whatever it +mentions by reference, so `[&base]` and `[base]` produce the same closure and +both see the variable's current value at call time. For a by-reference capture +this is C++'s semantics; for a by-value capture it is not, since C++ copies the +variable when the lambda is created. + +## Where the closure is emitted + +The refcount model emits a variable initialized with a lambda as a boxed closure +once and clones it out of the box at each use. + +> [!WARNING] +> +> The unsafe model does not emit a `let` for such a variable; the closure is +> emitted again at every use, which is why the example above shows it inline in +> the `apply_0` call. This was a workaround: a stored closure that captures +> locals by reference keeps them borrowed for as long as it lives, so +> `let foo = || { a += 1; a }; return foo() + a;` does not compile, while +> re-emitting the closure at each call keeps every borrow inside that call. It +> is a bug, since the lambda's creation and its uses are no longer the same +> object ([#314](https://github.com/Cpp2Rust/cpp2rust/issues/314)). + +A capture-less lambda assigned to a function pointer becomes a function pointer +value: `Some(|...| ...)` in the unsafe model and `FnPtr::new(|...| ...)` in the +refcount model (see [Function Pointers](./fn-pointers.md)). Lambdas with +captures cannot be converted to function pointers, as in C++. diff --git a/docs/src/codegen/types/mappings.md b/docs/src/codegen/types/mappings.md new file mode 100644 index 000000000..02bd111b5 --- /dev/null +++ b/docs/src/codegen/types/mappings.md @@ -0,0 +1,69 @@ +# Type Mappings + +The table gives the spelling of each C++ type in both models, before any +refcount boxing. `T` stands for the translated inner type. + +| C++ | Unsafe model | Refcount model | +| ------------------------------ | ----------------------------------------------- | --------------------------------------------------------- | +| `bool` | `bool` | `bool` | +| `int`, `unsigned long`, ... | `i32`, `u64`, ... (host width) | same | +| `float`, `double` | `f32`, `f64` | same | +| `char` | `libc::c_char` | `u8` | +| `size_t` and other typedefs | by type rule (`usize`), else desugared | same | +| `T[N]` | `[T; N]` | `Box<[T]>` | +| `T[]` | `[T]` | `Box<[T]>` | +| `struct S`, `enum E` | `S`, `E` | same | +| `T *`, `T &` | `*mut T`, `*const T` | [`Ptr`](../../runtime/rc.md#values-and-pointers) | +| `Abstract *` | `*mut dyn Abstract` | [`PtrDyn`](../../runtime/ptr-dyn.md) | +| `void *` | `*mut ::libc::c_void` | [`AnyPtr`](../../runtime/void.md) | +| `R (*)(A)` | `Option R>` | [`FnPtr R>`](../../runtime/fn-ptr.md) | +| `va_list` | [`VaList`](../../runtime/va-args.md) | [`VaList`](../../runtime/va-args.md) | +| lambda closure | `impl Fn(A) -> R` as a parameter, `_` elsewhere | same | +| `std::unique_ptr` | by type rule (`Option>`) | by type rule (`Option>`) | +| `std::vector` and other STL | by type rule (`Vec`) | by type rule (`Vec`, `Vec>>` when nested) | + +Other built-ins (`wchar_t`, `long double`, `char16_t`) are omitted. Rvalue +references (`T &&`) have no mapping of their own; they reach the converter only +through `std::move` and implicit move constructors, which are handled by rules +and by the constructor translation. + +## User-defined types as rules + +When a record or enum declaration is converted, +`Mapper::AddRuleForUserDefinedType` registers it in the mapper's type table: the +C++ name maps to the Rust name, and its pointer form maps to `*mut Name` or +`Ptr` (`*mut dyn Name` or `PtrDyn` for abstract classes); nested +records are registered too. This is what makes library types instantiated with +user types translatable: `Mapper::Map` matches `std::vector` against the +rule for `std::vector` and then has to map `T1 = Item` through the same +table, which would fail if `Item` were not in it. + +## Scalars + +`char` is `libc::c_char` in the unsafe model, whose signedness follows the +platform like C's, and `u8` in the refcount model, because C strings are byte +vectors there (see [C Strings](../../runtime/cstr.md)). Since most C +implementations have signed `char`, the refcount model is set to switch to `i8` +([#246](https://github.com/Cpp2Rust/cpp2rust/issues/246)). + +## Arrays + +In the refcount model a constant array becomes `Box<[T]>`, dropping the length. +`Ptr` carries only the element type, not `N`, so a `[T; N]` could not be +pointed to without a `Ptr` per length; `Box<[T]>` gives arrays of every length, +and heap arrays, the same shape. `[T; N]` survives only inside `sizeof`, which +becomes `::std::mem::size_of::<[T; N]>()`. + +Array parameters decay to pointers as in C. + +## Typedefs and qualifiers + +Typedef names are looked up as type rules before being desugared, which is how +`size_t` maps to `usize` instead of the underlying `unsigned long`. + +Constness is dropped: in the unsafe model it survives only as `*const` on +pointers and as a missing `mut` on bindings, and in the refcount model it has no +representation. + +The [Pointers and References](./pointers.md) page covers how values of pointer +types are read and written. diff --git a/docs/src/codegen/types/naming.md b/docs/src/codegen/types/naming.md new file mode 100644 index 000000000..d7d5b6be9 --- /dev/null +++ b/docs/src/codegen/types/naming.md @@ -0,0 +1,29 @@ +# Naming + +Rust has one flat namespace per module and no overloading, so C++ names are +flattened and disambiguated when they are emitted. + +Records and enums are named by `Mapper::ToRustName` from their qualified C++ +spelling: `::`, `<`, `>`, commas, and spaces all become `_`. So `ns::Foo` is +`ns_Foo`, the instantiation `MyContainer` is `MyContainer_int_`, and a +struct `Level1` nested in `Level0` is `Level0_Level1`. The same name is used for +the struct, its `impl` blocks, and every mention of the type. + +An anonymous struct, union, or enum is named `anon_N`, numbered in order of +first appearance. In C, an anonymous tag that is only reachable through a +typedef (`typedef struct { ... } Point;`) is emitted as `Point_struct` (or +`Point_enum`), because C keeps tags and ordinary identifiers in separate +namespaces and `Point` may already be a variable or function. + +Names that are Rust keywords get a trailing underscore: a variable `type` +becomes `type_`. The same applies to a keyword followed only by underscores, so +a C++ identifier that was already `type_` becomes `type__` and cannot collide +with the renamed `type`. + +Free functions and global variables get a numeric suffix (`main_0`, `foo_3`) +from a process-wide table keyed by mangled name, which keeps overloads and +same-named `static` functions from different files apart. Methods keep their +name unless they are overloaded, in which case the parameter types are appended +(`method_i32`, `method_i32_const`). `operator<` is emitted as `lt`; comparison +operators additionally produce the corresponding trait impls (`PartialOrd`, +`Ord`, `PartialEq`). diff --git a/docs/src/codegen/types/pointers.md b/docs/src/codegen/types/pointers.md new file mode 100644 index 000000000..448c5417e --- /dev/null +++ b/docs/src/codegen/types/pointers.md @@ -0,0 +1,138 @@ +# Pointers and References + +The unsafe model keeps C++ pointers as raw pointers and dereferences them +directly. The refcount model replaces every pointer and reference with +[`Ptr`](../../runtime/rc.md#values-and-pointers), a weak reference plus an +offset, and every dereference with a short-lived borrow of the pointee. Given + +```cpp +int f(int *q) { + int b = 2; + int *p = &b; + *p = *q; + return b; +} +``` + +the unsafe model produces + +```rust +pub unsafe fn f_0(mut q: *mut i32) -> i32 { + let mut b: i32 = 2; + let mut p: *mut i32 = &mut b as *mut i32; + *p = *q; + return b; +} +``` + +and the refcount model produces + +```rust +pub fn f_0(q: Ptr) -> i32 { + let q: Value> = Rc::new(RefCell::new(q)); + let b: Value = Rc::new(RefCell::new(2)); + let p: Value> = Rc::new(RefCell::new(b.as_pointer())); + p.borrow().write(q.borrow().read()); + return *b.borrow(); +} +``` + +The rest of the page goes through the pointer operations one at a time. + +## Address-of + +Unsafe model: `&x` becomes `&mut x as *mut T`, or `&x as *const T` when the +pointer type is to `const`. Globals use `&raw mut x` so no reference to the +`static` is formed. An array decays with `arr.as_mut_ptr()`, and the address of +an element is `&mut arr[i] as *mut T`. + +Refcount model: `&x` becomes `x.as_pointer()`, which produces a `Ptr` holding a +weak reference to the variable's `Value`. Since every field is its own `Value`, +`&s.field` is `s.field.as_pointer()`. An array decays with +`arr.as_pointer() as Ptr`, a `Ptr` to element 0 of the whole array, and +`&arr[i]` is that pointer offset by `i`. + +Both models push the address down to the innermost place expression: +`&(cond ? x : y)` becomes `if cond { &mut x } else { &mut y }` in the unsafe +model and `if cond { x.as_pointer() } else { y.as_pointer() }` in the refcount +one. The `if` yields a value copied out of whichever branch ran, not that +branch's storage, so the address has to be taken inside the branches, where the +place is still known. + +## Dereference + +Unsafe model: `*p` stays `*p`, `p->x` becomes `(*p).x`, and an assignment +through a pointer is `*p = v`. + +When the dereference is the base of an index, `(*p)[i]` with `p: *mut Vec`, +Rust would have to create a `&mut Vec` out of the raw pointer to call +`Index::index`, and its `dangerous_implicit_autorefs` lint rejects that as an +error. The converter makes the reference explicit instead: `EmitDeref` prints +`(&mut (*p))[i]`, or `(&(*p))[i]` when the `operator[]` is `const`, when +[`autoref_mut_`](../internals/state.md) is set. `PushExplicitAutoref` sets it +around the base of an overloaded subscript, which also covers a member of the +pointee, `(&mut (*hp)).v[i]`, around the range of a range-`for`, and around a +rule placeholder marked `is_index_base` (see [Rules IR](../../rules/ir.md)); +`EmitDeref` clears it once used, so nested dereferences inside the base are +printed plainly. + +Refcount model: a dereference cannot hand out a `&T` into the pointee, because +nothing would bound the borrow's lifetime, so a read copies the value out and a +write copies it in. Which form is emitted follows the +[expression kind](../expressions/kinds.md), what the enclosing construct expects +of the dereference: + +- An rvalue use copies the value out. A scalar or pointer pointee is `p.read()`. + A record pointee goes through `p.upgrade().deref()`, which briefly turns the + weak pointer into a [strong one](../../runtime/rc.md#strong-pointers) and + borrows the record; a field of it is a `Value` and is borrowed as usual, + `(*p.upgrade().deref()).x`. A read-only method on a boxed pointee borrows the + same way: `p->size()` is `(*p.upgrade().deref()).len()`. +- An address-of use prints `p` itself. +- An lvalue use prints nothing at once. The converter records the pointer + expression as a [pending dereference](../expressions/pending-deref.md), and + whoever consumes the lvalue, an assignment or a mapped method call, wraps it: + `p.write(v)`, or [`with_mut`](../../rules/rewriting.md) for a mutating method + on a boxed pointee. This is what lets `*p = v` come out as a single `write` + instead of a borrow followed by an assignment, and `*p += v` as + `{ let _ptr = p.clone(); _ptr.write(_ptr.read() + v) }`. + +The strong pointer only ever appears as a temporary inside the expression, which +is what breaks field writes and field addresses through reinterpreted pointers +([#309](https://github.com/Cpp2Rust/cpp2rust/issues/309)) and union accessors +([#311](https://github.com/Cpp2Rust/cpp2rust/issues/311)). + +## Arithmetic and comparison + +Unsafe model: `p + n` is `p.offset(n as isize)` and `p - n` is +`p.offset(-(n as isize))`; `p - q` is +`(p as usize - q as usize) / ::std::mem::size_of::()`; `++p` is +`p.prefix_inc()` through the [increment traits](../../runtime/inc-dec.md); +`p == NULL` is `p.is_null()` and the null literal is `std::ptr::null_mut()`, or +`std::ptr::null()` for a pointer to `const`. + +Refcount model: the same operations on `Ptr`, `p.offset(n as isize)`, +`p.clone() - q.clone()` (subtraction takes its operands by value, hence the +clones), `p.prefix_inc()`, `p.is_null()`, and `Ptr::null()`. Arithmetic only +moves the offset; whether the result is in bounds is checked when it is +dereferenced. + +## References + +A C++ reference is a pointer that cannot be made to point elsewhere, and both +models translate it as one. In the unsafe model a reference parameter is +`*mut T` (`*const T` for `const T &`), an argument `f(x)` is +`f(&mut x as *mut T)`, and uses of the reference are `*r`. In the refcount model +it is a `Ptr` that is [never boxed](./boxing.md): the argument is +`f(x.as_pointer())`, uses are `r.read()` and `r.write(v)`, and returning a +reference returns the `Ptr` (with a `.clone()`, since `Ptr` is not `Copy`). + +## Heap + +`new T(v)` becomes `Box::leak(Box::new(v)) as *mut T` in the unsafe model and +[`Ptr::alloc(v)`](../../runtime/rc.md#the-heap) in the refcount model; +`delete p` becomes `::std::mem::drop(Box::from_raw(p))` and `p.delete()`. Array +forms use a boxed slice and `Ptr::alloc_array`. In the refcount model the heap +allocation is a leaked `Rc` that `delete` recovers, so a double `delete` or a +`delete` of something that was not allocated with `new` panics instead of +corrupting memory. diff --git a/docs/src/codegen/types/special-types.md b/docs/src/codegen/types/special-types.md new file mode 100644 index 000000000..46f3aa668 --- /dev/null +++ b/docs/src/codegen/types/special-types.md @@ -0,0 +1,141 @@ +# Special-cased Library Types + +Library types are translated by [type rules](../../rules/overview.md), and for +most of them the converter does nothing beyond applying the rule. A few types +also have code of their own in the converter, which decides how their values are +dereferenced, iterated, or initialized. Some of it could be moved into +[rules](../../rules/writing-rules.md). This page lists what the converter does +today; the rest of the type comes from its rule module under `rules/`. + +## `std::unique_ptr` + +The type rule maps `std::unique_ptr` to `Option>` in the unsafe model +and `Option>` in the refcount model, and `std::make_unique` and +`std::move` are rules too. What the converter special-cases (`IsUniquePtr` in +`converter_lib`) is everything that treats a `unique_ptr` as a pointer: + +Given + +```cpp +std::unique_ptr x1 = std::make_unique(0); +std::unique_ptr x2 = std::make_unique(0); +*x2 = 1; +x1 = std::move(x2); +int *raw = &*x1; +``` + +the unsafe model produces + +```rust +let mut x1: Option> = Some(Box::new(0)); +let mut x2: Option> = Some(Box::new(0)); +*x2.as_deref_mut().unwrap() = 1; +x1 = x2; +let mut raw: *mut i32 = &mut (*x1.as_deref_mut().unwrap()) as *mut i32; +``` + +and the refcount model produces + +```rust +let x1: Value>> = + Rc::new(RefCell::new(Some(Rc::new(RefCell::new(0))))); +let x2: Value>> = + Rc::new(RefCell::new(Some(Rc::new(RefCell::new(0))))); +*(*x2.borrow_mut()).as_ref().unwrap().borrow_mut() = 1; +*x1.borrow_mut() = (*x2.borrow_mut()).take(); +let raw: Value> = + Rc::new(RefCell::new((*x1.borrow()).as_pointer())); +``` + +`*p` and `p->x` are overloaded operator calls in C++; the converter emits the +`as_deref_mut().unwrap()` and `as_ref().unwrap().borrow_mut()` forms instead of +an operator call, `&*p` becomes the raw pointer or `as_pointer()`, and +`std::move` of a `unique_ptr` is a plain move or a `take()`. In the unsafe model +`p == nullptr` becomes `p.is_none()`; the refcount model does not special-case +it and emits `is_null()` as for any pointer, which no test exercises on an +`Option>`. A struct with a `unique_ptr` field does not derive `Copy`. + +## Iterators + +Iterator types come from rules (`std::vector::iterator` maps to `*mut T` or +`Ptr`, `std::map::iterator` to `UnsafeMapIterator` or +`RefcountMapIter`), and the converter classifies them by +`GetStrongestIteratorCategory`: rule types marked as refcount pointers are +contiguous iterators and are handled exactly like a `Ptr`, and the map +iterator types are bidirectional. The classification drives a few decisions. + +Given + +```cpp +std::map m; +double sum = 0; +for (const auto &i : m) { + sum += i.second; +} +auto it = m.begin(); +sum += it->second; +``` + +the unsafe model produces + +```rust +for i in UnsafeMapIterator::begin(&m as *const BTreeMap>) { + sum += *i.second(); +} +let mut it: UnsafeMapIterator = + UnsafeMapIterator::begin(&m as *const BTreeMap>); +sum += *it.second(); +``` + +and the refcount model produces + +```rust +for i in RefcountMapIter::begin(m.as_pointer()) { + *sum.borrow_mut() += *i.second().borrow(); +} +let it: Value> = + Rc::new(RefCell::new(RefcountMapIter::begin(m.as_pointer()))); +*sum.borrow_mut() += *(*it.borrow()).second().borrow(); +``` + +`it->second` on a bidirectional iterator (map iterator) is not a pointer +dereference plus a field access, since the map iterator types have no pointer to +hand out; the converter emits the iterator itself and the field rule turns the +access into an accessor call. + +The loop variable of a range-`for` over a `std::map` is the map iterator itself, +an entry with `first()`/`second()` accessors, not a pointer to an element; the +converter remembers such variables in [`map_iter_decls_`](../internals/state.md) +so that uses of them are not dereferenced. + +A converting-constructor call that only wraps an iterator does not clone it +(`PushSuppressIteratorClone`). libstdc++ and libc++ differ in whether such a +wrapping constructor appears in the AST, so skipping the clone keeps the output +identical on Linux and macOS. + +`IsIteratorType` recognizes any record that declares an `iterator_category` +typedef. + +## `std::array` + +`std::array` maps to `Vec` (see `rules/array`), so an initializer +`{1, 2, 3}` becomes `vec![1, 2, 3]`. The converter knows the type by name in +three places: the default value of an uninitialized `std::array` variable is +built element by element from `N`, a struct with a `std::array` field does not +derive `Default`, and it does not derive `Copy` either since the field is a +`Vec`. + +> [!WARNING] +> +> An empty initializer, `std::array a = {};`, becomes `vec![]`, a vector +> of length 0, where C++ value-initializes `N` elements; indexing it panics +> ([#313](https://github.com/Cpp2Rust/cpp2rust/issues/313)). + +## `std::string` and streams + +`std::string` maps to `Vec` in the unsafe model and `Vec` in +the refcount model through its rules; the converter itself only special-cases +string literals (their type in an initializer, and ASCII escaping) and +range-`for` over a string. `std::ostream` calls (`std::cout << x`) are detected +with `IsCallToOstream` and translated by a dedicated path rather than by rules; +that path is described with `printf` under Expressions. diff --git a/docs/src/codegen/types/traits.md b/docs/src/codegen/types/traits.md new file mode 100644 index 000000000..9110897cb --- /dev/null +++ b/docs/src/codegen/types/traits.md @@ -0,0 +1,90 @@ +# Traits + +Every emitted struct comes with a fixed set of trait implementations. Some are +derived, some are written out; which is which depends on the model. Enums derive +`Clone, Copy, PartialEq, Debug, Default` in both models, and unions derive +`Copy, Clone` in the unsafe model regardless of their fields; the table below is +for structs. + +| Trait | Unsafe model | Refcount model | +| -------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------- | +| `Copy` | derived when every field is copyable | never (a `Value` is not `Copy`) | +| `Clone` | derived | hand-written, deep-copies each field into a new box | +| `Default` | derived when possible, else hand-written | same | +| `Drop` | not emitted ([#310](https://github.com/Cpp2Rust/cpp2rust/issues/310)) | hand-written from a user destructor with a body | +| `Ord`, `PartialOrd`, `PartialEq`, `Eq` | hand-written from `operator<` | same | +| `ByteRepr` | not needed | hand-written for every record and enum | + +## Copy and Clone + +In the unsafe model a record derives `Copy` unless a field is translated to a +`Vec`, `BTreeMap`, `Option>` (`std::unique_ptr`), or a record that is not +itself `Copy`; `Clone` is derived unless the C++ copy constructor is deleted. +The refcount model skips `Clone` altogether when the copy constructor is +deleted. The refcount model cannot derive either, because a `Value` field is +an `Rc` and a derived `Clone` would only bump its count, leaving the copy +aliasing the original. The generated `Clone` therefore rebuilds every field: + +```rust +impl Clone for Counter { + fn clone(&self) -> Self { + let mut this = Self { + count_: Rc::new(RefCell::new(*self.count_.borrow())), + }; + this + } +} +``` + +This is what gives struct assignment and pass-by-value C++'s member-by-member +copy. + +## Default + +`Default` is the value of a `T x;` without initializer, of `T x = {}`, and of +the elements of `new T[n]`. It is derived when the derived impl gives the C zero +value, and hand-written otherwise: when the class has a user-defined default +constructor, `default()` calls it; when a field is a C array, a `std::array`, a +function pointer, or a libc record, `default()` builds the struct field by +field, each with the same default value the converter uses for a variable of +that type declared without an initializer: + +```rust +impl Default for S { + fn default() -> Self { + S { + head: 0_i32, + tail: [0_i32; 3], + buf: [0 as libc::c_char; 4], + } + } +} +``` + +Unions always get a hand-written impl that zeroes their bytes. + +## Drop + +A user-defined destructor with a non-empty body becomes `impl Drop`, with the +body translated as a method body. Only the refcount model emits it; the unsafe +model drops destructors silently +([#310](https://github.com/Cpp2Rust/cpp2rust/issues/310)). + +## Comparison + +A class that defines `operator<` (as a method or an out-of-line function) gets +`Ord`, `PartialOrd`, `PartialEq`, and `Eq`, all expressed through the emitted +`lt` method: `cmp` calls it both ways to pick `Less`, `Greater`, or `Equal`, and +`eq` is "neither is less". Only one comparison operator per class is supported, +and only `operator<`. The converter assumes the operator is `const`, which Rust +requires (`cmp` and `eq` take `&self`) but C++ does not; a non-`const` +`operator<` is still emitted as `lt(&self, ...)`. + +## ByteRepr + +The refcount model emits [`ByteRepr`](../../runtime/reinterpret.md#byterepr) for +every record and enum: `byte_size`, `to_bytes`, and `from_bytes` laid out with +the C offsets of the fields (enums go through their `i32` value). It is what +lets a `Ptr` to the type be reinterpreted as bytes, and bytes be read back as +the type. A record with a field that has no byte representation gets an empty +impl, and reinterpreting it panics at run time. diff --git a/docs/src/codegen/types/unions.md b/docs/src/codegen/types/unions.md new file mode 100644 index 000000000..97e57f321 --- /dev/null +++ b/docs/src/codegen/types/unions.md @@ -0,0 +1,106 @@ +# Unions + +Given + +```c +union Number { + int i; + float f; +}; + +int foo(void) { + union Number u; + u.i = 42; + return u.i; +} +``` + +the unsafe model produces + +```rust +#[repr(C)] +#[derive(Copy, Clone)] +pub union Number { + pub i: i32, + pub f: f32, +} +impl Default for Number { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} +pub unsafe fn foo_0() -> i32 { + let mut u: Number = ::default(); + u.i = 42; + return u.i; +} +``` + +and the refcount model produces + +```rust +pub struct Number { + __bytes: Value>, +} +impl Number { + pub fn i(&self) -> Ptr { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + pub fn f(&self) -> Ptr { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } +} +impl Default for Number { + fn default() -> Self { + Number { + // 4 is sizeof(union Number): the size of its largest member + __bytes: Rc::new(RefCell::new(Box::from([0u8; 4]))), + } + } +} +pub fn foo_0() -> i32 { + let u: Value = Rc::new(RefCell::new(::default())); + (*u.borrow_mut()).i().write(42); + return (*u.borrow()).i().read(); +} +``` + +(`Clone` and `ByteRepr` impls omitted.) + +## Unsafe model + +A union is a Rust `union` with `#[repr(C)]` and `#[derive(Copy, Clone)]`, one +field per member with the same types as a struct would have. Rust cannot derive +`Default` for a union, so a hand-written impl zeroes the bytes, which is also +what C's zero-initialization gives. Members are read and written like struct +fields; the whole function is `unsafe`, so no extra block is needed. + +## Refcount model + +Rust unions are unusable from safe code, so the refcount model stores the union +as one byte buffer, `__bytes: Value>`, sized to the largest member, +and emits one accessor method per member. Each accessor takes a pointer to the +buffer and [reinterprets](../../runtime/reinterpret.md) it as the member type, +returning a `Ptr` (a `Ptr` to the element type for array members). Member +access `u.i` therefore becomes a call, `u.i()`, and the read or write goes +through the pointer: `.read()` and `.write(v)` for scalars, `.upgrade().deref()` +for struct members whose fields are then accessed as usual. + +Because every member views the same bytes, writing through one member and +reading through another has C's semantics: the bytes are reinterpreted, not +converted. This is also why the type must implement `ByteRepr`; the accessor's +`reinterpret_cast` needs the member types to have a byte-level representation. + +`Default` fills the buffer with zeros, `Clone` copies the buffer into a fresh +`Value`, and `ByteRepr` copies the buffer in and out. The Rust struct has no +`pub` fields, so translated code can reach the storage only through the +accessors. + +> [!WARNING] +> +> Accessors are broken on a reinterpreted union. Reading a `Ptr` obtained +> from `reinterpret_cast` builds a temporary `U` from the bytes with +> `from_bytes`, so `p.upgrade().deref().i()` returns a pointer into that +> temporary's buffer, which dangles as soon as the statement ends, and a write +> through it would never reach the original allocation +> ([#311](https://github.com/Cpp2Rust/cpp2rust/issues/311)).