DEV Community

CrabPascal
CrabPascal

Posted on

Runtime Internals: Heap, VMT, and Built-ins | Internals do runtime

Bilingual post · Post bilíngue

Jump to: English · Português


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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

  1. tecnico-detalhado — module-by-module Rust tour
  2. Runtime section + Project Manager dual-mode
  3. 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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

  1. tecnico-detalhado — tour Rust módulo a módulo
  2. Seção Runtime + Project Manager dual-mode
  3. 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

Top comments (0)