Διαδρομές
/
x86-64 Assembly
x86-64 Assembly
/
Ασκήσεις
/
Φύλλο βαθμολογίας
Φύλλο βαθμολογίας

Φύλλο βαθμολογίας

Άσκηση εκμάθησης

Εισαγωγή

SIMD: Μάσκες και Συνθήκες

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

Γι' αυτό ο κώδικας SIMD είναι χωρίς διακλαδώσεις από προεπιλογή.

Αντί να βασίζονται σε σημαίες, οι πακεταρισμένες συγκρίσεις συνήθως παράγουν μια μάσκα στον τελεστέο προορισμού. Για κάθε λωρίδα, η σύγκριση γεμίζει ολόκληρη τη λωρίδα με άσσους όταν είναι αληθής και με μηδενικά όταν είναι ψευδής. Αν διαβαστεί ως προσημασμένος ακέραιος, μια αληθής λωρίδα είναι -1 και μια ψευδής λωρίδα είναι 0.

Αυτή η μάσκα μπορεί στη συνέχεια να συνδυαστεί με πράξεις σε επίπεδο bit για να φιλτράρει συγκεκριμένες λωρίδες.

Πακεταρισμένες Συγκρίσεις

Μια κλιμακωτή cmp είναι γενική με την έννοια ότι χρησιμοποιείται για να θέσει διάφορες σημαίες ταυτόχρονα. Μια άλλη εντολή μπορεί στη συνέχεια να καταναλώσει αυτές τις σημαίες είτε για να κάνει διακλάδωση είτε για να εκτελέσει υπολογισμούς.

Ωστόσο, επειδή μια πακεταρισμένη σύγκριση και ελέγχει μια συνθήκη και υπολογίζει μια μάσκα, δεν είναι γενική. Η σύγκριση πρέπει να δεχτεί την ακριβή συνθήκη που ελέγχεται.

Υπάρχουν δύο τρόποι να γίνει αυτό:

  • Οι συγκρίσεις ακεραίων δέχονται τη συνθήκη ως κατάληξη: eq για ισότητα και gt για «μεγαλύτερο από». Οι άλλες παραλλαγές κατασκευάζονται συνδυάζοντας το αποτέλεσμα μιας από αυτές.
  • Οι συγκρίσεις κινητής υποδιαστολής δέχονται τη συνθήκη κωδικοποιημένη σε ένα άμεσο. Διαφορετικές τιμές αυτού του άμεσου αντιστοιχούν σε διαφορετικές συνθήκες που ελέγχονται.

Εκτός από τη χρήση μιας συγκεκριμένης κατάληξης συνθήκης στις συγκρίσεις ακεραίων, η σύνταξη ακολουθεί την ίδια δομή που έχουμε ήδη δει:

  • Για ακεραίους, p + cmp + συνθήκη + μέγεθος (b, w, d ή q).
  • Για αριθμούς κινητής υποδιαστολής, cmp + p + μέγεθος (s ή d). Η συνθήκη περνιέται σε ένα άμεσο ως πρόσθετος τελεστέος.
Συγκρίσεις Ακεραίων

Όπως αναφέρθηκε, υπάρχουν μόνο συγκρίσεις ακεραίων για ισότητα και για «μεγαλύτερο από»:

εντολή περιγραφή
pcmpeqb, pcmpeqw, pcmpeqd, pcmpeqq ισότητα ανά λωρίδα
pcmpgtb, pcmpgtw, pcmpgtd, pcmpgtq προσημασμένο «μεγαλύτερο από» ανά λωρίδα
movdqa  xmm0, [rel scores]
pcmpgtd xmm0, [rel threshold] ; lane i = 0xFFFFFFFF (-1) if scores[i] > threshold[i], else 0

Για να δημιουργήσεις μια σύγκριση «μικρότερο από», χρησιμοποίησε το gt με τους τελεστέους αντεστραμμένους: a < b == b > a.

