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
4 changes: 3 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 0 additions & 18 deletions cpp2rust/converter/converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<clang::Expr>(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) {
Expand Down
1 change: 0 additions & 1 deletion cpp2rust/converter/converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,6 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {

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__)

Expand Down
2 changes: 2 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
book
mermaid.min.js
mermaid-init.js
4 changes: 4 additions & 0 deletions docs/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
20 changes: 15 additions & 5 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
55 changes: 55 additions & 0 deletions docs/src/codegen/pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# The Translation Pipeline

<style>pre.mermaid { text-align: center; }</style>

```mermaid
flowchart TD
driver["<b>cpp2rust</b><br/><code>cpp2rust/cpp2rust.cpp</code>"]
lib["<b>TranspileSrc / TranspileDir</b><br/><code>cpp2rust/cpp2rust_lib.cpp</code>"]
action["<b>FrontendAction, ASTConsumer</b><br/><code>cpp2rust/ast_consumer.cpp</code>"]
factory["<b>CreateConverter</b><br/><code>cpp2rust/converter/factory.cpp</code>"]
mapper["<b>Mapper::LoadTranslationRules</b><br/><code>cpp2rust/converter/mapper.cpp</code>"]
conv["<b>Converter / ConverterRefCount</b><br/><code>cpp2rust/converter/</code>"]
out["<b>output file</b><br/>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.
52 changes: 52 additions & 0 deletions docs/src/codegen/types.md
Original file line number Diff line number Diff line change
@@ -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<int> 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<i32>,
}
pub unsafe fn count_0(mut item: Item) -> i32 {
return item.id;
}
```

and the refcount model produces

```rust
pub struct Item {
pub id: Value<i32>,
pub name: Value<Box<[u8]>>,
pub refs: Value<Vec<i32>>,
}
pub fn count_0(item: Item) -> i32 {
let item: Value<Item> = Rc::new(RefCell::new(item));
return *(*item.borrow()).id.borrow();
}
```

Every struct field is boxed in its own `Value<T>` 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)).
13 changes: 13 additions & 0 deletions docs/src/codegen/types/bitfields.md
Original file line number Diff line number Diff line change
@@ -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)).
52 changes: 52 additions & 0 deletions docs/src/codegen/types/boxing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Boxing

In the refcount model a variable is boxed: its type `T` is wrapped in
`Value<T>`, an alias for `Rc<RefCell<T>>` (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<i32>` | `Value<Item>` | `Value<Box<[i32]>>` |
| function parameter, return type | `i32` | `Item` | decays to `Ptr<i32>` |
| pointee of `Ptr<T>`, 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<i32> = Rc::new(RefCell::new(a));
let item: Value<Item> = 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<T>` 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<std::vector<int>>` maps to `Vec<Value<Vec<i32>>>`, and
the `carray` rules map `int a[2][2]` to `Box<[Value<Box<[i32]>>]>`, both before
the outer `Value<...>` of the declaration is added.
138 changes: 138 additions & 0 deletions docs/src/codegen/types/casts.md
Original file line number Diff line number Diff line change
@@ -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 `<E>::from(0)`.
- Pointer to `bool`: `!p.is_null()`.
- Integer to enum: `<E>::from(x)`, the `From<i32>` 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
`<Color>::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<usize> = Rc::new(RefCell::new(20_usize));
let r: Value<u64> = 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 <target>`. 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<u32> = Rc::new(RefCell::new(67305985_u32));
let bytes: Value<Ptr<u8>> =
Rc::new(RefCell::new(value.as_pointer().reinterpret_cast::<u8>()));
let any: Value<AnyPtr> = Rc::new(RefCell::new((*bytes.borrow()).to_any()));
let back: Value<Ptr<u8>> =
Rc::new(RefCell::new((*any.borrow()).reinterpret_cast::<u8>()));
```

### 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<T>` is a weak reference to a `RefCell<T>`, so it cannot simply be
relabeled as a `Ptr<U>`: 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::<U>()` produces a `Ptr<U>` 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::<T>()` recovers a `Ptr<T>` 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<T>`, where the `as` only names the pointer type. An
upcast from a derived class to an abstract base becomes
`(p.to_strong() as Value<dyn Base>).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.
Loading
Loading