
# Baidikoodi virtuaalmasin

Vaikimisi käib täitmine puupõhise interpretaatori kaudu — külastatakse iga AST-sõlme ja tagastatakse väärtus. Miljoneid kordi kutsutavate kuumade funktsioonide puhul on pudelikaelaks iga sõlme väljakutse lisakulu. Phasis lisab selle vähendamiseks spekulatiivse baidikoodist PHP sulgude kompilaatori.

## Millal virtuaalmasin käivitub

Igal `JsFunction` on kuumade kutsete loendur. Kui see ületab lävendi (vaikimisi 100), proovib mootor kompileerida funktsiooni keha `src/Bytecode/JsToPhp.php` kaudu PHP suluks.

Kompileerimine õnnestub, kui funktsiooni keha sobib teadaolevate kujude hulka:

- Lokaalmuutujate omistamised ennustatavate tüüpidega (arv, objekt, funktsioon, massiiv, string).
- Aritmeetika arvudega.
- Liikmete lugemine objektitüüpi lokaalmuutujatelt JA `this`-ilt (`this.v`, `obj.x`).
- Meetodite kutsumine objektitüüpi lokaalmuutujatel (`obj.method(args)`), kus vastuvõtja edastatakse kui `$thisValue`.
- `new Ctor(args)` konstruktorikutsed — muudavad LHS-lokaalmuutuja tüübi väärtuseks `object` ja lisavad loobumiskontrolli, kui tagastusväärtus pole objekt.
- `for` / `while` tsüklid lihtsate tingimustega.
- `return` arvutatava avaldisega.
- `if` / `else` tüübistatud tingimustel.

Kompileerimine ebaõnnestub (ja funktsioon jääb puupõhisesse interpretaatorisse), kui keha sisaldab:

- `with` lauseid.
- `try / catch / finally` (puupõhine interpretaator käsitleb seda lõpetamise semantika tagamiseks).
- `eval()` kutseid.
- Otsest `arguments` muutmist.
- Midagi, mida kompilaator veel madalamale tasemele teisendada ei oska.

Kui kompileerimine õnnestub, vahetab mootor funktsiooni kutsetee kompileeritud sulu vastu. Järgmised kutsed käivitavad natiivset PHP-i ühe funktsioonikutse lisakuluga, mitte iga AST-sõlme kohta eraldi.

## Loobumised

Kompileeritud kiirtee eeldab teatud tingimusi (arvud jäävad arvudeks, atribuudid jäävad samasse kujusse). Kui käitusajal mõni eeldus ei kehti, viskab sulg välja `Bailout` ning täitmine jätkub selle kutse puhul puupõhises interpretaatoris.

Siin on spetsifikatsioonile vastavus kõige olulisem. Puupõhine interpretaator peab saama loobunud kutse algusest peale uuesti käivitada, ilma et ebaõnnestunud kiirtee kõrvalmõjud oleks nähtavad. Phasis lahendab selle kahel viisil:

1. **Puhtad eesliited** — kompilaator otsib enne spekuleerimist kõrvalmõjusid. Kui funktsioon teeb enne spekuleerimispunkti sisend-väljundtoiminguid (console.log, host-funktsioonid), ei jäta kiirtee neid vahele, vaid viib need lõpuni.
2. **Kompileerimisest keeldumine** — kujude puhul, mis nõuaksid nähtavate kõrvalmõjude tagasipööramist (mittenumbriline `let r = fn()`, mille korral `fn()` muudab olekut), keeldub kompilaator kompileerimast. Funktsioon jääb puupõhisesse interpretaatorisse.

Näide, mis seda ajendas: mittenumbriline omistamine käivitab kiirteel `fn()`, viskab välja `Bailout` ja käivitub seejärel puupõhises interpretaatoris uuesti — `fn()` käivitatakse kaks korda ja mõlemal korral tekivad kõrvalmõjud. Kompileerimisest keeldumine hoiab ära `fn()` käivitamise kiirteel, nii et puupõhine interpretaator näeb ainult üht kutset.

## Virtuaalmasina väljakutse (teine kiht)

Osa funktsioone kompileeritakse PHP sulgudeks, teised kasutavad `src/Bytecode/VM.php`-is veelgi lihtsamat baidikoodi interpretaatorit. Baidikood on lame opkoodide massiiv (`LOAD_LOCAL`, `STORE_PROP`, `CALL_METHOD`, …), mida töötleb `switch` väljakutse.

Virtuaalmasin on mõeldud funktsioonidele, mida pole mõtet PHP suluks kompileerida, kuid mis on siiski piisavalt sageli käivitatavad, et AST läbimise vahelejätmisest kasu saada. Sisseehitatud objektide meetodikutseid (Array.prototype.map, String.prototype.split, …) töötlevad virtuaalmasina kiirteed, mis jätavad üldise `callFunction` vahenduskihi vahele.