Πρόσεξε ότι η σύγκριση είναι προσημασμένη. Για να κάνεις σύγκριση χωρίς πρόσημο, αντιστρέψε το πιο σημαντικό bit και των δύο τελεστέων. Αυτό μπορεί να γίνει με ένα XOR με μια μάσκα όπου μόνο το πιο σημαντικό bit έχει τεθεί.

Note

Δύο βολικά ιδιώματα είναι:

  1. Το να κάνεις XOR έναν καταχωρητή με τον εαυτό του για να παραγάγεις όλα μηδενικά.
  2. Το να συγκρίνεις έναν καταχωρητή με τον εαυτό του για να παραγάγεις όλα άσσους.

Για παράδειγμα:

pxor xmm4, xmm4    ; xmm4 = all zeros
pcmpeqd xmm7, xmm7 ; xmm7 = all ones

Τα «όλα μηδενικά» και τα «όλα άσσους» είναι συνηθισμένες μάσκες για να κωδικοποιήσουν το "ψευδές παντού" και το "αληθές παντού", αντίστοιχα. Μπορούν επίσης να χρησιμοποιηθούν για να αναπαραστήσουν πακεταρισμένο 0 ή πακεταρισμένο -1, που είναι συνηθισμένες τιμές φρουροί. Για παράδειγμα, το NUL που σηματοδοτεί το τέλος μιας συμβολοσειράς είναι ένα 0.

Συγκρίσεις Κινητής Υποδιαστολής

Οι λωρίδες κινητής υποδιαστολής χρησιμοποιούν διαφορετική μορφή: μία εντολή, την cmpps (και την cmppd για λωρίδες 64 bit), με τη συνθήκη ως άμεσο:

movaps xmm0, [rel readings]
cmpps  xmm0, [rel limits], 1 ; condition 1 is "less than": lane i = all ones if readings[i] < limits[i]

Ο NASM έχει επίσης ψευδοεντολές που αντιστοιχίζονται στο σωστό άμεσο και είναι πιο εύκολες στην απομνημόνευση. Σε όλα τα παρακάτω, το x στο px μπορεί να είναι s (αριθμοί κινητής υποδιαστολής 32 bit) ή d (αριθμοί κινητής υποδιαστολής 64 bit):

ψευδοεντολή άμεσο σύγκριση
cmpeqpx 0 a == b
cmpltpx 1 a < b
cmplepx 2 a <= b
cmpunordpx 3 το a είναι NaN ή το b είναι NaN
cmpneqpx 4 a != b
cmpnltpx 5 a >= b
cmpnlepx 6 a > b
cmpordpx 7 ούτε το a ούτε το b είναι NaN

Επιλογή με Μάσκα

Μια μάσκα κωδικοποιεί το αποτέλεσμα μιας συνθήκης. Μπορεί στη συνέχεια να χρησιμοποιηθεί για να επιλέξεις, λωρίδα προς λωρίδα, ανάμεσα σε δύο σύνολα τιμών σύμφωνα με αυτή τη συνθήκη. Παίρνουμε τη λωρίδα από τη μια τιμή όπου η μάσκα είναι αληθής και από την άλλη όπου είναι ψευδής:

; result = (a AND mask) OR (b AND NOT mask)
movdqa xmm2, xmm0  ; xmm0 holds the mask, keep a copy
pand   xmm2, xmm3  ; xmm2 = a AND mask: lanes of a where mask is true
pandn  xmm0, xmm4  ; xmm0 = NOT mask AND b: lanes of b where mask is false
por    xmm2, xmm0  ; combine the two halves

Πρόσεξε ότι η ασυμμετρία της pandn αποδίδει εδώ: η μάσκα βρίσκεται στον προορισμό, αρνείται και επιλέγει από το b με μία μόνο εντολή.

Αυτό το μοτίβο είναι η πακεταρισμένη μορφή της επιλογής χωρίς διακλαδώσεις. Κάθε λωρίδα υπολογίζεται και μόνο η μάσκα αποφασίζει ποια τιμή επιβιώνει, χωρίς κανένα jcc πουθενά.

Ειδικές Εντολές Blend

