
# VM bytecode

Η προεπιλεγμένη διαδρομή εκτέλεσης είναι ένας περιπατητής δέντρου — επισκέπτεται κάθε κόμβο AST και επιστρέφει μια τιμή. Για συναρτήσεις που καλούνται εκατομμύρια φορές, το κόστος αποστολής ανά κόμβο γίνεται σημείο συμφόρησης. Το Phasis προσθέτει από πάνω έναν εικαστικό μεταγλωττιστή bytecode σε closure PHP για να μειώσει αυτό το κόστος.

## Πότε ενεργοποιείται η VM

Κάθε `JsFunction` διαθέτει μετρητή συχνών κλήσεων. Όταν ξεπεράσει ένα όριο (προεπιλογή 100), η μηχανή επιχειρεί να μεταγλωττίσει το σώμα της συνάρτησης σε closure PHP μέσω του `src/Bytecode/JsToPhp.php`.

Η μεταγλώττιση επιτυγχάνει όταν το σώμα της συνάρτησης ταιριάζει σε ένα γνωστό σύνολο σχημάτων:

- Αναθέσεις τοπικών μεταβλητών με προβλέψιμους τύπους (αριθμητικός, object, συνάρτηση, πίνακας, συμβολοσειρά).
- Αριθμητική με αριθμούς.
- Ανάγνωση μελών από τοπικές μεταβλητές τύπου object ΚΑΙ από `this` (`this.v`, `obj.x`).
- Κλήσεις μεθόδων σε τοπικές μεταβλητές τύπου object (`obj.method(args)`), με προώθηση του δέκτη ως `$thisValue`.
- Κλήσεις κατασκευαστή `new Ctor(args)` — αναβαθμίζει τον τύπο της τοπικής μεταβλητής LHS σε `object` και προστατεύει με Bailout την περίπτωση μη-object επιστροφής.
- Βρόχοι `for` / `while` με απλά σχήματα συνθηκών.
- `return` με υπολογίσιμη έκφραση.
- `if` / `else` πάνω σε τυποποιημένες συνθήκες.

Η μεταγλώττιση αποτυγχάνει (και η συνάρτηση παραμένει στον περιπατητή δέντρου) όταν το σώμα περιέχει:

- Δηλώσεις `with`.
- `try / catch / finally` (το χειρίζεται ο περιπατητής δέντρου για σωστή σημασιολογία ολοκλήρωσης).
- Κλήσεις `eval()`.
- Άμεση μετάλλαξη `arguments`.
- Οτιδήποτε δεν ξέρει ακόμη να μετατρέπει ο μεταγλωττιστής.

Όταν η μεταγλώττιση πετύχει, η μηχανή αλλάζει τη διαδρομή κλήσης της συνάρτησης στο μεταγλωττισμένο closure. Οι επόμενες κλήσεις εκτελούν εγγενές PHP με το κόστος μίας κλήσης συνάρτησης, αντί για μία ανά κόμβο AST.

## Διακοπές

Η γρήγορη διαδρομή μεταγλώττισης βασίζεται σε παραδοχές (οι αριθμοί παραμένουν αριθμοί, οι ιδιότητες παραμένουν στο ίδιο σχήμα). Αν κάποια παραδοχή παραβιαστεί κατά την εκτέλεση, το closure ρίχνει ένα `Bailout` και η εκτέλεση ξαναρχίζει στον περιπατητή δέντρου για εκείνη την κλήση.

Εδώ έχει τη μεγαλύτερη σημασία η ορθότητα της προδιαγραφής. Ο περιπατητής δέντρου πρέπει να μπορεί να επανεκτελέσει από την αρχή την κλήση που διακόπηκε, χωρίς να παρατηρήσει παρενέργειες από την αποτυχημένη γρήγορη διαδρομή. Το Phasis το διαχειρίζεται με δύο τρόπους:

1. **Καθαρά προθέματα** — ο μεταγλωττιστής ελέγχει για παρενέργειες πριν από κάθε εικασία. Αν η συνάρτηση εκτελεί I/O (console.log, συναρτήσεις host) πριν από το σημείο εικασίας, η γρήγορη διαδρομή δεν το παραλείπει· δεσμεύει τις αλλαγές και συνεχίζει.
2. **Άρνηση κατά τη μεταγλώττιση** — για σχήματα που θα απαιτούσαν αναίρεση παρατηρήσιμων παρενεργειών (μη αριθμητικό `let r = fn()` όπου το `fn()` μεταβάλλει την κατάσταση), ο μεταγλωττιστής αρνείται να μεταγλωττίσει. Η συνάρτηση παραμένει στον περιπατητή δέντρου.

