一键导入
c-codegen-2c
Comprehensive guide to the C translation backend (2c) — how Chemical AST is translated to C code, key patterns, gotchas, and optimization strategies.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Comprehensive guide to the C translation backend (2c) — how Chemical AST is translated to C code, key patterns, gotchas, and optimization strategies.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Comprehensive guide to the Chemical compiler test infrastructure — how tests are organized, written, and executed. Covers the test framework, @test annotation dispatch, test_env and test libraries, and how compiler plugins get tested via lang/tests/build.lab.
Comprehensive deep-dive into the Chemical build pipeline — how chemical.mod is converted to build.lab, how build scripts are JIT-compiled via TinyCC, how jobs are created and executed, and how ASTProcessor orchestrates the parallel compilation passes.
All documentation related to building and running Project or Tests
Comprehensive guide to the Chemical Compiler Binding Interface (CBI) — how compiler plugins are built, registered, and integrated into the compilation pipeline.
Documentation of Syntax and APIs for Chemical Programming Language, A must read before implementing something in chemical programming language.s
Comprehensive guide to Chemical's compiler intrinsic functions and reflection APIs — how GlobalFunctions.cpp provides interpreter-friendly implementations, compile-time reflection, and metadata access.
| name | C Codegen (2c) |
| description | Comprehensive guide to the C translation backend (2c) — how Chemical AST is translated to C code, key patterns, gotchas, and optimization strategies. |
The "2c" backend translates the Chemical AST into C code. This is used by TCCCompiler and is also the foundation for the TinyCC JIT compilation path. The 2c backend produces readable, portable C that can be compiled with any C compiler.
Type-checked AST → ToCAstVisitor (structure/declaration level) → BufferedWriter → C source file
| File | Purpose |
|---|---|
preprocess/2c/2cASTVisitor.h | Main C codegen visitor declaration — ToCAstVisitor class |
preprocess/2c/2cASTVisitor.cpp | Main implementation — translates all AST nodes to C |
preprocess/2c/SubVisitor.h | Base classes and utilities for C translation visitors |
preprocess/2c/CTopLevelDeclVisitor.h | Top-level declaration visitor (functions, structs, globals) |
preprocess/2c/CDestructionVisitor.h | Visitor for destructor code generation |
preprocess/2c/2cBackendContext.h | Backend context — manages output, naming, and state |
preprocess/2c/BufferedWriter.h | Efficient buffered output writer |
Chemical structs become C structs:
struct Point {
var x : int
var y : int
}
typedef struct Point {
int32_t x;
int32_t y;
} Point;
Struct methods become C functions with the self pointer as the first parameter:
func p.sum(&self) : int {
return self.x + self.y;
}
int32_t Point_sum(Point* self) {
return self->x + self->y;
}
Chemical functions become C functions with name mangling applied:
| Chemical Function | C Name |
|---|---|
func foo() | foo (no mangling for plain names) |
func Namespace::foo() | Namespace_foo (scope prefix) |
generic foo<T>() | foo__cgs__N or foo__cfg__N (generic suffix) |
@extern func printf() | printf (no mangling at all) |
@no_mangle func bar() | bar (no mangling) |
Functions returning structs use the sret (struct return) pattern via compound expressions:
// Chemical:
func create_point(x : int, y : int) : Point { ... }
// C — hidden sret pointer:
void create_point(Point* __result, int32_t x, int32_t y);
// Using the result in an expression:
(*({ struct Point __tmp; create_point(&__tmp, 1, 2); &__tmp; }))
Warning: When the result is discarded (expression-statement context), gcc -Wall emits -Wunused-value on the * dereference. This is a pre-existing pattern throughout generated C.
&selfIn a method chain like a.b().c():
b() is called on a — generates receiver variablec() is called on the result of b() — if c() has no &self parameter, do not create a receiver variableBefore the fix: ToCAstVisitor created an unused struct Type* __chx__recv__N variable, producing "unused variable" warnings.
After the fix: Check if the called function has a self parameter before creating the receiver variable.
| Chemical | C |
|---|---|
*int | int32_t* |
*mut int | int32_t* (no const) |
*char | const char* |
&int (ref) | int32_t* (same as pointer) |
[10]int | int32_t[10] |
Variants become a struct with a discriminator and union:
variant Option<int> {
Some(value : int)
None()
}
typedef struct Option_i32 {
uint8_t __disc; // discriminator
union {
struct { int32_t value; } Some;
struct { } None;
} __data;
} Option_i32;
The BufferedWriter and CTopLevelDeclVisitor handle:
__chx__ prefix to avoid collisions__chx__temp__N for intermediate values__chx__result for sret pointers__chx__recv__N for method chain receivers__chx__for__N, __chx__while__N for break/continue targets| Construct | C Pattern |
|---|---|
if/else | if(...) { ... } else { ... } |
while | while(...) { ... } |
do-while | do { ... } while(...) |
for | for(...; ...; ...) { ... } |
switch | switch(...) { case ...: ... } |
break | goto __chx__break__N; (for nested loops) |
continue | goto __chx__continue__N; |
| Defer | N/A (no defer in Chemical) |
Break/continue implementation: Because C's break/continue only works for the innermost loop, Chemical's break/continue are implemented as goto to unique labels. Each loop gets a __chx__break__N and __chx__continue__N label.
class ToCBackendContext {
BufferedWriter writer; // Output buffer
std::vector<ASTNode*> currentScope; // Scope stack
bool hasReturnType; // Current function has non-void return
// ... encoding state, name counters, etc.
};
The backend maintains scope information for:
Functions returning structs use the (*({ ... })) pattern. When the result is discarded:
// Generated when discarding a struct return:
(*({ struct Type __tmp; func(&__tmp, args...); &__tmp; }));
// gcc -Wall: warning: unused value
Workaround: Wrap in (void) cast or use a different pattern for discarded results.
Chemical bool is i1 in LLVM but int in C. The codegen must:
// Chemical bool:
bool flag = true;
// C translation:
int32_t flag = 1; // bool promoted to int32_t
Chemical allows zero-length arrays. In C, this requires flexible array member or zero-length array extension:
// Chemical: var arr : [0]int
// C (GCC extension): int32_t arr[0];
Chemical does NOT support variadic function declarations for user-defined functions. Only @extern C functions use variadic args:
// Chemical:
@extern func printf(format : *char, ...) : int
// C:
extern int printf(const char* format, ...);
Chemical supports bare { } as a scope expression. These must generate a compound statement:
// Chemical:
var x = {
var tmp = 42;
tmp
};
// C:
int32_t x = ({ int32_t tmp = 42; tmp; });
Chemical supports hex float literals. C99+ supports 0x prefix for floats:
// Chemical: 0x1.921fb6p+1f
// C: 0x1.921fb6p+1f
Currently, the 2c backend is mostly serial within a job. Future strategies:
BufferedWriter with pre-allocated buffers# With TCCCompiler:
cmake-build-debug/TCCCompiler "lang/compiled/temp.ch" -o "lang/compiled/temp.c" -v -bm-modules
The .c extension tells the compiler to emit C source instead of an executable.
| Warning | Cause | Fix |
|---|---|---|
unused variable | Unused receiver for method chain without &self | Check receiver requirement in ToCAstVisitor |
unused value | Discarded struct-returning expression | Use different pattern for discarded results |
incompatible pointer types | Wrong type in a cast | Check type conversion in codegen |
implicit declaration | Missing forward declaration | Declare before use or use extern |
When adding a new AST node to the 2c backend:
2cASTVisitor.cpp2cASTVisitor.hCDestructionVisitor.h or CTopLevelDeclVisitor.h if neededSubVisitor.h — add the node type to the dispatch table// In 2cASTVisitor.cpp:
void ToCAstVisitor::VisitNewNode(NewNode* node) {
// 1. Handle any dependencies
VisitNode(node->dependency);
// 2. Generate C code
writer.write("/* chemical new node */\n");
// 3. Visit children
for(auto child : node->children) {
VisitNode(child);
}
}