Υπάρχουν εντολές που εκτελούν ακριβώς αυτή την επιλογή απευθείας, διαβάζοντας ένα bit ανά στοιχείο από έναν καταχωρητή μάσκας. Ονομάζονται εντολές blend:

εντολή στοιχείο πηγή μάσκας
pblendvb byte υπονοούμενο xmm0
blendvps λωρίδα 32 bit υπονοούμενο xmm0
blendvpd λωρίδα 64 bit υπονοούμενο xmm0

Πρόσεξε ότι η πρώτη εντολή ακολουθεί τη σύνταξη των ακεραίων, ενώ οι άλλες δύο ακολουθούν τη σύνταξη των αριθμών κινητής υποδιαστολής. Ωστόσο, επειδή αυτές οι εντολές απλώς επιλέγουν ακατέργαστα byte, οποιαδήποτε από αυτές μπορεί να χρησιμοποιηθεί και με ακεραίους και με αριθμούς κινητής υποδιαστολής.

Για κάθε στοιχείο, η blend κρατάει τον προορισμό όταν το πιο σημαντικό bit του αντίστοιχου στοιχείου της μάσκας είναι μηδενισμένο, και παίρνει την πηγή όταν αυτό έχει τεθεί. Εξετάζεται μόνο αυτό το πιο σημαντικό bit, κάτι που μια μάσκα σύγκρισης ικανοποιεί, αφού οι λωρίδες της είναι όλες άσσους ή όλες μηδενικά. Ο καταχωρητής μάσκας είναι πάντα ο xmm0, ο οποίος είναι υπονοούμενος:

movaps   xmm0, [rel mask]  ; the selecting mask must be in xmm0
movaps   xmm1, [rel b]     ; destination: kept where the mask bit is clear
blendvps xmm1, [rel a]     ; source: taken where the mask bit is set

Είναι επίσης δυνατό να χρησιμοποιήσεις την pblendvb για να επιλέξεις λωρίδες από μια μάσκα σύγκρισης για οποιοδήποτε άλλο μέγεθος. Επειδή όλα τα byte σε μια αληθή λωρίδα είναι όλα άσσους, η pblendvb τα επιλέγει όλα.

Note

Όλες αυτές οι εντολές προσθέτουν ένα v μετά την πράξη που εκτελούν (blend). Αυτό το v σημαίνει μεταβλητό, επειδή η επιλογή δεν είναι στατική: εξαρτάται από έναν καταχωρητή.

Υπάρχουν επίσης παραλλαγές χωρίς το v, που επιλέγουν σύμφωνα με ένα άμεσο. Ακολουθούν το ίδιο μοτίβο, επιλέγοντας τη λωρίδα i αν το bit i του άμεσου έχει τεθεί.

Από τη Μάσκα Πίσω στον Κλιμακωτό Κόσμο

Παρόλο που είναι ισχυρός, ο κώδικας SIMD στερείται μεγάλο μέρος της ευελιξίας του κλιμακωτού κώδικα. Σε πολλές περιπτώσεις, είναι απαραίτητο να μετακινηθείς από έναν πακεταρισμένο καταχωρητή πίσω στον κόσμο των κλιμακωτών εντολών.

Η οικογένεια εντολών movmsk λειτουργεί ως γέφυρα ανάμεσα στους δύο κόσμους. Αυτές οι εντολές εξάγουν το πιο σημαντικό bit κάθε λωρίδας σε έναν καταχωρητή γενικού σκοπού:

εντολή συλλέγει πλάτος αποτελέσματος
pmovmskb το πιο σημαντικό bit καθενός από τα 16 byte 16 bit
movmskps το πιο σημαντικό bit καθενός από τα 4 dwords 4 bit
movmskpd το πιο σημαντικό bit καθενός από τα 2 qwords 2 bit

Αν χρησιμοποιηθεί μετά από μια σύγκριση, κάθε bit που έχει τεθεί αντιπροσωπεύει μια "αληθή" λωρίδα και κάθε bit που έχει μηδενιστεί, μια "ψευδή" λωρίδα. Αυτό το αποτέλεσμα μπορεί στη συνέχεια να χειριστεί ως συνήθως με κλιμακωτές εντολές. Για παράδειγμα, μια popcnt μετράει τον αριθμό των αντιστοιχιών και η tzcnt βρίσκει την πρώτη.

