Visual C++ 2017 AMD64-hez assembly

Visual C++ 2017 AMD64-hez assembly
2017-10-13T20:46:08+02:00
2017-10-14T21:13:52+02:00
2022-12-04T20:15:36+01:00
x00
Visual C++-ban sajnos nincs AMD64 inline assembler, külön .asm fájlban kell megírni és hozzálinkelni. C++-ban ugyanolyan nevű függvényből lehet különböző paraméterlistájú, lehet névtérben, osztály tagfüggvénye lehet. Assemblyben mit írjak a függvény nevének? Tehát C++-ban csak prototípus lesz, assemblyben lesz megírva a függvény. Ha meghívom, linkelési hiba, kiírja mi hiányzik: úgy lehet, de ennél hatékonyabban lehet-e?

A többi tudás elvileg megvan:

Ha jól értem, ha egy struct mérete 3, 5-7, 9... byte, veremben adja át, a méretét 8-cal oszthatóra felkerekítve. Return típusként is cím szerint: RCX-ben a címe bemeneti paraméter, ugyanazt a címet kell adni RAX-ban. Érdekes hogy __m128-at veremben ad át és nem xmm regiszterben, az is hogy cím szerint átadottak címe 16 byte-on illesztett: ha egy szöveg/tömb az aktuális paraméter, és nem olyan, lemásolja előtte? Szerintem ez csak a nagyméretű visszatérési típusra vonatkozik.

Ha megtartandó regisztert módosít a rutin (RBX, stb.), és azt mentem és visszaállítom, elvileg a PROC után USES nem kell.

Azt írták hogy ha a célregiszter 32 bites, a felső 32 bitje 0 lesz: például XCHG EAX, EAX. Ez pont a NOP: RAX felső 32 bitjét törölnie kellene eszerint. Úgyhogy RAX-ba -1-et töltve kipróbálom mi lesz: mov, add, xchg, pop.

Köszönöm.
Mutasd a teljes hozzászólást!
A fordítód dokumentációjából van esélyed kibányászni, hogy hogyan működik a name mangling (ez a hivatalos neve annak, hogy a függvények és metódusok átneveződnek fordítás közben), de szerintem nem érdemes szívni vele. Az ASM függvényeidet hívd C hívási konvencióval ('extern "C"' módosítóval). Bár elvben a C is átnevezi a függvényeket, de az algoritmusa jóval egyszerűbb, mondjuk egy aláhúzásjel kerül a függvény neve elé. Maga a hívási konvenció is egyszerűbb és szabványosabb, mert nem kell olyasmikkel foglalkozni, mint konstruktorok, destruktorok, this pointer vagy kivételek.