Το χαρακτηριστικό παράδειγμα: μια μη αριθμητική ανάθεση εκτελεί το `fn()` στη γρήγορη διαδρομή, ρίχνει το `Bailout` και μετά εκτελείται ξανά στον περιπατητή δέντρου — εκτελώντας το `fn()` δύο φορές, με δύο σύνολα παρενεργειών. Η άρνηση κατά τη μεταγλώττιση αποτρέπει την εκτέλεση του `fn()` στη γρήγορη διαδρομή, ώστε ο περιπατητής δέντρου να παρατηρήσει τη μοναδική κλήση.

## Αποστολή VM (το δεύτερο επίπεδο)

Ορισμένες συναρτήσεις μεταγλωττίζονται σε closures PHP· άλλες περνούν σε έναν ακόμη απλούστερο διερμηνευτή bytecode στο `src/Bytecode/VM.php`. Το bytecode είναι ένας επίπεδος πίνακας opcodes (`LOAD_LOCAL`, `STORE_PROP`, `CALL_METHOD`, …) που καταναλώνεται από αποστολή `switch`.

Η VM προορίζεται για συναρτήσεις που δεν αξίζει να μεταγλωττιστούν σε closure PHP, αλλά είναι αρκετά συχνές ώστε να ωφελούνται από την παράλειψη της διάσχισης AST. Οι κλήσεις μεθόδων σε ενσωματωμένα αντικείμενα (Array.prototype.map, String.prototype.split, …) ακολουθούν γρήγορες διαδρομές στη VM που παρακάμπτουν το γενικό trampoline `callFunction`.

## Προσαρμοσμένη στοίβα κλήσεων

Τα `Op::CALL`, `Op::CALL_METHOD` και `Op::NEW_CALL` χρησιμοποιούν διαδρομή inline κλήσεων που διατηρεί ολόκληρη τη στοίβα κλήσεων JS μέσα στο `VM::execute` — χωρίς νέο πλαίσιο PHP για κάθε κλήση JS. Κατά την είσοδο, η κατάσταση του καλούντος (pc, ροή κώδικα, στοίβα τελεστέων, τοπικές μεταβλητές, env, `this`, σημαία strict mode) αποθηκεύεται σε μια δεξαμενή `savedFrames` ανά VM με στιγμιότυπα `SavedFrame`, και ο βρόχος αποστολής ανασυνδέει τις τοπικές μεταβλητές του με τις μεταβλητές του καλούμενου. Το `Op::RET` αφαιρεί το αποθηκευμένο πλαίσιο και επαναφέρει την κατάσταση. Οι εξαιρέσεις ξετυλίγονται διασχίζοντας τα αποθηκευμένα πλαίσια πριν επιστρέψουν στον περιπατητή δέντρου.

Η επιλεξιμότητα αποθηκεύεται προσωρινά στο `JsFunction`: `vmInlineCallCache` για κανονικές κλήσεις (σώμα μεταγλωττισμένο σε BC, χωρίς δέσμευση σε `bind()`, όχι εγγενές), `vmInlineConstructCache` για εκφράσεις `new` (επιπλέον `isConstructable`, όχι derived, χωρίς αρχικοποιητές πεδίων instance ή ιδιωτικών μεθόδων). Ό,τι άλλο εξακολουθεί να περνά από το κανονικό trampoline `callFunction` / `vmNewExpression`.

Το αρχιτεκτονικό πρόσθετο όφελος: το όριο στοίβας PHP δεν περιορίζει πλέον το βάθος αναδρομής JS. Μόνο το `CallStack::maxDepth` θέτει πλέον όριο — στα **4 096**, αρκετά πάνω από κάθε θεμιτή περίπτωση test262 (οι περισσότερες χρειάζονται κάτω από 2 k πλαίσια) και αρκετά χαμηλά ώστε οι παθολογικές δοκιμές άπειρης αναδρομής να αποτυγχάνουν γρήγορα, εντός του ορίου χρόνου του runner. Η διαδρομή closure που παράγεται από transpiler (`JsToPhp`) έχει δικό της όριο 8 πλαισίων 192 για την εναλλακτική διαδρομή χωρίς inline κλήσεις.

## Generators με στιγμιότυπο πλαισίου

Απλά σώματα generator — χωρίς `yield*`, χωρίς `yield` μέσα σε try/catch/finally και χωρίς `await` — μεταγλωττίζονται σε bytecode. Ο μεταγλωττιστής εκπέμπει νέο opcode `Op::YIELD` στο σημείο αναστολής του σώματος· το `Op::YIELD` καταγράφει την κατάσταση αποστολής (cf, pc+1, stack, sp, locals, env, thisValue, strict) σε αντικείμενο σωρού `GeneratorSnapshot` και επιστρέφει ένα sentinel `YieldResult` από το `VM::execute`. Το `JsGenerator` που οδηγεί την εκτέλεση αποθηκεύει το στιγμιότυπο και το επιστρέφει στο `VM::execute` στο επόμενο `next()` / `throw()`, αντικαθιστώντας την τιμή συνέχισης στην κορυφή της στοίβας τελεστέων.