Ο καταχωρητής γενικού σκοπού μπορεί να είναι πλάτους 32 ή 64 bit.

Έλεγχος Ολόκληρου του Διανύσματος

Υπάρχει επίσης μια πακεταρισμένη παραλλαγή της κλιμακωτής εντολής test: η ptest.

Είναι παρόμοια με το κλιμακωτό αντίστοιχό της στο ότι εκτελεί μια πράξη AND ανάμεσα σε δύο τελεστέους, χωρίς να τους τροποποιεί. Σε αντίθεση με την test, η ptest εκτελεί επίσης μια πράξη ANDN, αρνώντας τον πρώτο τελεστέο.

Έτσι, η ptest μπορεί να θεωρηθεί ως μια μη καταστρεπτική εκδοχή των pand και pandn που θέτει σημαίες σύμφωνα με το αποτέλεσμα. Με τον ίδιο περίπου τρόπο όπως αυτές οι δύο εντολές, η ptest αντιμετωπίζει ολόκληρο τον καταχωρητή SIMD ως μία μόνο λωρίδα και έτσι δεν παίρνει πρόθεμα μεγέθους.

Αν το αποτέλεσμα μιας πράξης AND είναι 0, τίθεται η ZF, και αν η πράξη ANDN έχει ως αποτέλεσμα 0, αυτό που τίθεται είναι η CF. Αυτό σημαίνει ότι η ptest μπορεί να χρησιμοποιηθεί για να ελέγξει και μια μάσκα «όλα άσσους» και μια μάσκα «όλα μηδενικά»:

  1. Η χρήση της ptest σε έναν καταχωρητή με τον εαυτό του θέτει τη ZF μόνο αν ο καταχωρητής είναι όλα μηδενικά. Αυτό μιμείται το συνηθισμένο κλιμακωτό ιδίωμα της χρήσης της test σε έναν καταχωρητή με τον εαυτό του για να ελέγξεις το 0.
  2. Η χρήση της ptest σε έναν καταχωρητή με μάσκα «όλα άσσους» θέτει τη CF μόνο αν ο καταχωρητής είναι όλα άσσους. Επίσης, θέτει τη ZF μόνο αν ο καταχωρητής είναι όλα μηδενικά, κάνοντας δυνατό να ελέγξεις και τις δύο μάσκες ταυτόχρονα.
pxor    xmm0, xmm0   ; all zeros
pcmpeqb xmm1, xmm1   ; all ones
pcmpeqb xmm2, xmm2

ptest xmm0, xmm0     ; ZF set: a register against itself detects all zeros
ptest xmm0, xmm1     ; ZF set, CF clear: xmm0 is all zeros, not all ones
ptest xmm2, xmm1     ; CF is set only if xmm2 is all ones

Το αποτέλεσμα μιας ptest μπορεί να χρησιμοποιηθεί για διακλάδωση ή σε εντολές χωρίς διακλάδωση, όπως οι setcc ή cmovcc, ως συνήθως.

Οδηγίες

Διαχειρίζεσαι τον σταθμό βαθμολόγησης ενός σχολείου, βαθμολογώντας τα αποτελέσματα της τάξης ένα μπλοκ τη φορά.

Κάθε μπλοκ περιέχει 4 αποτελέσματα και ο σταθμός εφαρμόζει την ίδια πράξη σε κάθε αποτέλεσμα του μπλοκ. Μια βαθμολογία είναι ένας αριθμός κινητής υποδιαστολής 32 bit. Αρκετά βήματα δουλεύουν με μια μάσκα: ένα μπλοκ 4 λωρίδων όπου κάθε λωρίδα είναι είτε όλα άσσους (ένα ναι για αυτό το αποτέλεσμα) είτε όλα μηδενικά (ένα όχι).

