MicroPythonin merkkijonojen sisäistäminen

MicroPython käyttää merkkijonojen sisäistämistä säästääkseen sekä RAM- että ROM-muistia. Tämä välttää saman merkkijonon kaksoiskappaleiden tallentamisen. Ensisijaisesti tämä koskee koodisi tunnisteita, sillä esimerkiksi funktion tai muuttujan nimi esiintyy hyvin todennäköisesti koodissa useassa paikassa. MicroPythonissa sisäistettyä merkkijonoa kutsutaan nimellä QSTR (uniQue STRing).

QSTR-arvo (jonka tyyppi on qstr) on indeksi QSTR-altaiden linkitettyyn listaan. QSTR:t tallentavat pituutensa ja sisältönsä tiivisteen nopeaa vertailua varten kaksoiskappaleiden poistoprosessin aikana. Kaikki merkkijonojen kanssa toimivat tavukoodioperaatiot käyttävät QSTR-argumenttia.

QSTR:ien generointi käännösaikana

MicroPythonin C-koodissa kaikki merkkijonot, jotka tulisi sisäistää lopulliseen laiteohjelmistoon, kirjoitetaan muodossa MP_QSTR_Foo. Käännösaikana tämä muuntuu qstr-arvoksi, joka osoittaa merkkijonon "Foo" indeksiin QSTR-altaassa.

Monivaiheinen prosessi Makefile-tiedostossa saa tämän toimimaan. Tiivistettynä tässä prosessissa on kolme osaa:

  1. Etsi kaikki MP_QSTR_Foo-tokenit koodista.

  2. Generoi staattinen QSTR-allas, joka sisältää kaiken merkkijonodatan (mukaan lukien pituudet ja tiivisteet).

  3. Korvaa kaikki MP_QSTR_Foo (esikäsittelijän avulla) niitä vastaavalla indeksillä.

MP_QSTR_Foo-tokeneita etsitään kahdesta lähteestä:

  1. Kaikki tiedostot, joihin viitataan muuttujassa $(SRC_QSTR). Tämä on kaikki C-koodi (eli py, extmod, ports/stm32), mutta ei kolmannen osapuolen koodia, kuten lib.

  2. Lisäksi $(QSTR_GLOBAL_DEPENDENCIES) (joka sisältää mpconfig*.h).

Huomaa: frozen_mpy.c (jonka mpy-tool.py generoi) sisältää oman QSTR-generointinsa ja -altaansa.

Jotkin lisämerkkijonot, joita ei voida ilmaista MP_QSTR_Foo-syntaksilla (esimerkiksi koska ne sisältävät ei-aakkosnumeerisia merkkejä), määritellään eksplisiittisesti tiedostoissa qstrdefs.h ja qstrdefsport.h muuttujan $(QSTR_DEFS) kautta.

Käsittely tapahtuu seuraavissa vaiheissa:

  1. qstr.i.last on tulos, joka saadaan ajamalla jokainen yksittäinen syötetiedosto C-esikäsittelijän läpi. Tämä tarkoittaa, että kaikki ehdollisesti pois kytketty koodi poistetaan ja makrot laajennetaan. Näin emme lisää altaaseen merkkijonoja, joita ei käytetä lopullisessa laiteohjelmistossa. Koska tässä vaiheessa (QSTR_GEN_CFLAGS-asetuksen lisäämän NO_QSTR-makron ansiosta) MP_QSTR_Foo:lla ei ole määritelmää, se kulkee tämän vaiheen läpi muuttumattomana. Tämä tiedosto sisältää myös esikäsittelijän kommentit, jotka sisältävät rivinumerotiedot. Huomaa, että tämä vaihe käyttää vain muuttuneita tiedostoja, mikä tarkoittaa, että qstr.i.last sisältää dataa vain niistä tiedostoista, jotka ovat muuttuneet edellisen käännöksen jälkeen.

  2. qstr.split on tyhjä tiedosto, joka luodaan komennon makeqstrdefs.py split ajamisen jälkeen tiedostolle qstr.i.last. Sitä käytetään vain riippuvuutena osoittamaan, että vaihe ajettiin. Tämä skripti tuottaa yhden tiedoston jokaista syöte-C-tiedostoa kohti, genhdr/qstr/...file.c.qstr, joka sisältää vain löydetyt QSTR:t. Jokainen QSTR tulostetaan muodossa Q(Foo). Tämä vaihe on välttämätön, jotta olemassa olevat tiedostot voidaan yhdistää inkrementaalisesta päivityksestä tiedostossa qstr.i.last syntyneeseen uuteen dataan.

  3. qstrdefs.collected.h on tulos, joka saadaan yhdistämällä genhdr/qstr/* komennolla makeqstrdefs.py cat. Tämä on nyt koko joukko koodista löydettyjä MP_QSTR_Foo:ja, nyt muotoiltuna muotoon Q(Foo), yksi per rivi, kaksoiskappaleiden kanssa. Tämä tiedosto päivitetään vain, jos qstr-joukko on muuttunut. QSTR-datan tiiviste kirjoitetaan toiseen tiedostoon (qstrdefs.collected.h.hash), mikä mahdollistaa muutosten seurannan käännösten välillä.

  4. Generoi luettelo (enum), jonka jokainen merkintä yhdistää MP_QSTR_Foo:n sitä vastaavaan indeksiin. Se yhdistää tiedoston qstrdefs.collected.h tiedostoihin qstrdefs*.h ja muuntaa sitten jokaisen rivin muodosta Q(Foo) muotoon "Q(Foo)", jotta ne kulkevat esikäsittelijän läpi muuttumattomina. Sen jälkeen esikäsittelijää käytetään käsittelemään mahdollinen ehdollinen kääntäminen tiedostoissa qstrdefs*.h. Sitten muunnos perutaan takaisin muotoon Q(Foo) ja tallennetaan tiedostona qstrdefs.preprocessed.h.

  5. qstrdefs.generated.h on komennon makeqstrdata.py tulos. Jokaiselle Q(Foo):lle tiedostossa qstrdefs.preprocessed.h (sekä joillekin ylimääräisille kovakoodatuille) se tulostaa QDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo").

Sitten pääkäännöksessä tiedostolle qstrdefs.generated.h tapahtuu kaksi asiaa:

  1. Tiedostossa qstr.h jokaisesta QDEF:stä tulee merkintä luettelossa (enum), mikä tekee MP_QSTR_Foo:sta koodin saatavilla olevan ja yhtä suuren kuin kyseisen merkkijonon indeksi QSTR-taulukossa.

  2. Tiedostossa qstr.c varsinainen QSTR-datataulukko generoidaan mp_qstr_const_pool->qstrs:n elementteinä.

QSTR:ien generointi ajon aikana

Lisää QSTR-altaita voidaan luoda ajon aikana, jotta niihin voidaan lisätä merkkijonoja. Esimerkiksi koodi:

foo[x] = 3

Joutuu luomaan QSTR:n arvolle x, jotta sitä voidaan käyttää ”load attr” -tavukoodissa.

Lisäksi Python-koodia käännettäessä tunnisteille ja literaaleille on luotava QSTR:t. Huomaa: vain alle 10 merkkiä pitkistä literaaleista tulee QSTR:iä. Tämä johtuu siitä, että tavallinen merkkijono keossa vie aina vähintään 16 tavua (yksi GC-lohko), kun taas QSTR:t mahdollistavat niiden tehokkaamman pakkaamisen altaaseen.

QSTR-altaat (ja niiden taustalla olevat ”lohkot”, jotka tallentavat merkkijonodatan) varataan tarpeen mukaan keosta vähimmäiskoolla.