English {#english}
Runtime Internals: Heap, VMT, and Built-ins
Post 037 explained how real .pas units load. Once parsed, something must allocate objects, dispatch virtual methods, and execute WriteLn. That "something" is CompletePascalRuntime — detailed across tecnico-detalhado and arquitetura-design.
This post is a guided tour of the three subsystems every Delphi developer intuitively expects.
Object heap and identity
Pascal objects are reference-like on the heap. The runtime tracks instances in a map keyed by numeric ID:
struct ObjectInstanceData {
class_name: String,
fields: HashMap<String, Value>,
vtable_pointer: String,
}
// object_heap: HashMap<usize, ObjectInstanceData>
TObject.Create allocates the next ID, initializes fields from class metadata, and wires the VMT pointer. Free removes the entry — Delphi-style deterministic cleanup without a GC pause.
When debugging "object not found" errors, print the heap keys — often a double-free or use-after-free across anonymous Horse handlers.
Virtual Method Table (VMT)
Polymorphism requires late binding. Each class gets a VMT: ordered method slots. Inheritance copies parent slots; override replaces an entry; virtual adds one.
Dispatch algorithm (simplified):
call obj.MakeSound
→ lookup class VMT for MakeSound
→ if missing, walk parent VMT chain
→ invoke Rust closure or Pascal method body
The challenges doc records this as "Desafio Épico 2" — getting override + abstract + inherited right. Golden tests under examples/polimorfismo-test/ guard regressions.
Conceptually:
TDog = class(TAnimal)
procedure MakeSound; override;
end;
At runtime, a TDog instance's VMT slot for MakeSound points to the dog implementation even when typed as TAnimal.
Built-in dispatch layer
Not everything goes through VMT — scalars and RTL intrinsics short-circuit:
| Category | Examples | Implementation |
|---|---|---|
| I/O |
WriteLn, ReadLn
|
Direct stdout hooks |
| Math |
Sin, Sqrt
|
Rust libm wrappers |
| Strings |
Length, Copy, +
|
UTF-16 Delphi semantics (Sprint 19+) |
| SysUtils |
IntToStr, Format
|
Mix of intrinsics + rtl/sys/*.pas
|
The v2.0.1 shift moved library surface toward Pascal units; intrinsics remain for hot paths and types the parser must know natively.
Values and the interpreter loop
Internally, expressions evaluate to a Value enum — integers, floats, booleans, strings, object refs, arrays. The main interpret loop walks AST nodes:
Assignment → evaluate RHS → store in scope or object field
MethodCall → resolve receiver → VMT or static dispatch
BinaryOp → promote types → compute
Semantic analysis already proved types compatible; runtime trusts but verifies on edge cases (nil dereference → exception path).
Connection to codegen path
run uses this interpreter. build-exe emits C that calls stubs.c — parallel implementations of heap, VMT, and RTL helpers. Parity tests (tests/run_build_parity.rs, Sprint 9+) ensure both paths agree on stdout and basic OOP.
When they diverge, fix the contract in stubs first, then align runtime — not the other way around.
Reading order in Mintlify
- tecnico-detalhado — module-by-module Rust tour
- Runtime section + Project Manager dual-mode
- arquitetura-design — why heap+VMT beat pure AST walking
Pick one class (TProdutoService in CRUD) and trace Create → field init → method call in a debugger print — best 30-minute exercise for new runtime contributors.
Debugging tips
Enable runtime trace flags when investigating dispatch bugs:
set CRABPASCAL_TRACE_VMT=1
crab-pascal run examples/polimorfismo-test/polimorfismo.dpr
Compare output between run and build-exe on the same file — divergence usually means stubs.c drift, not Pascal source error. File issues with both stdout captures attached.
Next in series
Post 039 — Compiler Challenges and Solutions documents borrow-checker battles, PartialEq on 34 AST nodes, and the hard problems still on the backlog.
Português {#portugus}
Internals do runtime: heap, VMT e built-ins
O post 037 explicou como units .pas reais carregam. Depois do parse, algo precisa alocar objetos, despachar métodos virtuais e executar WriteLn. Esse algo é o CompletePascalRuntime — detalhado em tecnico-detalhado e arquitetura-design.
Este post é um tour guiado pelos três subsistemas que todo desenvolvedor Delphi espera instintivamente.
Heap de objetos e identidade
Objetos Pascal são referência-like no heap. O runtime rastreia instâncias em mapa keyed por ID numérico:
struct ObjectInstanceData {
class_name: String,
fields: HashMap<String, Value>,
vtable_pointer: String,
}
// object_heap: HashMap<usize, ObjectInstanceData>
TObject.Create aloca o próximo ID, inicializa campos a partir dos metadados da classe e liga o ponteiro VMT. Free remove a entrada — cleanup determinístico estilo Delphi sem pausa de GC.
Ao debugar "object not found", imprima as chaves do heap — muitas vezes double-free ou use-after-free em handlers Horse anônimos.
Virtual Method Table (VMT)
Polimorfismo exige late binding. Cada classe tem VMT: slots ordenados de métodos. Herança copia slots do pai; override substitui entrada; virtual adiciona.
Algoritmo de dispatch (simplificado):
call obj.MakeSound
→ busca MakeSound na VMT da classe
→ se faltar, sobe cadeia VMT do pai
→ invoca closure Rust ou corpo Pascal
O doc de desafios registra como "Desafio Épico 2" — acertar override + abstract + inherited. Golden tests em examples/polimorfismo-test/ guardam regressões.
Conceitualmente:
TDog = class(TAnimal)
procedure MakeSound; override;
end;
Em runtime, o slot MakeSound na VMT de TDog aponta para a implementação do cachorro mesmo tipado como TAnimal.
Camada de dispatch de built-ins
Nem tudo passa pela VMT — escalares e intrinsics RTL encurtam caminho:
| Categoria | Exemplos | Implementação |
|---|---|---|
| I/O |
WriteLn, ReadLn
|
Hooks diretos stdout |
| Math |
Sin, Sqrt
|
Wrappers libm Rust |
| Strings |
Length, Copy, +
|
Semântica UTF-16 Delphi (Sprint 19+) |
| SysUtils |
IntToStr, Format
|
Mix intrinsics + rtl/sys/*.pas
|
A virada v2.0.1 moveu superfície de biblioteca para units Pascal; intrinsics ficam para hot paths e tipos que o parser precisa conhecer nativamente.
Values e loop interpretador
Internamente, expressões viram enum Value — inteiros, floats, booleanos, strings, refs de objeto, arrays. O loop principal percorre nós AST:
Assignment → avalia RHS → guarda em escopo ou campo
MethodCall → resolve receiver → VMT ou dispatch estático
BinaryOp → promove tipos → calcula
A análise semântica já provou tipos compatíveis; runtime confia mas verifica casos limite (nil → caminho de exceção).
Conexão com codegen
run usa este interpretador. build-exe emite C que chama stubs.c — implementações paralelas de heap, VMT e helpers RTL. Testes de paridade (tests/run_build_parity.rs, Sprint 9+) garantem acordo em stdout e OOP básico.
Quando divergem, corrija o contrato em stubs primeiro, depois alinhe runtime — não o contrário.
Ordem de leitura no Mintlify
- tecnico-detalhado — tour Rust módulo a módulo
- Seção Runtime + Project Manager dual-mode
- arquitetura-design — por que heap+VMT venceu AST walk puro
Escolha uma classe (TProdutoService no CRUD) e trace Create → init de campo → call de método com prints — melhor exercício de 30 minutos para novos contribuidores de runtime.
Próximo da série
Post 039 — Desafios do compilador e soluções documenta batalhas com borrow checker, PartialEq em 34 nós AST e problemas difíceis ainda no backlog.
Published on dev.to/@crabpascal · Código em CrabPascal