Αυτό αντικαθιστά τη διαδρομή PHP-Fiber για το υποστηριζόμενο υποσύνολο. Ο οδηγός που βασίζεται σε Fiber (`executeGeneratorBody`) παραμένει για σχήματα που απορρίπτει ο μεταγλωττιστής — ανάθεση `yield*`, generators που κάνουν yield μέσα σε try-block, async generators και async συναρτήσεις με `await`. Το `JsGenerator` λειτουργεί σε δύο τρόπους: κάθε `next()` / `throw()` / `return()` ελέγχει αν το στιγμιότυπο διαθέτει snapshot ή Fiber και αποστέλλει ανάλογα.

Δεν υπάρχει ρύθμιση για βελτιστοποίηση επιδόσεων· η επιλεξιμότητα αποφασίζεται κατά τη μεταγλώττιση και αποθηκεύεται προσωρινά στο `JsFunction`. Το όφελος είναι μία λιγότερη εναλλαγή περιβάλλοντος PHP ανά yield στη διαδρομή snapshot· η διαδρομή Fiber παραμένει ίδια για όλα τα υπόλοιπα.

## Ενσωματωμένες κρυφές μνήμες

Το `Op::LOAD_MEMBER` διατηρεί inline cache ανά PC, με κλειδί την ταυτότητα του prototype, για το μοτίβο μεθόδου prototype σε instance (`c.sum(...)`, `arr.push(...)` και κάθε αποστολή μεθόδου κλάσης). Η cache θεωρείται έγκυρη όταν ο δέκτης δεν έχει δική του θέση για το όνομα, η αποθηκευμένη αναφορά prototype ταυτίζεται με το `getPrototype()` του δέκτη και το `PropertyMap::$version` του prototype εξακολουθεί να αντιστοιχεί στην καταγεγραμμένη έκδοση.

Η ακύρωση της cache βασίζεται στο `PropertyMap::$version`. Οι δομικές μεταλλάξεις (set / delete) το αυξάνουν πάντα. Οι αντικαταστάσεις τιμών σε θέσεις δεδομένων το αυξάνουν μόνο όταν το `PropertyMap::$isUsedAsProto` είναι true — μια σημαία write-barrier που ενεργοποιείται την πρώτη φορά που το αντικείμενο εντοπίζεται ως `[[Prototype]]` ενός άλλου αντικειμένου. Τα αντικείμενα instance που δεν χρησιμοποιούνται ποτέ ως prototype δεν πληρώνουν το κόστος της υποδομής IC σε κάθε `obj.x = v`.

Οι προσπελάσεις getter δεν αποθηκεύονται ποτέ στην cache (πρέπει να εκτελούνται ξανά σε κάθε ανάγνωση).

Το `PropertyMap` διατηρεί δεύτερο μετρητή, το `$mutationVersion`, που αυξάνεται *πάντα* σε κάθε εγγραφή (χωρίς write-barrier). Η cache `JSON.stringify` δεσμεύει αυτόν τον μετρητή σε κάθε εμφωλευμένο αντικείμενο/πίνακα που επισκέπτεται, ώστε μια μετάλλαξη μόνο στο instance, όπως το `o.a = 99`, να ακυρώνει σωστά τη συμβολοσειρά της cache, ακόμη κι όταν το `$version` της IC θα παρέλειπε την αύξηση.

## Cache JSON.stringify

Το `JsObject` διατηρεί τα `$jsonCacheString` + `$jsonCacheVersions` (μια λίστα από ζεύγη `[obj, mutationVersion]` για κάθε εμφωλευμένο αντικείμενο που επισκέφτηκε η απλή διαδρομή stringify). Στην επόμενη κλήση `JSON.stringify(o)` χωρίς replacer / space, η απλή γρήγορη διαδρομή επαληθεύει ότι καμία καταγεγραμμένη έκδοση δεν έχει αλλάξει και επιστρέφει τη συμβολοσειρά της cache χωρίς νέα διάσχιση ή κωδικοποίηση. Δημοφιλή μοτίβα βαθιάς κλωνοποίησης, όπως το `JSON.parse(JSON.stringify(o))`, επιταχύνονται περίπου κατά 2× στο σκέλος stringify.