Έχεις πέντε εργασίες. Τα τελούμενα τα λαμβάνεις μέσω διευθύνσεων μνήμης. Κάποιες εργασίες γράφουν την απάντησή τους σε μια διεύθυνση αποτελέσματος, ενώ άλλες την επιστρέφουν απευθείας.

Όλες οι διευθύνσεις μνήμης σε αυτή την άσκηση είναι ευθυγραμμισμένες στα 16 byte.

Note

Οι υπολογισμοί σε αυτή την άσκηση θα πρέπει να γίνουν με εντολές SIMD.

1. Επισήμανε τις βαθμολογίες πάνω από το κατώφλι

Το πρώτο βήμα αξιολογεί κάθε αποτέλεσμα σε σχέση με ένα κατώφλι. Ένα αποτέλεσμα περνάει το όριο όταν η βαθμολογία του είναι αυστηρά μεγαλύτερη από το κατώφλι. Οποιαδήποτε βαθμολογία μικρότερη ή ίση με το κατώφλι δεν το περνάει.

Υλοποίησε τη συνάρτηση flag_above_threshold, η οποία φτιάχνει μια μάσκα με λωρίδα όλα άσσους για κάθε βαθμολογία πάνω από το κατώφλι της και λωρίδα όλα μηδενικά σε κάθε άλλη περίπτωση.

Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:

  • result: διεύθυνση μνήμης για ένα buffer όπου γράφονται οι 4 λωρίδες της μάσκας.
  • scores: διεύθυνση μνήμης των βαθμολογιών, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).
  • thresholds: διεύθυνση μνήμης του κατωφλίου για κάθε λωρίδα, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).
scores     = {72.0, 55.0, 90.0, 40.0}
thresholds = {60.0, 60.0, 60.0, 60.0}
result     = {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}

Αυτή η συνάρτηση δεν έχει τιμή επιστροφής.

2. Επισήμανε τις τέλειες βαθμολογίες

Μια ξεχωριστή αναφορά αναδεικνύει τα τέλεια αποτελέσματα, εκείνα που έφτασαν τη μέγιστη δυνατή βαθμολογία.

Υλοποίησε τη συνάρτηση flag_perfect, η οποία φτιάχνει μια μάσκα με λωρίδα όλα άσσους για κάθε βαθμολογία ίση με τη μέγιστη τιμή της και λωρίδα όλα μηδενικά σε κάθε άλλη περίπτωση.

Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:

  • result: διεύθυνση μνήμης για ένα buffer όπου γράφονται οι 4 λωρίδες της μάσκας.
  • scores: διεύθυνση μνήμης των βαθμολογιών, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).
  • maxima: διεύθυνση μνήμης της μέγιστης βαθμολογίας για κάθε λωρίδα, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).
scores = {100.0, 88.0, 100.0, 73.0}
maxima = {100.0, 100.0, 100.0, 100.0}
result = {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}

Αυτή η συνάρτηση δεν έχει τιμή επιστροφής.

3. Ανάθεσε μια κατάταξη

Κάθε βαθμολογία κερδίζει μια κατάταξη από το 1 έως το 3:

  • Κατάταξη 1 για βαθμολογία ίση ή χαμηλότερη από το κατώφλι επιτυχίας του 50.0.
  • Κατάταξη 2 για βαθμολογία πάνω από αυτό το κατώφλι αλλά κάτω από τη μέγιστη.
  • Κατάταξη 3 για τέλεια βαθμολογία, μία που ισούται με τη μέγιστη.

Υλοποίησε τη συνάρτηση assign_ranks, η οποία γράφει την κατάταξη κάθε βαθμολογίας.