## Kohandatud kutsepinu

`Op::CALL`, `Op::CALL_METHOD` ja `Op::NEW_CALL` kasutavad inline-kutse teed, mis hoiab kogu JS kutsepinu `VM::execute` sees — iga JS kutse jaoks ei looda uut PHP raami. Sisenemisel salvestatakse kutsuja olek (pc, koodivoog, operandipinu, lokaalid, env, `this`, range režiimi lipp) VM-ile omasesse `savedFrames` kogumisse, mis sisaldab `SavedFrame` eksemplare, ning väljakutsesilmus ühendab lokaalid kutsutava funktsiooniga. `Op::RET` eemaldab salvestatud raami ja taastab oleku. Erandid keritakse läbi salvestatud raamide enne puupõhise interpretaatori juurde naasmist.

Sobivus jäetakse meelde üksusel `JsFunction`: `vmInlineCallCache` tavaliste kutsete puhul (BC-kompileeritud keha, pole seotud `bind()`-iga ega natiivne), `vmInlineConstructCache` `new`-avaldiste puhul (lisaks `isConstructable`, pole tuletatud klass, pole eksemplariväljade ega privaatmeetodite algväärtustajaid). Kõik muu läbib endiselt kanoonilise `callFunction` / `vmNewExpression` vahenduskihi.

Arhitektuurne kõrvalkasu: PHP-i pinu ülempiir ei piira enam JS rekursiooni sügavust. Nüüd on ainsaks piiriks `CallStack::maxDepth` — väärtuseks on määratud **4 096**, mis ületab mugavalt kõigi õiguspäraste test262 juhtude vajadused (enamik vajab alla 2 k kaadri) ja on piisavalt väike, et patoloogilise lõpmatu rekursiooni testid ebaõnnestuksid käitaja ajalimiidi piires kiiresti. Transpileeritud sulgude tee (`JsToPhp`) kasutab mittesisese fallback-tee jaoks eraldi 8 192-kaadri piirangut.

## Kaadripildi generaatorid

Lihtsad generaatorikehad — puudub `yield*`, try/catch/finally sees puudub `yield` ning puudub `await` — kompileeritakse baidikoodiks. Kompilaator väljastab uue `Op::YIELD` opkoodi kohta, kus keha peatub; `Op::YIELD` jäädvustab väljakutse oleku (cf, pc+1, pinu, sp, lokaalid, env, thisValue, strict) `GeneratorSnapshot` kuhjaobjekti ja tagastab `VM::execute`-ist `YieldResult` kohatäitja. Käitav `JsGenerator` salvestab kaadripildi ja edastab selle järgmisel `next()` / `throw()` korral tagasi funktsioonile `VM::execute`, asendades operandipinu tipus oleva jätkamisväärtuse.

Toetatud alamhulga puhul asendab see PHP-Fiberi tee. Fiber-põhine käitaja (`executeGeneratorBody`) jääb kasutusse kujude jaoks, mille kompilaator tagasi lükkab — `yield*` delegeerimine, generaatorid, mis annavad väärtuse try-ploki sees, asünkroonsed generaatorid ning asünkroonsed funktsioonid koos `await`-iga. `JsGenerator` töötab kahes režiimis: iga `next()` / `throw()` / `return()` kontrollib, kas eksemplaril on kaadripilt või Fiber, ja suunab täitmise vastavalt.

Seadistusnuppu pole vaja; sobivus otsustatakse kompileerimise ajal ja jäetakse meelde üksusel `JsFunction`. Kaadripildi teel jääb iga yield'i kohta ära üks PHP kontekstivahetus; muu puhul jääb Fiberi tee samaks.

## Inline-vahemälud

`Op::LOAD_MEMBER`-il on iga PC kohta prototüübi identiteediga võtmetud inline-vahemälu, mida kasutatakse prototüübi meetodi kutsumisel eksemplaril (`c.sum(...)`, `arr.push(...)` ja iga klassimeetodi väljakutse). Vahemälu tabab, kui vastuvõtjal pole sellele nimele oma välja, vahemällu salvestatud prototüübiviide on sama mis vastuvõtja `getPrototype()` ning prototüübi `PropertyMap::$version` vastab endiselt jäädvustatud versioonile.

Vahemälu kehtetuks muutmine kasutab `PropertyMap::$version`-i. Struktuurimuudatused (seadmine / kustutamine) suurendavad seda tingimusteta. Andmevälja väärtuse ülekirjutamine suurendab seda ainult siis, kui `PropertyMap::$isUsedAsProto` on tõene — kirjutuspiirde lipp lülitatakse sisse esimest korda, kui objekti vaadeldakse teise objekti `[[Prototype]]`-ina. Eksemplariobjektid, mida ei kasutata kunagi prototüüpidena, ei kanna IC-taristu kulu iga `obj.x = v` puhul.