Η cache απορρίπτεται σιωπηρά αν αποτύχει ο έλεγχος (διαφέρει οποιαδήποτε καταγεγραμμένη έκδοση) και συμπληρώνεται ξανά στην επόμενη διάσχιση. Αντικείμενα με `toJSON`, getters, accessors, symbols ή θέσεις `[[PrimitiveValue]]` περνούν στην υλοποίηση σειριοποίησης της προδιαγραφής και δεν γεμίζουν ποτέ την cache — έτσι η διαδρομή cache βλέπει μόνο δομικά ομοιόμορφα δεδομένα.

## Γρήγορες διαδρομές ενσωματωμένων

Τα μεγαλύτερα κέρδη επιδόσεων προέκυψαν από την προσθήκη ειδικών ενσωματωμένων λειτουργιών στη VM:

- **`Atomics.load` / `store` / `compareExchange`** με συνεργατικό προγραμματισμό `loadSpinHook` / `storeNotifyHook` — ανακτήθηκαν 4 δοκιμές βρόχου αναμονής Atomics.
- **`Date.prototype.getTimezoneOffset`** με cache DST απευθείας σε hashmap — 45 % ταχύτερο στις δοκιμές καταπόνησης DST του SpiderMonkey.
- **`String.fromCharCode`** με inline γρήγορη διαδρομή ASCII — εξοικονομούνται ~1 M αποστολές ανά τυπική σάρωση.
- **`decodeURI` / `decodeURIComponent`** με υπερταχεία διαδρομή `%XX%XX%XX%XX` 12 χαρακτήρων που χρησιμοποιεί μαζική αποκωδικοποίηση `hex2bin`.
- **`Object.keys` / `Object.values` / `Object.entries`** με άμεση πρόσβαση στον χάρτη ιδιοτήτων.
- **`Array.prototype.map` / `filter` / `forEach` / `reduce`** με εγγενή επανάληψη στον υποκείμενο πίνακα PHP.

Κάθε γρήγορη διαδρομή ελέγχεται μέσω «ελέγχου σχήματος» — αν ο δέκτης δεν έχει το αναμενόμενο σχήμα (e.g. μια προβολή TypedArray που δεν βασίζεται σε απλό PHP `string`), η VM περνά στην αργή διαδρομή που συμμορφώνεται με την προδιαγραφή.

## Τι δεν κάνει αυτό

Η VM bytecode **δεν είναι JIT** με την έννοια του V8. Δεν παράγει εγγενή κώδικα μηχανής ανά συνάρτηση και δεν κάνει εικασίες βάσει παρατηρούμενων τύπων, πέρα από τον έλεγχο μονομορφικής ταυτότητας prototype της IC. Η ενσωμάτωση στα όρια συναρτήσεων προσεγγίζεται με την προσαρμοσμένη στοίβα κλήσεων (χωρίς επιπλέον πλαίσιο PHP ανά κλήση JS), αντί με μετατροπή του σώματος της καλούμενης συνάρτησης σε bytecode του καλούντος.

Ο transpiler JsToPhp είναι το πιο κοντινό πράγμα σε JIT που διαθέτει η μηχανή: παράγει πηγαίο κώδικα PHP για σώματα συχνών συναρτήσεων, τον οποίο το tracing JIT της έκδοσης 8.5 του PHP μεταγλωττίζει έπειτα σε εγγενή κώδικα. Το JsToPhp περνά από το `eval()`, οπότε επιβαρύνεται με εφάπαξ κόστος μεταγλώττισης και το tracing JIT πρέπει να εντοπίσει τη συνάρτηση σαν να ήταν κώδικας χρήστη. Η παράλειψη του βήματος `eval()` με απευθείας έκδοση opcodes opcache θα μείωνε περισσότερο τη διαφορά — δείτε [Συγκριτικές μετρήσεις § Οδικός χάρτης](https://phasis.inline0.dev/el/docs/advanced/benchmarks#roadmap).

## Συγκριτικές μετρήσεις

Το `bench/microbench.js` εκτελεί δώδεκα και κάτι μικρές συγκριτικές δοκιμές (loop-arith, loop-fib, fn-recurse, fn-deep-recurse, obj-create, obj-prop, proto-method, gen-iterate, arr-push, arr-map, str-concat, str-split-join, json-roundtrip, closure, destructure). Διάμεσος χρόνος πραγματικού χρόνου:

```bash
php bench/run.php
```

Οι τρέχουσες μετρήσεις καταγράφονται στο `BENCH.md` μετά από κάθε εκτέλεση ροής εργασίας `bench`. Οι ροές εργασίας `bench` και του πίνακα test262 εκκινούνται χειροκίνητα.
