VPHP Compiler Architecture
May 19, 2026 ยท View on GitHub
Goal
vphp.compiler is responsible for turning V source annotated with VPHP metadata into:
php_bridge.hphp_bridge.cbridge.v
The compiler is intentionally split into a few small layers so that parsing, linking, and code generation can evolve independently.
Top-Level Layout
vphp/compiler/
entry.v # Compiler entry and compile pipeline
export.v # export assembly and final file emission
c_emitter.v # C-side wrapper and glue emission
c_builder_binding.v # repr -> C builder mapping
c_function_glue.v # global PHP_FUNCTION wrapper emission
c_class_glue.v # class PHP_METHOD wrapper emission
c_type_glue.v # interface/enum C implementation emission
c_name_binding.v # C symbol / PHP name lookup helpers
c_template.v # small C template helpers
v_glue.v # V-side bridge/glue emission
function_binding.v # function wrapper glue planning
arg_binding.v # PhpArgRepr -> V glue argument bindings
params_struct_binding.v # @[params] struct argument construction
return_binding.v # PhpReturnRepr -> V glue return handling
class_lifecycle_binding.v # class raw allocation / cleanup glue
class_shadow_binding.v # class shadow const/static accessors
class_method_binding.v # class method call / sync / return composition
class_property_binding.v # class property get / set / sync glue
class_handlers_binding.v # class handler table glue
inherited_receiver_binding.v # inherited receiver load / sync glue
php_types/ # shared PHP-facing type/spec mapping
repr/ # compiler representations
parser/ # AST -> repr
linker/ # repr -> linked repr
builder/ # repr -> export/code fragments
Layer Responsibilities
1. repr
Module: vphp.compiler.repr
Purpose:
- Define the compiler's internal representations
- Hold normalized export metadata
- Stay as close as possible to "plain data"
Examples:
PhpClassReprPhpInterfaceReprPhpEnumReprPhpFuncReprPhpConstReprPhpTaskReprPhpGlobalsRepr
Non-goals:
- No AST walking
- No builder logic
- No final code emission
2. parser
Module: vphp.compiler.parser
Purpose:
- Parse V AST nodes into
reprvalues - Own all "how do we read this V syntax?" logic
Examples:
parse_class_decl(...)parse_interface_decl(...)parse_enum_decl(...)parse_function_decl(...)parse_constant_decl(...)add_class_method(...)add_class_static_method(...)
Input:
v.astnodes
Output:
repr.Php*Repr
3. linker
Module: vphp.compiler.linker
Purpose:
- Resolve relationships that are not fully known during the initial parse
- Perform post-parse enrichment on
repr
Current responsibility:
- Class shadow linking:
- shadow static properties
- shadow constants
Current entry:
link_class_shadows(mut elements, table)
This layer exists to keep entry.v from becoming a second parser.
4. builder
Module: vphp.compiler.builder
Purpose:
- Convert
reprinto reusable export/code fragments - Encapsulate repetitive Zend/C boilerplate assembly
Key types:
ClassBuilderFuncBuilderConstantBuilderModuleBuilderExportFragments
This layer should answer:
- "What should this symbol export?"
- "What declarations / registrations / tables does it contribute?"
It should not answer:
- "How do I parse V syntax?"
4.5. php_types
Module: vphp.compiler.php_types
Purpose:
- Describe PHP-facing type semantics once
- Share V type normalization between glue and arginfo generation
- Keep semantic wrapper metadata out of ad hoc emitter branches
Typical examples:
PhpTypeSpecPhpTypeSpec.from_v_type(...)PhpTypeSpec.semantic_wrapper_for(...)PhpDefaultSpec.from_v_type(...)normalize_v_type_key(...)
This layer is intentionally small. It should describe facts such as
"PhpArray maps to PHP array and uses PhpArg.array() for decoding"; it
should not render C macros or V glue lines directly.
5. export
File: vphp/compiler/export.v
Purpose:
- Orchestrate all export fragments
- Emit final
php_bridge.handphp_bridge.c - Coordinate
builder,c_emitter, andv_glue
This file is not a parser and not a low-level emitter. It is the assembly layer.
6. c_emitter
File: vphp/compiler/c_emitter.v
Purpose:
- Generate concrete C wrappers and method glue
- Emit C function bodies that cannot be expressed as simple builder fragments
Typical examples:
PHP_METHOD(...)wrappers- object construction wrappers
- instance/static method bridge templates
6.5. c_builder_binding
File: vphp/compiler/c_builder_binding.v
Purpose:
- Convert
reprobjects intobuilderobjects used for arginfo, type declarations, method tables, constants, interfaces, enums, and class metadata - Keep
repr -> buildermapping out of concrete C wrapper body emission
Key responsibilities:
build_func(...)build_class_type(...)build_interface_type(...)build_enum_type(...)- shared visibility, argument, attribute, and return-spec mapping helpers
This layer answers "what builder model should represent this compiler repr?"
c_emitter.v still owns concrete C wrapper bodies and template selection.
6.6. c_function_glue
File: vphp/compiler/c_function_glue.v
Purpose:
- Emit concrete global
PHP_FUNCTION(...)wrapper bodies - Keep function entry forwarding separate from class method template selection
- Reuse
build_func(...)for arginfo while owning the C-side call/return checks
This layer is intentionally small because global function glue has a much smaller decision surface than class method glue.
6.7. c_class_glue
File: vphp/compiler/c_class_glue.v
Purpose:
- Emit concrete class
PHP_METHOD(...)wrapper bodies - Own class method template selection for constructors, static methods, instance methods, inherited receivers, context-aware methods, and object returns
- Keep the large class wrapper decision tree out of the top-level C emitter
6.8. c_type_glue
File: vphp/compiler/c_type_glue.v
Purpose:
- Emit concrete C implementation fragments for PHP interfaces and native enums
- Keep simple type implementation bodies separate from the top-level C export assembly
6.9. C Helper Files
Files:
vphp/compiler/c_name_binding.vvphp/compiler/c_template.v
Purpose:
- Keep C symbol/PHP name lookup helpers and small template helpers out of the top-level C export assembly
- Share these helpers between function/class/type C glue files without making
c_emitter.vgrow again
7. v_glue
File: vphp/compiler/v_glue.v
Purpose:
- Generate the V-side bridge layer in
bridge.v - Connect PHP-visible wrappers to real V logic
Typical examples:
@[export: 'vphp_wrap_xxx']- task registration glue
- object property sync helpers
Ownership-facing rule:
- generated glue should prefer
ctx.arg[T](...)withT = RequestBorrowedZBox/RequestOwnedZBox/PersistentOwnedZBoxwhen the exported V signature can express that ownership shape directly - raw
ZValremains the low-level escape hatch for bridge internals and callable-heavy paths
Zend boundary rule:
- generated Zend ABI signatures may contain raw C pointer shapes such as
ex &C.zend_execute_data,ret &C.zval,rv &C.zval, orvalue &C.zval - the generated function body should wrap those values at the top of the
function with
vphp.Context.from_ptr(...),vphp.PhpObjectPropertyHandler,vphp.ZVal.from_ptr(...), orvphp.ZendObject.from_ptr(...) - after that entry wrapping, generated V glue should use runtime wrappers
instead of spelling direct
C.vphp_*, oldfrom_raw(...),raw_zval(), or manualC.zval{}patterns
7.5. arg_binding
File: vphp/compiler/arg_binding.v
Purpose:
- Convert
repr.PhpArgReprinto V glue argument bindings - Keep PHP argument indexing,
Contextescape hatch handling, semantic wrapper decoding, lifecycle box decoding, and@[params]struct construction in one place
Key types:
PhpArgBindingPhpArgBindingKindPhpArgReadPhpArgSetup
This layer answers "how does this exported parameter become a V call argument?"
The caller-facing glue files should consume PhpArgSetup instead of
reimplementing argument indexing or wrapper decoding.
PhpArgRead is the shared internal read expression for both ordinary
parameters and flattened @[params] struct fields. It owns the generated
php_args.at_named_or_index(...), php_args.has_named_or_index(...), default
fallback, and semantic wrapper decoding expressions so those rules do not drift
between the two binding paths.
Implementation files:
arg_binding.v: top-level parameter binding planarg_read.v: read / presence / semantic wrapper expressionsarg_default.v: optional default literal conversion
arg_read.v also contains the positional Context argument expression helper
used by generated struct closure bridges, so closure bridge fields and ordinary
argument glue do not maintain separate type-to-read switch tables.
PHP-facing argument names are resolved before this layer. Direct V parameters
and @[params] struct fields both default from snake_case to camelCase,
unless @[php_arg_name] provides an explicit override.
7.6. function_binding
File: vphp/compiler/function_binding.v
Purpose:
- Build and render plain function wrapper glue from
repr.PhpFuncRepr - Keep struct-closure helper emission, argument setup, keyword-safe call names,
request frame scope, and
ReturnBindingbehavior out ofv_glue_func.v
Key types:
FunctionGlue
This layer answers "how does an exported V function become a generated
vphp_wrap_* function?"
7.7. params_struct_binding
File: vphp/compiler/params_struct_binding.v
Purpose:
- Convert flattened
@[params]fields into one V params struct argument - Keep params struct field indexes, field defaults, and field value expressions out of generic argument binding
Key types:
ParamsStructBinding
This layer answers "how do these PHP arguments become this V @[params]
struct literal?"
7.8. return_binding
File: vphp/compiler/return_binding.v
Purpose:
- Convert return type strings into V glue return behavior
- Keep
!T,?T,void, plain value, and returned-closure handling in one place
Key types:
ReturnBindingReturnBindingKind
This layer answers "how does this V call result get written back to PHP?" Glue
files should consume ReturnBinding instead of reimplementing result / option /
closure branches.
7.9. class_method_binding
File: vphp/compiler/class_method_binding.v
Purpose:
- Compose class method call results with class-specific side effects
- Keep wrapper signature, inherited receiver loading, request frame scope,
static-property sync, object returns, and
ReturnBindingbehavior out of the main class glue loop
Key types:
ClassMethodGlueClassMethodGlueContext
This layer answers "after a generated class method call runs, what extra PHP runtime state must be written back, and how is the result returned?"
ClassMethodGlue builds the method wrapper plan from PhpMethodRepr, including
the glue name, struct-closure helper, argument setup, call expression, and return
context. ClassMethodGlueContext owns the wrapper body details once that plan is
known.
7.10. class_lifecycle_binding
File: vphp/compiler/class_lifecycle_binding.v
Purpose:
- Generate raw V object allocation, cleanup, and free glue for PHP class handlers
- Keep generated lifecycle helper shape out of the main class glue loop
Key types:
ClassLifecycleGlue
This layer answers "which raw lifecycle helper functions must be emitted for a
class, and how do optional cleanup() / free() hooks run?"
7.11. class_property_binding
File: vphp/compiler/class_property_binding.v
Purpose:
- Generate class property read, write, and sync glue
- Keep scalar public property filtering and per-type read/write code out of the main class glue loop
Key types:
ClassPropertyGlueClassPropertyFieldBinding
This layer answers "how are V struct fields exposed and synchronized as PHP object properties?"
ClassPropertyFieldBinding owns the per-field filter and scalar read/write/sync
emission. The class-level glue only decides which handler function is being
rendered.
7.12. class_shadow_binding
File: vphp/compiler/class_shadow_binding.v
Purpose:
- Generate class shadow const/static accessors
- Keep PHP static-property synchronization helpers out of the main class glue loop
Key types:
ClassShadowGlue
This layer answers "how does generated V code access and synchronize a class' shadow const/static state?"
7.13. class_handlers_binding
File: vphp/compiler/class_handlers_binding.v
Purpose:
- Generate the class handler table factory used by the C bridge
- Keep handler pointer wiring out of the main class glue loop
Key types:
ClassHandlersGlue
This layer answers "which generated V functions are wired into
ZendClassHandlers for this class?"
7.14. inherited_receiver_binding
File: vphp/compiler/inherited_receiver_binding.v
Purpose:
- Generate inherited receiver load/sync helpers for V classes backed by PHP parent objects
- Keep per-field scalar / ZVal load and scalar write-back expressions out of the main class glue loop
Key types:
InheritedReceiverGlueInheritedReceiverFieldBinding
This layer answers "when a V receiver is reconstructed from a PHP object, which fields are read from the object and which scalar fields are synchronized back?"
Compile Pipeline
The current pipeline in entry.v is:
flowchart TD
A["Parse V files into AST"] --> B["Scan type declarations"]
B --> C["Scan functions / constants / tasks"]
C --> D["Link shadow statics and constants"]
D --> E["Collect export fragments"]
E --> F["Emit php_bridge.h / php_bridge.c"]
D --> G["Emit bridge.v"]
Phase 0: Source parsing
For each input file:
- parse V source into AST
- extract extension metadata:
nameversiondescriptionini_entries
Phase 1: Type scan
First pass over AST statements:
- interfaces
- enums
- globals struct
- classes
Why a separate type scan exists:
- methods and static methods need the class index to already exist
Phase 2: Function scan
Second pass over AST statements:
- instance methods
- static methods
- global functions
- constants
- tasks
Phase 3: Link
After parsing is complete:
- resolve class shadow statics
- resolve class shadow constants
This step mutates class reprs to append derived PHP-visible properties/constants.
Export Pipeline
After compile() succeeds, generation is driven from export.v.
There are two main fragment collections:
- non-type fragments
- type fragments
These are merged into:
- declarations
- implementations
MINITlines- function table entries
ExportFragments is the common transport object for this stage.
Data Flow
The intended dependency direction is:
flowchart LR
A["repr"] --> B["parser"]
A --> C["linker"]
A --> D["builder"]
A --> E["compiler root"]
B --> E
C --> E
D --> E
Rules:
reprshould not depend onparser,linker, orbuilderparsershould not depend onbuilderbuildershould not depend onparser- root
compilercoordinates everything
Naming Conventions
Files
Prefer role-oriented file names:
export.vc_emitter.vv_glue.v
Submodules use domain names:
repr/parser/linker/builder/
Builder types
Prefer:
- file name is the domain
- type name is
*Builder - constructor is
new_xxx_builder(...)
Examples:
builder/class.v->ClassBuilderbuilder/function.v->FuncBuilderbuilder/constant.v->ConstantBuilder
Repr types
Repr types should stay explicit and stable:
PhpClassReprPhpFuncReprPhpEnumRepr
These are part of the compiler's internal language and should avoid clever renames.
Extension Guidance
When adding a new PHP-visible capability, use this checklist.
If the feature is new V syntax / annotation parsing
Change:
parser/- maybe
repr/
Examples:
- trait parsing
- interface property parsing
- new export annotations
If the feature is a post-parse relationship
Change:
linker/
Examples:
- explicit V
implementsto PHP interface linking - trait flattening/linking
- parent/child metadata reconciliation
If the feature is mostly export boilerplate
Change:
builder/- maybe
export.v
Examples:
- new constant registration style
- new class registration flags
- reusable arginfo/table fragments
If the feature needs concrete wrapper bodies
Change:
c_emitter.v- or
v_glue.v
Examples:
- new object/method wrapper shape
- special result/exception bridge logic
- task glue runtime behavior
Current Pain Points
These are known areas that can still improve:
export.vstill knows a fair amount about fragment groupingc_emitter.vstill contains large template-heavy logicv_glue.vstill mixes object glue, task glue, and function glue in one file- shadow-linking is isolated now, and future relationship passes may continue to grow
linker/
Recommended Near-Term Next Steps
- Add a short doc for
reprfield semantics - Split
v_glue.vlater by domain:- function glue
- class glue
- task glue
- Continue splitting linker responsibilities by relationship type when new passes are added
- Keep resisting the urge to move
export/c_emitter/v_glueinto agen/submodule until their boundaries are even more stable
Summary
The current architecture is:
reprfor dataparserfor AST parsinglinkerfor post-parse reconciliationbuilderfor reusable export fragmentsexportfor assemblyc_emitterfor concrete C wrapper emissionv_gluefor V bridge generation
That split keeps the compiler understandable while still leaving room for more advanced PHP features later.