Ο αρχικός ιδιωματισμός _ftol στη 32-bit Delphi μοιάζει με έξυπνο μονόγραμμο: μια συνάρτηση-περιτύλιγμα σε Pascal που βουτά σε inline assembly για να χειριστεί τη λέξη ελέγχου της x87 FPU, να αποκόψει την τιμή στη στοίβα της FPU και να βγάλει το αποτέλεσμα. Μεταγλωττιζόταν μια χαρά με τον DCC32 για πολύ καιρό, και ακριβώς γι αυτό κατέληξε σε τόσες παλιότερες μονάδες γραφικών και PDF χωρίς κανείς να το αμφισβητήσει
Αλλάξτε τον στόχο μεταγλώττισης σε 64-bit και ο μεταγλωττιστής τερματίζει με E1025 Unsupported language feature: 'ASM'. Αυτό το σφάλμα δεν είναι προειδοποίηση συμβατότητας. Σημαίνει ότι ο DCC64 δεν πρόκειται να μεταγλωττίσει καθόλου τη ρουτίνα, ανεξάρτητα από το πόσο καλά δούλευε η assembly πριν
Το 32-bit πρωτότυπο συνήθως έμοιαζε κάπως έτσι:
function _ftol(f: Double): Integer; cdecl;
begin
asm
lea eax, f
fstp qword ptr [eax]
end;
Result := Trunc(f);
end;
Αυτό το μπλοκ asm μέσα σε ένα σώμα begin...end της Pascal είναι ακριβώς αυτό που αρνείται ο DCC64. Οι δύο μεταγλωττιστές έχουν διαφορετικούς κανόνες για το πού επιτρέπεται η assembly, και το όριο έχει σημασία
Γιατί ο DCC64 τραβά τη γραμμή αλλού
Ο DCC32 επιτρέπει inline assembly μέσα σε συνηθισμένες ρουτίνες Pascal. Ο μεταγλωττιστής γνωρίζει τη σύμβαση κλήσης των 32 bit και μπορεί να συλλογιστεί πού βρίσκονται οι τοπικές μεταβλητές και οι παράμετροι, οπότε ανέχεται αποσπάσματα assembly που φτάνουν στο πλαίσιο στοίβας με το όνομα. Ο DCC64 παίρνει αυστηρότερη θέση: η assembly πρέπει να βρίσκεται σε αποκλειστική συνάρτηση assembler, μία όπου ολόκληρο το σώμα είναι assembly και η σύμβαση κλήσης αντιμετωπίζεται ρητά. Ο συνδυασμός Pascal συν asm δεν υποστηρίζεται καθόλου
Ο υποκείμενος λόγος είναι αρχιτεκτονικός. Στη σύμβαση κλήσης των 64-bit Windows (Microsoft ABI), οι πρώτες τέσσερις παράμετροι φτάνουν στους RCX, RDX, R8 και R9 για ακέραιους τύπους, ή στους XMM0 έως XMM3 για κινητή υποδιαστολή. Δεν εμπλέκεται καθόλου η x87 FPU στο κανονικό πέρασμα παραμέτρων· η x87 είναι τεχνικά διαθέσιμη αλλά το ABI δεν τη χρησιμοποιεί για τη μεταφορά ορισμάτων. Assembly που υποθέτει ότι μια τιμή βρίσκεται «στη στοίβα της FPU» συλλογίζεται πάνω σε μια κατάσταση που το 64-bit ABI δεν δημιουργεί ποτέ
Άρα το παλιό απόσπασμα δεν έχει απλώς πρόβλημα σύνταξης. Ακόμη κι αν ο DCC64 το δεχόταν, οι υποθέσεις για τους καταχωρητές θα ήταν λανθασμένες
Γράφοντας μια σωστή 64-bit έκδοση σε assembler
Όταν πράγματι χρειάζεστε να εξάγετε ένα σύμβολο _ftol με σύμβαση cdecl για δυαδική συμβατότητα, η συνάρτηση πρέπει να γραφτεί ως καθαρή ρουτίνα assembler. Υπό το 64-bit ABI μια παράμετρος Double φτάνει στον XMM0, και το ακέραιο αποτέλεσμα πρέπει να βρίσκεται στον RAX κατά την επιστροφή. Η οδηγία .NOFRAME λέει στον DCC64 ότι η ρουτίνα διαχειρίζεται τη δική της στοίβα, κάτι κατάλληλο για μια τόσο σύντομη φυλλική συνάρτηση:
function _ftol: Integer; cdecl;
// τιμή Double αναμένεται στον XMM0 σύμφωνα με το 64-bit ABI
asm
.NOFRAME
cvttsd2si rax, xmm0 // αποκοπή σε ακέραιο, αποτέλεσμα στον rax
end;
Η CVTTSD2SI είναι η εντολή SSE2 για τη μετατροπή ενός αριθμού κινητής υποδιαστολής διπλής ακρίβειας σε προσημασμένο ακέραιο με αποκοπή προς το μηδέν, που είναι ακριβώς αυτό που υποτίθεται ότι κάνει το _ftol. Είναι μία εντολή, παίρνει την παράμετρο κατευθείαν από εκεί που την άφησε το ABI, και τοποθετεί το αποτέλεσμα εκεί που το περιμένει το ABI. Δεν χρειάζεται κανένα ταχυδακτυλουργικό με τη λέξη ελέγχου της FPU
Σημειώστε ότι αν η είσοδος ξεπερνά το εύρος ενός προσημασμένου ακεραίου 32 bit, η CVTTSD2SI επιστρέφει την ακέραια απροσδιόριστη τιμή ($80000000). Αυτή είναι η ίδια συμπεριφορά με την fistp της x87 σε είσοδο εκτός εύρους. Το αν οι καλούντες σας μπορούν να παραγάγουν τέτοιες τιμές αξίζει να επιβεβαιωθεί πριν κηρύξετε τη μετάβαση ολοκληρωμένη
Πότε το Trunc είναι η καλύτερη απάντηση
Η παραπάνω έκδοση σε assembler αξίζει να γραφτεί μόνο όταν έχετε πραγματική απαίτηση δυαδικής συμβατότητας: κάποιος εξωτερικός καλών περιμένει το σύμβολο _ftol με συγκεκριμένη σύμβαση κλήσης, και δεν μπορείτε να αλλάξετε αυτούς τους καλούντες. Αυτή η κατάσταση είναι ασυνήθιστη. Τις περισσότερες φορές, το _ftol ήταν ένας ιδιωτικός βοηθός που χρησιμοποιούνταν μόνο μέσα στην ίδια μονάδα, και δεν υπάρχει καμία εξωτερική εξάρτηση από το όνομα ή τη σύμβασή του
Για αυτή την περίπτωση, αντικαταστήστε το με απλή Pascal:
function _ftol(f: Double): Integer; cdecl;
begin
Result := Trunc(f);
end;
Η Trunc αποκόπτει προς το μηδέν, κάτι που ταιριάζει με αυτό που έκανε το _ftol με τη λέξη ελέγχου της x87 ρυθμισμένη σε λειτουργία αποκοπής. Μεταγλωττίζεται σε DCC32 και DCC64 χωρίς τροποποίηση. Ο μεταγλωττιστής παράγει την κατάλληλη εντολή για κάθε στόχο: στην x64 συνήθως θα εκπέμψει ούτως ή άλλως CVTTSD2SI, την ίδια εντολή με τη χειρόγραφη έκδοση. Παίρνετε πανομοιότυπη συμπεριφορά, χωρίς συνθήκες πλατφόρμας και χωρίς assembly προς συντήρηση
Η μία σημασιολογική διαφορά που αξίζει να ελέγξετε: η Trunc εγείρει εξαίρεση EInvalidOp στην προεπιλεγμένη διαμόρφωση της Delphi όταν η είσοδος είναι NaN ή άπειρο. Η fistp της x87 στον αρχικό κώδικα απλώς έγραφε ένα μοτίβο bit χωρίς να εγείρει τίποτα. Αν ο κώδικάς σας τροφοδοτεί ασυνήθιστες τιμές κινητής υποδιαστολής σε αυτή τη συνάρτηση και η παλιά συμπεριφορά ήταν σιωπηλή, προστατευθείτε με IsNaN και IsInfinite από τη Math πριν καλέσετε την Trunc
Υπό συνθήκη μεταγλώττιση όταν παραμένουν ενεργοί και οι δύο στόχοι
Ορισμένα έργα πρέπει να συνεχίσουν να διανέμουν και 32-bit και 64-bit δυαδικά. Αν η αρχική έκδοση σε assembler πρέπει να διατηρηθεί για τα 32 bit και να δοθεί νέα υλοποίηση για τα 64 bit, χρησιμοποιήστε τη συνθήκη CPUX64:
function _ftol(f: Double): Integer; cdecl;
begin
{$IFDEF CPUX64}
Result := Trunc(f);
{$ELSE}
// διαδρομή 32 bit: ο DCC32 δέχεται inline asm
asm
lea eax, f
fstp qword ptr [eax]
end;
Result := Trunc(f);
{$ENDIF}
end;
Αυτή είναι η ελάχιστη μηχανική διόρθωση, και αξίζει να την αντιμετωπίσετε ως προσωρινή. Μια βάση κώδικα που κουβαλά assembly ειδική ανά αρχιτεκτονική μέσα σε έναν βοηθό του οποίου ο μοναδικός σκοπός είναι η αποκοπή από κινητή υποδιαστολή σε ακέραιο κουβαλά περιττό χρέος. Ο κλάδος των 32 bit μπορεί να φύγει εντελώς μόλις επιβεβαιώσετε ότι τίποτα δεν εξαρτάται από τις παρενέργειες της παλιάς υλοποίησης στην FPU
Αν η συνάρτηση εμφανίζεται σε ένα στοιχείο που χρησιμοποιείται σε πολλές μονάδες, ψάξτε ολόκληρη τη βάση κώδικα για _ftol πριν αποφασίσετε πώς θα κάνετε τη μετάβαση. Ένα σύμβολο με αυτό το όνομα μπορεί να δηλώνεται σε περισσότερα από ένα σημεία· ο linker διαλέγει ένα και αγνοεί σιωπηλά τα υπόλοιπα, πράγμα που σημαίνει ότι μπορεί να διορθώσετε το ένα αντίγραφο και να συνδέεστε ακόμη με ένα άλλο που δεν το αγγίξατε