Ha mindenképp szükséged van overload-ra, akkor is szerintem maradj a C konvenciónál, és adj az ASM függvényeidnek egyedi belső neveket. A külvilág kedvéért meg írhatsz overload-olt inline függvényeket, amik ráhívnak a megfelelő ASM függvényre. Az inline miatt nem lesz extra indirekció a kész kódban (legalább is release bináris fordításakor), de a névfeloldás megmarad a fordító dolgának.
Mutasd a teljes hozzászólást!

  • Pontosan miből gondolod, hogy jó ötlet Assembly-ben beletoldani a C-programba?
    Mutasd a teljes hozzászólást!
  • C++-ba, nem C-be. Sok apró rövid rutin, például amiknél 2 eredmény lenne, ezért az intrinsic az egyiket memóriába írja, de olyan is van ahol csak 1 eredmény lenne mégis memóriába írja vagy maszk is kell neki. Például BSF, BSR, BT, BTC, BTR, BTS. Nekem elég hogy 0-ra nem használom a BSF-et, és csak a bitindexet mondja meg, azt ne hogy 0-e. BT regiszterre, nem memóriára. Tetszőlegesen nagy egészekhez unsigned __int64 vektor: ADC, SBB utasítások használatával +, -.
    Mutasd a teljes hozzászólást!
  • A fordítód dokumentációjából van esélyed kibányászni, hogy hogyan működik a name mangling (ez a hivatalos neve annak, hogy a függvények és metódusok átneveződnek fordítás közben), de szerintem nem érdemes szívni vele. Az ASM függvényeidet hívd C hívási konvencióval ('extern "C"' módosítóval). Bár elvben a C is átnevezi a függvényeket, de az algoritmusa jóval egyszerűbb, mondjuk egy aláhúzásjel kerül a függvény neve elé. Maga a hívási konvenció is egyszerűbb és szabványosabb, mert nem kell olyasmikkel foglalkozni, mint konstruktorok, destruktorok, this pointer vagy kivételek.

    Ha mindenképp szükséged van overload-ra, akkor is szerintem maradj a C konvenciónál, és adj az ASM függvényeidnek egyedi belső neveket. A külvilág kedvéért meg írhatsz overload-olt inline függvényeket, amik ráhívnak a megfelelő ASM függvényre. Az inline miatt nem lesz extra indirekció a kész kódban (legalább is release bináris fordításakor), de a névfeloldás megmarad a fordító dolgának.
    Mutasd a teljes hozzászólást!
  • Sok apró rövid rutin, például amiknél 2 eredmény lenne, ezért az intrinsic az egyiket memóriába írja, de olyan is van ahol csak 1 eredmény lenne mégis memóriába írja vagy maszk is kell neki.

    Azt kimérted, hogy a memóriába írás gondot jelent, vagy csak gondolod? Azért ha az adott memória a cache-ben van, nem olyan lassú azt elérni. Ha nem is olyan gyors, mint egy regiszter, a függvényhívás extra költsége még így is lehet, hogy nagyobb. A másik, hogy mivel a fordító pontosan tudja, mit csinál az az intrinsic függvény, lehet hogy csak magas szinten tűnik úgy, hogy memóriába ír, mert mire végez vele az optimalizáció, már regiszterbe kerül az adat.
    Mutasd a teljes hozzászólást!
  • *általános asm-ról lebeszélô hozzászólás* 
    Mutasd a teljes hozzászólást!
  • Azt javaslom, keress arra, hogy 'premature optimization'.
    Mutasd a teljes hozzászólást!
  • Tessék, ha a címedre keresek "-hez" nélkül, elsőlapos találat Google-ben: Introduction to writing x64 assembly in Visual Studio
    Mutasd a teljes hozzászólást!
  • Köszi, ez nagyon hasznos. Mivel a kérdés az volt hogy a függvény neve mi lesz, az meg nincs benne, más meg erre válaszolt, így az övét kellett elfogadnom megoldásnak. Egyébként hiányos a cikk: nem írja hogy vegyem fel a forrásfájlok közé a .asm fájlt, az extern "C"-t csak annál írja ha assemblyből akarok elérni C++-ban deklaráltat: ha fordítva, mint nálam, a példában nem írta.

    Pár éve nem találtam, és ahogy lenni szokott, miután feltettem a kérdést, eszembe jutott hogy hátha most megtalálom. Mivel 23 óra volt már, akkor már nem néztem meg hogy megtalálom-e, így hagytam a kérdést.

    Ha egyszer lenne saját programnyelvem, fastcall helyett idiotcall-nak nevezném benne. 8 regiszter van paraméterátadásnak, de csak 4 adható át benne akkor is ha 4 egész és 4 valós paramétere van. Vararg (...) nélkül is a hívó szabadítja fel a vermet, 32 byte-ot paramétermentesnél is lefoglalunk, RSP legyen 16 byte-on illesztett a call előtt ahelyett hogy az xmm-et használó rutin maga gondoskodna erről, ha egy struct 3 byte akkor a hívó 16 byte-on illesztett területet foglal neki például a veremben, és a címét adja át: ha 4 byte, vagyis hosszabb, akkor egész regiszterbe befér, 3 byte-os már nem. Nem írja hogy 1 byte-osnál mondjuk a felső 56 bit mindegy mi: ezek szerint 0-ra kell állítani regiszterben és veremben is.

    32 bites célregiszternél a felső 32 bit 0 lesz: kivéve XCHG EAX, EAX esetén ahol úgymarad: ez ugyanis a NOP. Tehát ha EAX, ECX is -1, az XCHG EAX, ECX más eredményt ad RAX-ban mint az XCHG EAX, EAX: attól függ az eredmény hogy melyik regiszterben van az érték (nem csak az értéktől). Kipróbáltam.

    Linkelési hibaüzenetnél megadja a prototípust és a dekorált nevet is: úgy is lehet hogy mindet meghívom, és extern "C"-vel is.

    Természetesen másoknak is köszönöm a választ.
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd