Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Proposal: preserve special C type semantics across llcppg and LLGo

Đang mở
#1,033 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
18/100
Loại issue
Tính năng
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
go
Lĩnh vực
compilers, tooling

Hướng nghiên cứu

Start by reading #1032 and the generation probes described in this issue, then inspect llcppg’s existing C/C++ wrapper generation and the cited LLGo atomic, complex, and long-double implementations. Compare the compiler-native and C-wrapper candidates for each special type, including layout, access, and calling conventions on the stated Linux/amd64 revisions. Done means the design choices and contracts are resolved; this proposal does not identify an implementation task or approve a route.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

feature proposal

Summary

Split the special-type and access-semantics work out of #1032. That issue remains about declared records/enums, non-array pointers, anonymous-member promotion, and the existing bit-field contract. This proposal describes the intended generation for every special case found in that investigation and identifies the boundary between llcppg, LLGo, and the C support library.

The design question is how to preserve C representation, access semantics, and calling convention when an ordinary Go field or function type is insufficient. LLVM having a matching IR type is not evidence that LLGo already exposes it correctly to Go source.

This is a design proposal, not an implementation approval. The compiler-native and C-wrapper routes below are alternatives for review; a C-wrapper fallback has not been selected. No new public type names, function signatures, compiler directives, configuration fields, or dependency policy are finalized here.

Scope and evidence

Included: _Atomic, _Complex, signed/unsigned __int128, _Float16, __float128, target-specific long double, their pointer/typedef uses, relevant alignment requirements, const/volatile/restrict, and old-style unprototyped function pointers.

All C array forms remain deferred together with flexible arrays. Internal representation buffers are not a proposal to enable C array conversion. Ordinary nested records/enums and union bit-fields remain in #1032.