Getter-juurdepääsufunktsioone ei salvestata kunagi vahemällu (neid tuleb iga lugemise korral uuesti käivitada).

`PropertyMap`-il on teine loendur, `$mutationVersion`, mis suureneb *tingimusteta* iga kirjutamise korral (kirjutuspiiret pole). `JSON.stringify` vahemälu fikseerib selle loenduri iga külastatud pesastatud objekti/massiivi puhul, et ainult eksemplari muutmine, näiteks `o.a = 99`, muudaks vahemällu salvestatud stringi kehtetuks ka siis, kui IC `$version` oleks loenduri suurendamise vahele jätnud.

## JSON.stringify vahemälu

`JsObject`-il on `$jsonCacheString` + `$jsonCacheVersions` (loend `[obj, mutationVersion]` paaridest iga triviaalset stringify-käiku läbinud pesastatud objekti kohta). Järgmisel `JSON.stringify(o)` kutsel ilma asendaja ja tühikuta kontrollib triviaalne kiirtee, et kõigi fikseeritud versioonid on muutumatud, ning tagastab vahemällu salvestatud stringi ilma uuesti läbimise või kodeerimiseta. Kuumad süvakloonimise mustrid nagu `JSON.parse(JSON.stringify(o))` kiirendavad stringifitseerimise osa ligikaudu 2×.

Vahemälu tühjendatakse vaikselt möödalasu korral (kui fikseeritud versioon erineb) ja täidetakse uuesti järgmisel läbimisel. Objektid, millel on `toJSON`, getterid, juurdepääsufunktsioonid, sümbolid või `[[PrimitiveValue]]` väljad, suunatakse spetsifikatsioonikohasesse serialiseerijasse ega täida kunagi vahemälu — seega töötleb vahemällu salvestatud tee ainult ühtlase struktuuriga andmeid.

## Sisseehitatud kiirteed

Suurim jõudluse kasv saadi virtuaalmasinat konkreetsete sisseehitatud funktsioonide osas täiustades:

- **`Atomics.load` / `store` / `compareExchange`** koos `loadSpinHook` / `storeNotifyHook` koostööpõhise ajastamisega — taastati 4 Atomicsi pöördtsükli testi.
- **`Date.prototype.getTimezoneOffset`** otsese räsitabeli DST-vahemäluga — 45 % kiirem SpiderMonkey DST koormustestis.
- **`String.fromCharCode`** ASCII inline-kiirteega — säästab tüüpilise läbimise kohta ~1 M väljakutset.
- **`decodeURI` / `decodeURIComponent`** 12 märgi `%XX%XX%XX%XX` ülikiire teega, mis kasutab `hex2bin` massdekodeerimist.
- **`Object.keys` / `Object.values` / `Object.entries`** otsese atribuudikaardi juurdepääsuga.
- **`Array.prototype.map` / `filter` / `forEach` / `reduce`** aluseks oleva PHP massiivi natiivse iteratsiooniga.

Iga kiirtee algab „kujukontrolliga“ — kui vastuvõtja ei vasta eeldatud kujule (e.g, näiteks TypedArray vaade, mis ei põhine lihtsal PHP `string`-il), jätkab virtuaalmasin spetsifikatsioonikohase aeglase teega.

## Mida see ei tee

Baidikoodi virtuaalmasin **ei ole JIT** V8 tähenduses. See ei genereeri iga funktsiooni jaoks natiivset masinkoodi ega spekuleeri vaadeldud tüüpide põhjal kaugemale kui IC monomorfne prototüübi-identiteedi kontroll. Funktsioonipiiri sisestamist jäljendab kohandatud kutsepinu (iga JS kutse kohta pole lisanduvat PHP raami), mitte kutsutava funktsiooni keha teisendamine kutsuja baidikoodiks.

JsToPhp transpilaator on mootoris JIT-ile kõige lähemal: see genereerib kuumade funktsioonide kehade jaoks PHP lähtekoodi, mille PHP 8.5 jälgiv JIT seejärel natiivkoodiks kompileerib. JsToPhp kasutab `eval()`-i, mistõttu kaasneb ühekordne kompileerimiskulu ja jälgiv JIT peab funktsiooni avastama nagu kasutajakoodi. `eval()` sammu vahelejätmine otse opcache-opkoodide genereerimisega vähendaks vahet veelgi — vt [Võrdlustestid § Teekaart](https://phasis.inline0.dev/et/docs/advanced/benchmarks#roadmap).

## Võrdlustestid

`bench/microbench.js` käivitab kümmekond pisikest võrdlustesti ja veel mõned (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). Seinaaja mediaan:

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

Praegused näitajad salvestatakse faili `BENCH.md` pärast iga `bench` töövoo käivitamist. Nii `bench` kui ka test262 maatriksi töövood käivitatakse käsitsi.