Θα πρέπει να ορίσεις το κατώφλι επιτυχίας και τις τιμές των κατατάξεων ως συσκευασμένες σταθερές στη μνήμη. Οι συναρτήσεις των δύο προηγούμενων εργασιών μπορούν να ξαναχρησιμοποιηθούν: μια βαθμολογία είναι τουλάχιστον κατάταξη 2 όταν είναι πάνω από το κατώφλι, και κατάταξη 3 όταν ισούται με τη μέγιστη.

Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:

  • result: διεύθυνση μνήμης για ένα buffer όπου γράφονται οι 4 κατατάξεις, καθεμία ένας ακέραιος 32 bit χωρίς πρόσημο.
  • scores: διεύθυνση μνήμης των βαθμολογιών, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).
  • maxima: διεύθυνση μνήμης της μέγιστης βαθμολογίας για κάθε λωρίδα, με 4 κανονικούς αριθμούς κινητής υποδιαστολής 32 bit (ποτέ NaN).
scores = {40.0, 75.0, 100.0, 60.0}
maxima = {100.0, 100.0, 100.0, 100.0}
result = {1, 2, 3, 2}

Αυτή η συνάρτηση δεν έχει τιμή επιστροφής.

4. Μέτρησε τις αποτυχίες

Μέσα στη χρονιά, κάθε μαθητής συγκεντρώνει μια συνολική κατάταξη. Ο σταθμός καταμετρά πόσες κατατάξεις σε ολόκληρη την ομάδα πέφτουν κάτω από ένα κατώφλι επιτυχίας, για να προγραμματίσει πόσα επιπλέον τμήματα θα χρειαστούν.

Υλοποίησε τη συνάρτηση count_failures, η οποία επιστρέφει πόσες κατατάξεις, σε όλα τα μπλοκ, είναι αυστηρά κάτω από το κατώφλι επιτυχίας. Το κατώφλι δίνεται ως ένα μπλοκ 4 ίδιων λωρίδων, ώστε να μπορείς να το φορτώσεις μία φορά και να το ξαναχρησιμοποιήσεις σε κάθε μπλοκ.

Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:

  • ranks: διεύθυνση μνήμης των κατατάξεων, ένας ακέραιος αριθμός μπλοκ 4 λωρίδων, όπου κάθε κατάταξη είναι ένας ακέραιος 32 bit χωρίς πρόσημο.
  • block_count: ο αριθμός των μπλοκ 4 λωρίδων, πάντα μεγαλύτερος από 0.
  • pass_threshold: διεύθυνση μνήμης του κατωφλίου επιτυχίας, με 4 ίδιους ακέραιους 32 bit.
ranks          = {1, 2, 3, 1, 2, 2, 1, 3} // 2 blocks
block_count    = 2
pass_threshold = {2, 2, 2, 2}
// => 3

Αυτή η συνάρτηση επιστρέφει το πλήθος ως ακέραιο 32 bit με πρόσημο.

5. Πέρασαν όλοι;

Πριν καταχωριστούν οι καταγραφές, ο σταθμός ελέγχει αν η ομάδα είναι καθαρή: περνάει όταν δεν απέτυχε ούτε ένα αποτέλεσμα σε κανένα μπλοκ.

Υλοποίησε τη συνάρτηση all_passed, η οποία επιστρέφει 1 αν πέρασαν όλοι οι μαθητές και 0 διαφορετικά. Ένας μαθητής περνάει όταν η αντίστοιχη λωρίδα του στον πίνακα failing είναι όλα μηδενικά.

Αυτή η συνάρτηση παίρνει ως ορίσματα, με αυτή τη σειρά:

  • failing: διεύθυνση μνήμης των μασκών αποτυχίας, ένας ακέραιος αριθμός μπλοκ 4 λωρίδων, όπου κάθε λωρίδα είναι όλα άσσους ή όλα μηδενικά.
  • block_count: ο αριθμός των μπλοκ 4 λωρίδων, πάντα μεγαλύτερος από 0.
failing     = {0x00000000, 0x00000000, 0x00000000, 0x00000000,
               0x00000000, 0x00000000, 0x00000000, 0x00000000} // 2 blocks
block_count = 2
// => 1

Αυτή η συνάρτηση επιστρέφει την απάντηση ως ακέραιο 32 bit με πρόσημο, είτε 1 είτε 0.

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
x86-64 Assembly Exercism

Έτοιμος να ξεκινήσεις την άσκηση Φύλλο βαθμολογίας;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις x86-64 Assembly με 22 έννοιες130 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.