The generation probes used llcppg 6950e22b8e0c2008384bc06d02575c03e20323a2 (the #1031 branch snapshot), Linux/amd64, LLVM 22.1.8, LLGo b86d349178d610e6b15e95c2a5fdfcb130d09fa1, and goplus/lib v0.6.1. This is not a claim about newer revisions or other targets.

Case Observed result
_Atomic(int), _Atomic(int *), and _Atomic(int) * Unsupported: struct owner omitted or union generation fails
Tested float/double _Complex values and double-complex pointers Unsupported
Tested signed/unsigned __int128, _Float16, __float128 Unsupported; some union cases first fail on alignment 16
long double value Struct output builds but exact alignment is not established; union generation panics on alignment 16
long double * and long double ** Generation/build succeed; dereference and by-value ABI were not tested
Tested const, volatile, restrict combinations Output builds but erases the qualifiers
int (*legacy)() Unprototyped function type unsupported; distinct from int (*callback)(void)

Some unsupported struct inputs exit 0 after dropping the owner, so process exit alone is not evidence of support. The broader partial-generation diagnostic policy remains a separate decision in #1032.

Existing capabilities to reuse

  • C atomic operations in goplus/lib v0.6.1 include Load, Store, exchange, and read-modify-write operations. Their current generic constraint covers selected integer types and unsafe.Pointer, not every possible C atomic type.
  • LLGo atomic lowering at the tested revision uses sequentially consistent load/store operations. The SSA layer can mark loads/stores volatile, but that is not a demonstrated source-level qualifier contract for generated bindings.
  • Existing C complex bindings use complex64 and complex128 for C complex function arguments/results. This is a concrete starting point, not proof of all aggregate layouts and ABIs.
  • The x87 LongDouble carrier documents 16-byte C alignment versus its at-most-8-byte Go alignment.
  • The current software Int128 type, exposed through c.Int128T, is a high/low-word representation. It must not be assumed to have native C __int128 layout or calling convention.
  • llcppg already emits C/C++ wrappers for existing features. The existing ffi package supplies explicit-signature call machinery. Neither capability justifies silently changing every binding into a wrapper or adding a default libffi dependency.

Proposed generation, case by case

These are candidate contracts for review. A type is not fully supported merely because its bytes fit in a buffer: storage, access, and calls must each match the promised behavior.

1. Atomic values and atomic pointers

Preserve where atomicity applies:

C member Intended representation and access
_Atomic(int) value; Atomic integer storage with atomic load/store; not an unrestricted ordinary integer field
_Atomic(int *) pointer; Atomic pointer slot; loading/storing the slot is atomic, but the pointed-to integer is not thereby atomic
_Atomic(int) *pointer; Ordinary pointer slot pointing to an atomic object; dereferencing must retain atomic access semantics
Other atomic scalar or aggregate types Use the target's actual atomic size/alignment and operations, not the unqualified type's layout

The distinction is observable even in layout. In a Clang 22 Linux/amd64 probe, struct Triple { char a, b, c; }; has size/alignment 3/1, while _Atomic(struct Triple) has size/alignment 4/4. Removing _Atomic can therefore corrupt both access semantics and enclosing-field offsets.

For a compiler-native route, llcppg would preserve atomic type identity and target layout while LLGo lowers the corresponding operations. Reuse existing atomic operations where their type support and ABI are proven sufficient; extend compiler/runtime handling for the other approved cases rather than pretending they fit the current generic constraint.

For an explicit wrapper route, generated accessors would perform C atomic operations against the real C object. Ordinary C atomic reads/assignments use the corresponding sequentially consistent behavior. Initialization, supported memory orders, and any callable exchange/CAS/RMW methods need concrete contracts before implementation.

In either route, ++, +=, exchange, and CAS must remain single atomic read-modify-write operations where required. A getter followed by a setter is not an equivalent implementation. A raw mutable *T escape must not be advertised as preserving atomic semantics. Arbitrary existing user pointers do not become owned allocations of the binding.

2. Complex values

The proposed first mapping is explicit:

C type Candidate generated Go type
float _Complex complex64
double _Complex complex128
Pointers to those types Corresponding typed pointers, subject to verified target layout
long double _Complex A representation using two correctly represented target long double components, not a narrowing to complex128

The float/double cases have existing LLGo/library precedent and may primarily need llcppg conversion work and ABI validation, rather than a new compiler primitive. Struct field offsets, real/imaginary placement, alignment, and function argument/result lowering still need independent checks.

A union containing complex values must account for their scalar components when applying its storage/ABI rules. Do not label the aggregate correct merely because its total size matches. If the existing union representation cannot express a verified target's calling convention, that is a specific LLGo/representation requirement, not permission to keep an incorrect lowering.

Long-double complex is an additional design case, not a completed probe in the current inventory. Its generation depends on the long-double contract below.

3. Signed and unsigned 128-bit integers

The generation target is an exact signed/unsigned 128-bit value with the target's C alignment, field layout, and argument/result convention. Signedness must survive conversion.

The native candidate requires an LLGo-recognized representation that lowers as the corresponding native C integer, including conversions and any promised operations. llcppg then selects that representation instead of silently treating it as two ordinary Go integer fields.

The alternative wrapper candidate marshals the value through explicit high/low-word or exact-bit transport and lets C perform native field access/calls. If the existing software c.Int128T/c.Uint128T types are used as user-facing numeric carriers, conversion is numeric marshaling, not an unsafe pointer cast. Word order, endianness, signed conversion, and alignment must be checked.

In particular, the tested C __int128 requires alignment 16. Equal byte size does not make the existing software type an ABI-compatible embedded C field. A new native representation or a C-owned object route is needed wherever ordinary Go layout cannot meet that alignment.

4. Half-precision floating point

For _Float16, preserve the 16-bit floating-point representation and its C calling convention. A Go uint16 can transport bits, but is not interchangeable with a half-precision numeric value.

The native candidate uses a compiler-recognized half-precision carrier with the required load/store, conversions, and call lowering. Exact public naming or annotation syntax is not selected here.

The wrapper candidate copies the exact representation to/from C and performs explicitly requested numeric conversions in C. It must distinguish bit-preserving transport from value conversion; handling of NaNs, infinities, signed zero, and rounding belongs to the contract. A wider Go float used for a conversion must not silently replace the original field's storage or ABI.

5. Quadruple-precision floating point

For __float128, preserve the full supported target representation and precision; do not generate float64.

The native candidate requires a compiler-recognized scalar with matching layout, operations that the binding promises, and C call lowering. Merely emitting LLVM fp128 somewhere is insufficient without matching source-level type/layout handling.

The wrapper candidate uses exact representation transport and C operations/calls. A byte/word carrier can preserve values for transport, but is not a claim of native Go arithmetic or ABI-compatible inline storage. The observed alignment-16 union failure must be addressed under the aggregate-alignment contract, not bypassed by choosing an under-aligned integer buffer.

6. Target-specific long double and extended alignment

Read the actual format, size, and alignment from the selected target. Do not infer the format from byte count alone or treat every target's long double as double.

For targets where the existing representation really matches, reuse it after verification. On the observed x87 target, the C type uses an extended-precision value in 16 bytes with alignment 16; the current Go carrier does not establish that alignment.

Two candidate outputs must be distinguished:

  • Native C-compatible value: LLGo must support the required scalar/aggregate representation and alignment, and llcppg can then embed it as a field or use it by value.
  • C-object access: C retains the real storage and alignment; generated views/accessors and call wrappers operate on that object. This changes construction, ownership, and by-value APIs and cannot be introduced as an invisible fallback.

Padding before a field can fix a relative offset but cannot make an under-aligned enclosing object's base address correctly aligned. Stack/global/heap placement and embedded use all matter. Likewise, correct pointer storage does not prove that Go may allocate, dereference, or pass the pointee by value correctly.

For union owners, fixing the alignment limit must preserve both shared storage and the target's call classification. Disabling the existing check while retaining an under-aligned representation is not a support implementation.

7. Const-qualified objects and pointers

The generation candidate preserves the level of qualification:

Declaration Access that a qualifier-preserving binding should expose
const T value; Read-only value access; no write operation or ordinary mutable reference to the original object
const T *pointer; Assignable pointer slot, read-only pointee access
T *const pointer; Read-only pointer slot, ordinary mutable pointee access
const T *const pointer; Read-only slot and read-only pointee access

A getter that returns an unrestricted mutable pointee does not preserve the second case. The two candidates are compiler-recognized qualification through the relevant pointer levels, or generated read-only views/access methods. Ordinary Go types do not enforce these distinctions by themselves.

Copies obtained through value reads and pointer access to original storage must remain distinct. Any new view type or callable interface requires its own exact API review; no such interface is assumed to exist. Keeping today's qualifier erasure can be a compatibility mode only if explicitly chosen, not presented as complete const support.

8. Volatile-qualified objects and pointers

A volatile access must remain an actual volatile load/store of the correct object and width. It is not implemented by using a mutex, by ordinary field access, or by assuming atomic operations are an equivalent substitute.

  • volatile T value: the field's accesses are volatile.
  • volatile T *pointer: the pointer slot is ordinary; accesses to its pointee are volatile.
  • T *volatile pointer: loading/storing the pointer slot is volatile; the pointee is not automatically volatile.
  • Qualifier combinations must preserve both read-only restrictions and volatile effects where applicable.

The compiler-native candidate connects generated qualifier information or access operations to LLGo's existing volatile-capable backend. The inspected backend capability is real, but no public source-level binding interface for it has been selected.

The wrapper candidate performs these accesses in C using the original qualified type. That is a proposed alternative, not an approved fallback. In either route, exposing a normal pointer whose dereference loses volatility must not be called semantic preservation.

9. Restrict-qualified pointers

Keep the ordinary pointer storage representation. restrict is an access/aliasing contract, not a new memory layout.

Go-side code may conservatively avoid exploiting that optimization promise. C wrappers, if selected, retain the applicable C declaration semantics, and callers remain responsible for the C aliasing requirements. If LLGo is to exploit restrict-derived alias information, its scope and validity must be modeled correctly; a blanket global noalias assumption would not follow from the source contract.

This case may not need a new compiler type or generated helper. It still needs an explicit documented generation decision rather than being grouped with volatile and silently treated as identical.

10. Old-style unprototyped function pointers

int (*legacy)() does not specify the argument types. It is not the same type contract as int (*callback)(void), and a generic Go variadic signature is not automatically equivalent.

The representation candidate preserves a raw C function-pointer value in fields and union storage without inventing a static argument list. Invocation then requires one of two explicit contracts:

  • An actual prototype is supplied/established, and generation uses the existing typed C-call machinery.
  • The caller supplies the concrete signature and arguments to an explicit dynamic-call path, such as the existing ffi facilities on their supported targets, respecting default C argument promotions and actual callee compatibility.

No default libffi dependency or new signature-configuration schema is selected. A native unprototyped-call feature would require a separate LLGo call-lowering contract; it cannot infer absent parameter types. Generating pointer storage is a useful defined output, but must not be described as an automatically type-safe call binding.

Architecture boundary and alternatives

Owner Responsibility
llcppg Preserve Clang type identity, qualification level, target layout and function information; select a supported representation and emit the corresponding access/call binding
LLGo Make any claimed native source representation agree with LLVM layout, alignment, loads/stores, C ABI and the promised operations
C support library Supply reusable existing operations or separately reviewed exact carrier/view APIs where needed
Optional generated C wrappers Perform explicitly selected C-side accesses, conversions, and call adaptation without pretending their carriers are native C-layout Go fields

Prefer existing capabilities where they already satisfy the contract. The two major routes have different consequences:

Route Benefit Cost or boundary
Compiler-native representation Can preserve directly declared/embedded Go values and typed native calls Requires genuine LLGo source-type, layout and ABI support, not just generator aliases
Explicit C-wrapper/object representation Lets Clang/C implement qualified access, unsupported scalar operations, and native call adaptation May change allocation, lifetime, views and by-value APIs; wrapper compilation and any required runtime libraries must be explicit
Bit-preserving transport only Preserves an exact value for copying/marshaling Does not by itself provide correct inline alignment, arithmetic, qualified access or native calls

No route is silently chosen by this issue. Supporting one row does not require bundling all compiler work into a single patch. Conversely, putting a type into an opaque buffer is not a complete answer unless its allowed accesses, ownership and calls are specified.

Verification and compatibility

Each accepted representation must be checked independently for fields in struct/union owners, pointers, typedef aliases and function parameters/results where supported. Required evidence includes:

  • Size, alignment and offsets compared with Clang/C for the selected target; correct alignment of actual objects, not just matching field padding.
  • C-to-Go and Go-to-C read/write or value-transport checks, and separate by-value/callback checks where those calls are promised.
  • Atomic layout, initialization and operation behavior, including distinguishing an atomic pointer from a pointer to an atomic object; RMW must not degrade to load-plus-store.
  • Volatile operations present in the generated IR or wrapper operations; const-view APIs must not accidentally expose ordinary mutable access.
  • Exact integer/float transport, signedness, special floating-point values, rounding and conversions at their documented boundaries.
  • Existing ordinary types and the #1032 cases continue to generate as before; no accidental widening into the deferred C array work.

Compiler/runtime changes are shared and non-local. Assess all actual callers, target ABIs, allocation/layout consumers and calling paths before implementation; narrow the supported target set or explicitly agree unverified conditions rather than generalizing from one platform. The completed probe evidence is Linux/amd64 with LLVM 22.1.8 only; other operating systems, architectures, endianness and LLVM versions are not established by this report.

Use existing fixture/test boundaries and LLGo for generator validation. This issue adds no tests, exported functions, compiler syntax or implementation. Exact new interfaces and the compiler-native versus wrapper decisions must be approved before they become a specification or code change.

Decisions to settle

  1. Which cases can reuse the inspected compiler/library facilities unchanged, and which require a new LLGo type/access/call contract?
  2. For cases without a compatible native representation, should the binding wait for compiler support or explicitly expose C-object/wrapper access? The user has not selected a wrapper fallback.
  3. What exact carrier/view/API contracts are needed for any selected route? No hypothetical API names in this issue should be interpreted as existing ones.
  4. What target matrix is required for each type and operation before it is advertised as supported?

Every special case from the earlier unsupported/partial-support inventory has a generation target and a concrete route above. The remaining questions select and validate those routes; they are not a substitute for describing the intended output.

Ngôn ngữ chính
Go
Star
39
Fork
13
Merge trung bình
1 giờ 28 phút
Pull request đã merge (30 ngày)
256

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của goplus/llcppg

Tất cả issue của goplus/llcppg

Issue tương tự

Thêm issue về Go

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.