Proposal: generate a layout test package for converted Go packages
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
- issue の種類
- 機能追加
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
調査の方向性
Start by reading llcppg's conversion and type-layout code, the configuration handling for llcppg.cfg, and the end-to-end test suite; the issue does not name specific files. Prototype the first rollout step for non-template records, generating layout_test.go and layout.cpp, then run the generated package through llgo test. Done means mismatches report expected and actual values, failures return a non-zero exit code, and the staged rollout cases are covered.
索引モデルが issue の本文から書いたものです。
説明
1. Goal
llcppg converts C/C++ types into Go types that must have identical memory layouts (see the C/C++ to Go type mapping proposal). Today that guarantee rests on the conversion rules being right. Nothing checks the result.
This proposal makes llcppg generate a layout test package next to the converted package. Running go test (or llgo test) on it verifies, for every converted type:
sizeofmatches.alignofmatches.- The offset of every field matches.
- The size of every field matches.
A mismatch fails the test and names the type, the field, the expected value and the actual value.
2. Usage
After conversion, the output module contains a sibling test package:
mylib/ # converted Go package
mylib_layout/ # generated layout test package (do not edit)
layout_test.go
layout.cpp
Run it like any other test:
llgo test ./mylib_layout
llcppg generates it by default; -layout=false in the command line (or "layout": false in llcppg.cfg) turns it off. Regenerating the package overwrites it, so it is safe to wire into CI.
When a check fails, the output looks like:
--- FAIL: TestLayout/Baz
layout_test.go:42: Baz.X offset: C++ = 4, Go = 0
layout_test.go:43: Baz sizeof: C++ = 8, Go = 4
3. Where the expected values come from
There are two possible sources, and they catch different bugs.
- Static values captured at generation time. llcppg already reads the record layout from Clang when it converts a type. Writing those numbers into the test is cheap and needs no C++ compiler at test time. But it only proves the Go type agrees with what llcppg believed; if llcppg misread the layout, the test agrees with the mistake.
- Values computed by a real C++ compiler at test time. The test package includes a small C++ file that exports
sizeof,alignofandoffsetoffor every converted type. The Go test calls it and compares. This checks against the compiler that actually builds the library, so it also catches ABI and header drift.
Decision: use the second source by default. Offer -layout=static for environments that cannot build C++ code in tests, which emits the first kind instead. The two modes share the same generated Go test structure; only the origin of the expected numbers differs.
3.1 The generated C++ helper
For each type the helper exposes plain functions, with names derived from the Go type name so they are stable across regeneration:
#include <cstddef>
#include "mylib.h"
extern "C" {
size_t llcppg_layout_Baz_size() { return sizeof(baz); }
size_t llcppg_layout_Baz_align() { return alignof(baz); }
size_t llcppg_layout_Baz_off_x() { return __builtin_offsetof(baz, x); }
size_t llcppg_layout_Baz_sz_x() { return sizeof(((baz*)0)->x); }
}
__builtin_offsetof is used because offsetof is only conditionally supported for non-standard-layout types, and types with [[no_unique_address]] or base classes are common here. The helper may need -Wno-invalid-offsetof.
3.2 The generated Go test
func TestLayout(t *testing.T) {
var v mylib.Baz
check(t, "Baz sizeof", cxxSize_Baz(), unsafe.Sizeof(v))
check(t, "Baz alignof", cxxAlign_Baz(), unsafe.Alignof(v))
check(t, "Baz.X offset", cxxOff_Baz_x(), unsafe.Offsetof(v.X))
check(t, "Baz.X sizeof", cxxSz_Baz_x(), unsafe.Sizeof(v.X))
}
Each type gets its own subtest, so one failing type does not hide the others.
4. What is and is not checked
Checked
- Every converted struct/class: size and alignment.
- Every field that has a Go counterpart: offset and size.
- Embedded base classes: offset and size, using the embedded field name.
- Instantiated templates (see 4.2).
Skipped
- Generated padding fields (
_llcppg_paddingN). They exist to make the other offsets right; they have no C++ counterpart. - Bit-fields and union members. Their Go representation is defined by their own proposals; only the size and alignment of the enclosing type are checked. These skips are listed in the test output so they are visible.
- Types llcppg skipped during conversion.
4.1 Standalone empty classes
By design (known limitation in the type mapping document), an empty C++ class has sizeof 1 and its Go counterpart has unsafe.Sizeof 0. The test records this as an expected exception rather than a failure: it asserts C++ size is 1 and Go size is 0. If either side changes, the test fails, so the exception cannot silently drift.
Types that merely contain an empty class are checked normally. That is the case the padding rules exist for.
4.2 Templates
llcppg cannot know template arguments, but the layout directives (//llgo:padding, //llgo:padding-if, //llgo:no-padding) only make sense when verified on concrete instantiations. The test package therefore checks a set of instantiations:
- Instantiations that already appear in the converted headers (as field types, typedefs or base classes). These are collected automatically.
- Extra instantiations listed in
llcppg.cfg, so authors can cover the interesting combinations, in particular the empty-type ones.
Suggested config shape:
{
"layout": {
"instances": [
"Object<Empty, Empty>",
"Object<Empty, int>",
"Object<int, Empty>"
]
}
}
Each instance produces a C++ helper entry and a Go check on the corresponding instantiated Go type, for example mylib.Object[mylib.Empty, mylib.Empty].
The "same empty type twice" case needs explicit attention, since that is where the simple padding model breaks. llcppg should add <T, T> for every template that has more than one type parameter used as a [[no_unique_address]] member whenever T can be an empty class, even if no such instantiation appears in the headers.
5. Behavior and failure reporting
- One failure per mismatched value, with type, member, expected (C++) and actual (Go).
- A final summary line with the count of checked types, checked fields and skipped items.
- Non-zero exit code on any mismatch, so CI fails.
- Output order follows header declaration order, which keeps diffs stable.
6. Responsibilities
- llcppg generates
layout_test.goandlayout.cppfrom the same type information it used for conversion, and regenerates them whenever the package is regenerated. - LLGo builds the C++ helper together with the test and resolves the
extern "C"calls. - Library authors only run
llgo test, and may extendlayout.instancesfor templates.
7. Rollout
- Non-template structs and classes: size, alignment, field offsets and sizes.
- Embedded bases, standalone empty-class exceptions, skipped-item reporting.
- Template instantiations from headers, then config-listed instantiations.
-layout=staticmode.- Run the generated package for every package in the llcppg end-to-end test suite and in goplus/lib CI.
8. Open questions
- Package naming and location: sibling directory (
mylib_layout) versus aninternal/layouttestsubpackage of the converted module. - How to name helper functions for types in nested namespaces and for template instances so the names stay stable across regeneration.
- Whether the helper should also verify enum underlying type sizes and function pointer fields, which are layout-relevant but are not records.
- Handling of ABIs other than Itanium: offsets from a real compiler are correct by construction, but the expected-exception list in 4.1 is ABI-specific.
- Whether a failing layout check should also be offered as a generation-time warning when the C++ compiler is available, instead of waiting for tests.
- 主要言語
- Go
- スター
- 39
- フォーク
- 13
- 平均マージ
- 1時間 33分
- マージ済み PR(30日)
- 256
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
goplus/llcppg のほかの issue
-
難易度 3/5 1〜2日 初心者へのやさしさ 35/100
メンテナーはふだん 1 日以内に返信
-
configuration
難易度 4/5 3〜5日 初心者へのやさしさ 30/100
メンテナーはふだん 1 日以内に返信
-
Architecture-Design feature proposal
難易度 5/5 1週間以上 初心者へのやさしさ 15/100
goplus/llcppg#1056 · コメント 6 件 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 5/5 1週間以上 初心者へのやさしさ 28/100
メンテナーはふだん 1 日以内に返信
-
bug
難易度 4/5 3〜5日 初心者へのやさしさ 55/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
devantler-tech/ksail#7836 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
keyxmakerx/Chronicle#1469 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 73/100
メンテナーはふだん 1 日以内に返信
-
bug needs-acceptance wg/router-models-inference-runtime
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
vllm-project/semantic-router#5177 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
Gentleman-Programming/gentle-ai#5565 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信