feat(vm): interpreter loop, VM-CLI wiring, native dispatch, console - #7
Merged
Merged
Conversation
…ethods, frames) Wires up the actual execution loop on top of the scaffolding from the previous PR. New pieces, each with unit tests: - selector.rs: interns (name, arity) into a MethodNameIdx so Send only ever compares a u32, never a string, in the hot path (Wollok overloads by arity: do() and do(a) are different methods) - method.rs: MethodTable, compiled method bodies indexed by MethodRef - class.rs: ClassTable, a flat selector->method vtable per class, resolved once at class-definition time (never a hierarchy walk) - frame.rs: one method activation's locals + operand stack + ip - program.rs: splits out everything fixed once compiled (methods, classes, selectors, consts) from Vm (heap + inline caches, which DO change while running) — this is what lets run_method hold a method borrowed from `program` across recursive Send calls without fighting the borrow checker over &mut self on one do-everything struct - vm.rs: the actual instruction loop. Implements PushConst/Null/True/ False, Load/StoreLocal, Load/StoreField, Send (through the inline cache), Jump/JumpIfFalse, Return, NewInstance (fields start Null — constructors aren't wired on the parser side yet either). Everything else (NewArray/NewSet/NewClosure, try/catch, SendSuper) panics with todo!() naming itself rather than silently doing nothing. No compiler and no end-to-end test yet — next up.
…lots) A single-entry cache evicts on every call at a call-site that alternates between a couple of receiver classes, which is common enough in real code that it's worth the extra few bytes. Bumps InlineCache to a small fixed array (Hölzle/Chambers/Ungar, ECOOP '91 — real call-site polymorphism essentially never exceeds a handful of classes, so a linear scan over 4 slots beats a hashmap here), evicting round-robin only once all 4 are full. Same tiered design CPython 3.11's specializing adaptive interpreter (PEP 659) and Brunthaler's "Inline Caching Meets Quickening" (ECOOP '10) use, minus the opcode rewriting part of quickening — Program is deliberately immutable once compiled, so specialization happens in the cache data, not in the bytecode itself.
Pop was a real gap the first end-to-end test surfaced: a message send used as a statement (its result unwanted) had nowhere to put that result down, so the operand stack would just accumulate garbage across statements. tests/interpreter.rs hand-assembles bytecode (no compiler yet) and runs it through the real Vm, proving the whole chain works together, not just each module in isolation: - new object -> Send -> field read/write across two separate calls - the inline cache slot actually gets populated after a Send - the SAME call-site correctly dispatching to two different classes, round-tripping back to the first without losing it (the direct end-to-end proof that the polymorphic cache doesn't thrash) - Jump/JumpIfFalse branching both ways - sending an unimplemented selector panics with a clear message
Compiler (wollok-compiler) now runs through the VM end to end via the CLI. Send dispatches to native Rust methods for Int/Float/Bool/Str (never heap objects) through a registration table (native::NativeTable) that wollok-std populates instead of wollok-vm hardcoding them, so the standard library can grow on its own. Object receivers whose class doesn't define a selector fall back to a small native default bucket (PrimitiveKind::Object) for now just toString - not real inheritance, still tracked separately in docs/backlog.md item 4. Vm::send lets native code call back into user-defined methods dynamically. Program:: strings moved to Vm::strings (with real interning) since native methods that produce text (toString, concat) need a mutable place to put it at runtime.
A method can now be declared `native` (no body, like `abstract`), resolved at runtime by wollok-vm's native method table instead of compiled bytecode. Also fixes parse_object_body, which parsed plain method items only and silently rejected override/fallible/abstract (and now native) prefixes inside object bodies.
console is real Wollok source (wollok-std's console::SRC), merged into the user's Scope at compile time, not something wollok-compiler knows about by name. Its println method is declared `native` and resolved by class name (NativeTable::register_for_class), since its ClassId isn't known yet when the native gets registered. Accepts any number of arguments (Arity::Any) since Wollok selectors are arity-fixed by design and can't express variadics on their own. Also: CompileError::Unsupported now carries the actual unsupported AST node instead of a generic string, so failures are debuggable. Shared test helpers (assert_str, test_fn!) moved into a dedicated testing module, usable from every wollok-std submodule.
Missing # Panics/# Errors sections, possibly-truncating usize -> u32 casts without try_from, a non-single-variant wildcard match, an always-Ok Result, and two functions over clippy's line-count limit (interpreter loop and the compiler entry point, both allowed with a one-line reason: splitting either would hurt more than help).
…a const property A `property` field now gets a free getter (arity 0) and setter (`name=`, arity 1, matching what assignment syntax already compiles to) without writing either by hand. `const property` generates only the getter. An explicit `method name()`/`method name=(v)` still wins over the synthesized one, since it's inserted into the same vtable slot later in the same pass.
Four programs that actually compile and run end to end through the CLI, not pseudocode: a singleton with mutable state, a class with a guard clause, two instances of the same class messaging each other, and property/const property (getter/setter auto-generation, an explicit method overriding the synthesized getter).
The crate tree and processing flow still described the parser-only era, missing wollok-compiler/wollok-vm/wollok-std entirely and marking compilation/evaluation as "planned". Also adds a short section up top on what actually runs end to end now.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Segundo slice de la VM: el loop de ejecución de verdad, arriba del scaffold del PR anterior, y todo lo que hace falta para que un
.wlkcorra de punta a punta desde el CLI.Interpreter loop
Separé Program (lo compilado, fijo) de Vm (heap + caches, cambia en runtime) para no pelearme con el borrow checker al recursar en Send. Los selectores se internan a u32 antes de tiempo así Send compara enteros, no strings. El vtable de cada clase se resuelve una sola vez. Y subí la inline cache de monomórfica a polimórfica (4 casilleros) para que un call-site que alterna entre un par de clases no thrashee cada vez.
wollok-compiler
Conecta el AST (
wollok-ast) con el bytecode de la VM. Clases yobjects singleton con campos y métodos, constructores sintetizados por campo,new, sends (incluidos operadores), locals,if/return, literales. Sin arrays/sets/closures/try-catch/supertodavía (verdocs/backlog.md).Dispatch nativo + wollok-std
Sendya no asume que el receptor es un objeto de heap: los primitivos (Int/Float/Bool/Str) despachan contra una tabla nativa (NativeTable) que la crate nuevawollok-stdllena por registro, no un switch en el medio del intérprete. Un objeto cuya clase no define el selector cae a un bucket de defaults (toString) antes de fallar.console,nativemethodsconsolees Wollok fuente de verdad (compilado como cualquierobject), conprintlndeclaradonative(sin cuerpo) y resuelto por nombre de clase en runtime. Acepta cualquier cantidad de argumentos. De paso,nativese sumó como palabra clave del lenguaje (mismo patrón queabstract).Otros arreglos
Los strings dejaron de vivir en
Program(fijo tras compilar) y pasaron aVm(con interning real), porque untoString/concat necesita un lugar mutable donde poner el resultado. Los mensajes de error del compilador ahora incluyen el nodo real que falló, no un string genérico.162 tests en el workspace, clippy limpio, fmt limpio (rustfmt adoptado en este PR también).