[JAVA] Résumé du langage de programmation open source zig

Lorsque je recherchais le langage de programmation pour l'écriture de modules WebAssembly, j'ai senti que le langage de programmation open source relativement nouveau zig convenait à l'écriture de modules WebAssembly, j'ai donc cherché en écrivant et en exécutant le code source ** Ils sont répertoriés dans l'ordre dans lequel ils apparaissent dans le code **.

Le code source utilisé dans cet article peut être construit sur macOS ou Linux à l'URL suivante:

Qiita ne prend actuellement pas en charge la coloration syntaxique du code zig et n'est pas très visible, mais sur github, le code zig est également mis en évidence sur la syntaxe.

0. À propos du langage de programmation zig

Le langage de programmation open source zig est disponible à l'URL suivante.

zig est un langage de programmation implémenté dans LLVM, mais contrairement à C ++, Go et Rust, ** l'utilisation de la mémoire du tas est une option ** comme le langage C, et quelque chose comme new est dans la syntaxe du langage. Non intégré.

Les caractéristiques du langage zig sont:

Etc.

Cependant, notez que la syntaxe et les ** spécifications de la bibliothèque standard ne sont pas encore stables **. La version actuelle 0.4.0 et la version suivante sont à un stade où des incompatibilités se produisent au niveau de la syntaxe de type tableau de chaînes. Et ci-dessous, dans cet article, tout est décrit en fonction de la ** spécification de la version 0.4.0 **.

Tout d'abord, exécutez la commande zig zen pour avoir un aperçu de la politique de langage zig:

$ zig zen

 * Communicate intent precisely.
 * Edge cases matter.
 * Favor reading code over writing code.
 * Only one obvious way to do things.
 * Runtime crashes are better than bugs.
 * Compile errors are better than runtime crashes.
 * Incremental improvements.
 * Avoid local maximums.
 * Reduce the amount one must remember.
 * Minimize energy spent on coding style.
 * Together we serve end users.

$

Dans le langage zig, les balises de hachage telles que Twitter utilisent ziglang.

1. Installez la commande zig

Le langage zig est le principal gestionnaire de paquets homebrew, snap, nixos, scoop, chocolatey, packman, qui fournit le paquet zig ( ziglang in scoop). Il est facile d'installer la commande zig à partir de ces gestionnaires de paquets.

mode zig emacs

Le zig-mode officiel de la langue zig est enregistré dans MELPA.

Il semble également y avoir un mode pour vim, vscode, sublime:

2. Principes de base de la commande zig

Tous les outils de développement sont fournis en tant que sous-commandes de la commande zig.

commande d'exécution de fichier zig:

--zig run xxx.zig: Exécute la fonction main du fichier source xxx.zig (sans générer de binaire) --zig test xxx.zig: Exécute le code dans la syntaxe test décrite dans le fichier source xxx.zig dans l'ordre et affiche un résumé des résultats du test. --zig build: Exécute le processus de construction décrit dans la description de construction build.zig

commandes de compilation de fichiers zig:

--zig build-exe xxx.zig: Génère ** divers binaires cibles (xxx, xxx.exe, etc.) ** à partir du fichier zig (xxx.zig). --zig build-obj xxx.zig: Depuis le fichier zig (xxx.zig), ** fichier d'en-tête C ( xxx.h) et fichier objet C (xxx.o, xxx.obj) Etc.) ** Générer --zig build-lib xxx.zig: Depuis le fichier zig (xxx.zig), le fichier d'en-tête C ( xxx.h) et le fichier de bibliothèque statique C (libxxx.a, xxx.lib, etc. " ) Est généré --zig build-lib -dynamic xxx.zig: Depuis le fichier zig, le fichier d'en-tête C (xxx.h) et le fichier DLL ( libxxx.so, libxxx.dylib, xxx.dll et xxx.pdb etc.) etc.

Commandes utilitaires:

--zig init-exe: Génère des modèles de projet (build.zig et src / main.zig) pour les fichiers exécutables dans ce répertoire. --zig init-lib: Génère des modèles de projet (build.zig et src / main.zig) pour les bibliothèques statiques dans ce répertoire --zig fmt xxx.zig: xxx.zig est ** écrasé par un formateur de syntaxe. --zig translate-c xxx.c: le fichier d'en-tête C et le fichier d'implémentation C simple sont codés en zig et sortis en standard.

Commandes d'affichage des informations:

--zig --help: Liste des sous-commandes et liste des arguments de commande Afficher l'aide -- zig builtin: affiche le module d'informations sur l'environnement d'exécution de la commande zig @ import ("builtin") ʻen format de code source zig (il y a des informations telles que la cible de construction et le code dans build.zig Principalement utilisé dans) --zig cibles: Affiche une liste de ** cibles de construction qui peuvent être gérées par zig build-exe etc. ** --zig version: affiche la version spécifiée de la commande zig (telle que 0.4.0) --zig libc: Affiche les informations sur le chemin du répertoire libc de l'environnement d'exécution de la commande zig. --zig zen`: affiche la politique de conception pour le langage zig

Exemple de commande zig build-exe --release-fast -target wasm32-freestanding --name fft.wasm fft.zig

Cette ligne de commande génère un fichier de module .wasm dans WebAssembly à partir du code de la bibliothèque zig. Les options de ligne de commande ont les significations suivantes:

---- release-fast: Spécifie une construction axée sur la vitesse -- - relase-safe: axé sur la vitesse, mais le traitement des interruptions spécifie un construit avec intégré ---- release-small: Spécifie une construction sensible à la taille (sans interruptions) ---target wasm32-freestanding est la spécification wasm 32 bits de WebAssembly, qui spécifie une cible autonome à importer. ---target x84_64-windows: spécifiez la commande de la console Windows 64 bits (exe) comme cible. ---- name fft.wasm: Définit le nom du fichier de sortie sur fft.wasm`

3. programme zig: implémentation FFT

Comme exemple de code suffisamment compliqué pour toucher les différentes fonctions du langage, nous adoptons une implémentation en boucle de la FFT à transformée de Fourier ultra-rapide et l'écrivons en zig.

--Référence: "Understanding Fast Fourier Transform FFT"

Écrivez «fft.zig» comme une bibliothèque sans la fonction «main», écrivez le test dans la syntaxe «test» et vérifiez l'opération en exécutant le test avec «zig test fft.zig».

Le code entier de «fft.zig» est inférieur à 200 lignes, y compris le code de test. Ici, nous allons diviser le code en 7 parties, découper les morceaux de code dans l'ordre à partir du haut et expliquer les fonctions et les caractéristiques du langage zig inclus dans chaque morceau de code.


3.1. Charger la bibliothèque standard

Le fragment de code suivant est le code permettant de lire la valeur du rapport de circonférence et la fonction sin / cos à partir de la bibliothèque standard:

fft.zig(1)


const pi = @import("std").math.pi;
const sin = @import("std").math.sin;
const cos = @import("std").math.cos;

Bibliothèques standard et fonctions intégrées

Dans zig, vous pouvez utiliser les fonctionnalités de ** bibliothèque standard ** par @ import (" std "). Les identificateurs qui commencent par «@», tels que «@ import», sont appelés ** fonctions intégrées ** en zig, et la conversion de type est également fournie en fonction de cette fonction intégrée.

Modèle variable

En zig, ** les variables (similaires à C) sont des modèles de zones de mémoire continues **.

Par conséquent, la variable ** const ne peut pas changer tout le contenu de la zone mémoire **, et chaque membre dans le cas d'une structure et chaque élément dans le cas d'un tableau ne peuvent pas être modifiés. De plus, dans zig, les arguments de fonction sont également traités comme des variables const. Les variables dont le contenu peut être modifié sont déclarées avec var.

type const

zig a un type avec const. Le type const signifie que (en utilisant des pointeurs) les opérations de changement via cette ** référence ne sont pas autorisées **, et a un effet différent de la variable const.

Par exemple, une variable avec var a: f64 = 0.0 peut changer sa valeur et peut être référencée avec var ptr: * const f64 = & a, mais une modification de cette référence ptr. * = 1.0 entraînera une erreur de compilation. (Ptr. * Se réfère à la zone mémoire à la pointe du pointeur dans la signification de tous les membres. * Ptr en C). Inversement, pour un pointeur vers une variable avec var a: f64 = 0.0 et un pointeur vers la variable const avec const ptr: * f64 = & a, ptr. * = 1.0 est possible.

De plus, pour la variable const avec const a: f64 = 0.0, une erreur de compilation se produira à moins que le pointeur du type const`` var ptr: * const f64 = & a.

La variable const pour nommer le type

La variable const est également utilisée pour créer des alias vers des types (variables de type type) tels que struct / ʻunion / ʻenum / ʻerror`. La vérification du type de zig est déterminée par ** la correspondance de structure **, et non par la correspondance de nom de type. Par conséquent, il est possible d'affecter entre une variable de type alias et une variable de type nom d'origine.

Dans le code de, les fonctions et les variables de la bibliothèque standard sont utilisées en affectant le nom de la variable avec const, mais` @ import (" std ") ʻest une expression et peut être utilisée n'importe où dans le code.

De plus, dans zig-0.4.0, sin et cos sont des fonctions fournies par la bibliothèque standard, mais dans la prochaine version, elles seront transférées vers le intégré et utilisées comme @ sin et @ cos. Il semble que.


3.2. Définition du nombre complexe struct

L'extrait de code suivant est une déclaration de la structure de nombre complexe gérée par FFT et son constructeur et sa méthode:

fft.zig(2)


pub export const CN = extern struct {
    pub re: f64,
    pub im: f64,
    pub fn rect(re: f64, im: f64) CN {
        return CN{.re = re, .im = im,};
    }
    pub fn expi(t: f64) CN {
        return CN{.re = cos(t), .im = sin(t),};
    }
    pub fn add(self: CN, other: CN) CN {
        return CN{.re = self.re + other.re, .im = self.im + other.im,};
    }
    pub fn sub(self: CN, other: CN) CN {
        return CN{.re = self.re - other.re, .im = self.im - other.im,};
    }
    pub fn mul(self: CN, other: CN) CN {
        return CN{
            .re = self.re * other.re - self.im * other.im,
            .im = self.re * other.im + self.im * other.re,
        };
    }
    pub fn rdiv(self: CN, re: f64) CN {
        return CN{.re = self.re / re, .im = self.im / re,};
    }
};

Attribut pub

L'attribut pub est un attribut attaché aux ** variables et fonctions qui sont utilisés lorsque ** @ import est créé à partir d'autres fichiers zig (tels que pi et sin dans la bibliothèque standard). Sans pub, même si @ import est fait, les variables et les fonctions ne peuvent pas être utilisées directement.

Attribut ʻExport`

L'attribut ʻextport` est un attribut attaché aux ** fonctions et variables utilisées à partir de l'en-tête ** C (.h). Il s'agit d'un attribut attaché à la cible ** utilisé à partir du fichier objet ou du fichier bibliothèque.

Si vous ajoutez ʻexport à une fonction, la déclaration prototype correspondant à cette fonction est intégrée dans le fichier d'en-tête C. Si vous ajoutez ʻexport à une variable d'un type comme struct, les informations de type seront incorporées dans le fichier d'en-tête C.

Comme pour «externe» en C, ** ne doit pas être nommé dans tous les fichiers **.

Variation de struct

Il existe trois types de type struct de zig, en fonction de la différence de disposition de la mémoire et de gestion dans ʻexport`.

--struct: ** Inclut l'ordre des membres ** Ne spécifie pas la disposition de la mémoire ** struct. Dans l'en-tête C à ʻexport, il est incorporé en tant que déclaration avec le pointeur struct. --packed struct: ** La disposition de la mémoire structqui organise les membres dans l'ordre de déclaration **. Incorporé comme une déclaration de pointeurstruct dans l'en-tête C à ʻexport --ʻExtern struct: La disposition de la mémoire structqui organise les membres dans l'ordre de déclaration. Il est intégré en tant que déclaration de définition de membre ** de **struct dans l'en-tête C au moment de ʻexport.

Fonction membre de struct

En zig, vous pouvez déclarer la fonction fn inside struct. Cette fonction est appelée sous la forme de type nom.fonction nom (...). Si le premier argument est un struct défini, tel que ʻadd ou sub, vous pouvez également l'écrire dans le style ** method call ** ( CN.add (a, b) , Il peut également être décrit comme a.add (b) `).

Dans zig-0.4.0, même si la fonction est dans struct, dans l'en-tête C à ʻexport, il n'y a pas de préfixe du nom de la structure, et le prototype est déclaré avec le nom de la fonction tel quel ( CN). Quand .rect () ʻest ʻexport, il devient juste rect () ʻau lieu deCN_rect ().

Type numérique

Le type numérique de zig spécifie le nombre de bits **. Il existe également un type numérique dépendant de la cible pour l'interface du langage C:

--Types non numériques: type, noreturn, void, bool --Type entier signé: ʻi8, ʻi16, ʻi32, ʻi64, ʻi128`

De plus, le côté droit de ** opération de décalage (>>, <<) a le nombre de bits -1 sur le côté gauche tel que ʻu3, ʻu4, ʻu5, ʻu6, ʻu7` **. Utilisez des types entiers comme limite supérieure (les variables sont descendues vers ces types).

Dans zig, ** les types bool qui reçoivent true et false ne sont pas des types numériques **, mais ils peuvent être convertis en types numériques 0/1 en utilisant le.

--type est le type de type (y compris les types personnalisés tels que struct) --void est un type sans valeur et est également un type d'expression de bloc (sans passer une valeur avec break) --noreturn est le type de retour pour les fonctions qui ne se terminent pas normalement, comme un processus se terminant au milieu ou une boucle infinie.

initialiseur struct

L'initialiseur (littéral) de la structure est nom de la structure {. Nom du membre = valeur, ...}. La syntaxe est similaire à l'initialiseur de membre de structure C99 ((type) {.member name = value, ...}).

Les arguments de fonction et les valeurs de retour sont des copies des valeurs

Les arguments de fonction et les valeurs de retour sont des modèles ** valeurs de copie **. Même s'il s'agit d'un struct ou d'un tableau, il est traité comme une copie. Il est possible d'utiliser le type pointeur comme type d'argument ou de valeur de retour.

Quelle que soit la taille du type, il sera copié sur la pile, donc si vous gérez mal le type de tableau, vous risquez d'atteindre la limite de pile du système d'exploitation.


3.3. Code d’essai de la structure «CN»

L'extrait de code suivant est du code qui utilise la fonction membre CN déclarée dans la syntaxe du code de test.

fft.zig(3)


test "CN" {
    const assert = @import("std").debug.assert;
    const eps = @import("std").math.f64_epsilon;
    const abs = @import("std").math.fabs;

    const a = CN.rect(1.0, 0.0);
    const b = CN.expi(pi / 2.0);
    assert(a.re == 1.0 and a.im == 0.0);
    assert(abs(b.re) < eps and abs(b.im - 1.0) < eps);
    
    const apb = a.add(b);
    const asb = a.sub(b);
    assert(abs(apb.re - 1.0) < eps and abs(apb.im - 1.0) < eps);
    assert(abs(asb.re - 1.0) < eps and abs(asb.im + 1.0) < eps);
    
    const bmb = b.mul(b);
    assert(abs(bmb.re + 1.0) < eps and abs(bmb.im) < eps);
    
    const apb2 = apb.rdiv(2.0);
    assert(abs(apb2.re - 0.5) < eps and abs(apb2.im - 0.5) < eps);
}

partie test

Le test" name "{...} est la partie qui décrit le code ** qui est exécuté pendant la commande ** zig test.

Depuis zig-0.4.0, vous ne pouvez pas définir une fonction directement dans test. Cependant, il est possible de déclarer une fonction dans struct même dans test, et il est possible d'utiliser la fonction en appelant une méthode.

Appel de méthode de structure

La fonction de fn dans struct peut être appelée parnom de la structure.nom de la fonction (), en particulier lorsque le premier argument est que struct, elle peut également être appelée parnom de variable.nom de fonction (), Il a la syntaxe.

ʻA.add (b) dans ce code peut aussi être appelé par CN.add (a, b) `.


3.4. Code de test pour le tableau «CN»

L'extrait de code suivant est un code de test à utiliser avec un tableau de «CN».

fft.zig(4)


test "CN array" {
    const assert = @import("std").debug.assert;
    const eps = @import("std").math.f64_epsilon;
    const abs = @import("std").math.fabs;

    var cns: [2]CN = undefined;
    cns[0] = CN.rect(1.0, 0.0);
    cns[1] = CN.rect(0.0, 1.0);
    cns[0] = cns[0].add(cns[1]);
    assert(abs(cns[0].re - 1.0) < eps and abs(cns[0].im - 1.0) < eps);
}

tableau de zig

Le type de tableau de zig est «type d'élément [nombre d'éléments]», ce qui signifie ** zone continue avec le nombre d'éléments spécifié. Le type de tableau multidimensionnel est «type d'élément [nombre d'éléments externes] [nombre d'éléments internes]». Dans la disposition de la mémoire, plusieurs éléments internes sont alignés avec plusieurs éléments externes.

Si le type d'élément est struct, les membres sont disposés dans l'ordre, et cela devient une disposition de mémoire avec plusieurs éléments. L'affectation d'un élément de tableau à un élément écrase la valeur.

La déclaration de non-initialisation d'un tableau par la variable var est var nom de la variable: [nombre d'éléments] type d'élément = undefined. Le nombre d'éléments est obligatoire.

L'initialiseur de type de tableau (littéral) est [nombre d'éléments] type d'élément {valeur initiale 0, valeur initiale 1, ....}. Vous pouvez omettre le nombre premier total dans l'initialiseur. Les initialiseurs avec la même valeur peuvent aussi avoir un [] type d'élément {valeur initiale 0, valeur initiale 1, ...} ** nombre d'itérations. Dans ce cas, il s'agit d'un tableau de type du nombre de valeurs initiales ✕ du nombre d'éléments du nombre de répétitions.

La déclaration avec la valeur initiale du tableau par la variable var utilise l'initialiseur du type tableau, et le type d'élément var variable name = [nombre d'éléments] {valeur initiale 0, valeur initiale 1, ...}. Déclaration avec valeur initiale pour const variableconst variable name = [nombre d'éléments] Type d'élément {valeur initiale 0, valeur initiale 1, ...} ʻest généralement utilisé (exemple: const v = [3] u8 {1,2,3} `).

Notez que déclarer une variable tableau dans une fonction ** alloue de l'espace dans la mémoire de la pile **. Pour cette raison, il est facile d'atteindre la limite de pile du système d'exploitation (8 Mo, etc.) lors de l'utilisation d'un tableau, donc soyez prudent. (La méthode d'utilisation du tas, etc. dans la bibliothèque standard sera décrite plus tard.)


3.5. Inversion des bits d'index pour FFT

L'extrait de code suivant est la définition de la fonction d'inversion de bits d'index de tableau revbit dans FFT et son code de test.

fft.zig(5)


fn revbit(k: u32, n0: u32) u32 {
    return @bitreverse(u32, n0) >> @truncate(u5, 32 - k);
}

test "revbit" {
    const assert = @import("std").debug.assert;
    const n: u32 = 8;
    const k: u32 = @ctz(n);
    assert(revbit(k, 0) == 0b000);
    assert(revbit(k, 1) == 0b100);
    assert(revbit(k, 2) == 0b010);
    assert(revbit(k, 3) == 0b110);
    assert(revbit(k, 4) == 0b001);
    assert(revbit(k, 5) == 0b101);
    assert(revbit(k, 6) == 0b011);
    assert(revbit(k, 7) == 0b111);
}

Arithmétique binaire intégrée

Dans zig, ** les opérations sur les bits intrinsèques du processeur fournies par les instructions du processeur sont fournies comme intégrées **.

--@ bitreverse (type numérique, numérique): Bit à l'envers (Remarque: le nom deviendra @ bitReverse dans la prochaine version) --@ ctz (numeric): compte les zéros à la fin du bit le moins significatif. S'il s'agit d'une valeur de puissance de 2, c'est la même valeur que «log2 (valeur numérique)».

D'autres incluent «@ clz» (nombre de 0 après le bit le plus significatif), «@ popCount» (nombre de 1), «@ bswap» (à l'envers en octets), et ainsi de suite.

Distribution explicite

Avec zig, vous ne pouvez développer implicitement ** que si le nombre de bits augmente pour le même type numérique. En dehors de cela, vous devez sélectionner et utiliser chaque ** fonction intégrée pour la diffusion selon la méthode de conversion **.

--@ truncate (type de conversion, valeur): Cast qui coupe uniquement les bits inférieurs jusqu'au nombre de bits du type de conversion

Dans l'opération de décalage, il est nécessaire de tronquer à un type numérique qui correspond au nombre de bits du type de la variable à décaler. Puisque 32 vaut 2 à la 5ème puissance, il est essentiel de tronquer le type à «u5» dans la rvaleur de l'opération de décalage de «u32».

Il existe de nombreuses fonctions intégrées de cast, mais les suivantes sont typiques

--@ bitCast (type de transformation, valeur): Cast qui enregistre la disposition des bits. Par exemple, lors de la conversion de «f64» en «u64» en tant que champ de bits. --@ ptrCast (type de pointeur de conversion, valeur de pointeur): conversion de type pointeur --@ intCast (type entier de conversion, valeur entière): Conversion de type de valeurs entières avec différents nombres de bits --@ floatCast (conversion de type nombre flottant, nombre flottant): Conversion de type de nombre flottant avec un nombre différent de bits

--@ floatToInt (type entier de conversion, valeur décimale flottante): Conversion numérique en entier --@ intToFloat (type de fraction immobile de conversion, valeur entière): Conversion numérique en fraction flottante --@ ptrToInt (type entier de conversion, valeur du pointeur): Conversion de l'adresse du pointeur en entier --@ intToPtr (type de pointeur de conversion, valeur entière): Conversion de la valeur d'adresse du pointeur en pointeur --@ boolToInt (valeur de validité): Conversion de la valeur booléenne en entier (ʻu1`)


3.6. Corps FFT

Le fragment de code suivant est le code d'implémentation de la version en boucle de FFT.

fft.zig(6)


fn fftc(t0: f64, n: u32, c: [*]CN, r: [*]CN) void {
    {
        const k: u32 = @ctz(n);
        var i: u32 = 0;
        while (i < n) : (i += 1) {
            r[i] = c[@inlineCall(revbit, k, i)];
        }
    }
    var t = t0;
    var nh: u32 = 1;
    while (nh < n) : (nh <<= 1) {
        t /= 2.0;
        const nh2 = nh << 1;
        var s: u32 = 0;
        while (s < n) : (s += nh2) {
            var i: u32 = 0;
            while (i < nh) : (i += 1) {
                const li = s + i;
                const ri = li + nh;
                const re = @inlineCall(CN.mul, r[ri], @inlineCall(CN.expi, t * @intToFloat(f64, i)));
                const l = r[li];
                r[li] = @inlineCall(CN.add, l, re);
                r[ri] = @inlineCall(CN.sub, l, re);
            }
        }
    }
}
pub export fn fft(n: u32, f: [*]CN, F: [*]CN) void {
    fftc(-2.0 * pi, n, f, F);
}
pub export fn ifft(n: u32, F: [*]CN, f: [*]CN) void {
    fftc(2.0 * pi, n, F, f);
    const nf64 = @intToFloat(f64, n);
    var i: u32 = 0;
    while (i < n) : (i += 1) {
        f[i] = f[i].rdiv(nf64);
    }
}

Type de pointeur

La variable zig est une zone de mémoire et a un ** type de pointeur ** qui pointe vers son adresse de départ. Le type de pointeur vers le type valeur est un type avec «» au début du type, tel que « u8».

Dans zig, les variables de type pointeur et les variables du type à partir duquel elles sont basées sont accessibles avec la même description **.

Dans zig, les tableaux sont également des types de valeur qui représentent des zones de mémoire continues. Contrairement au langage C, ** les types de tableaux et les types de pointeurs vers les tableaux sont clairement distingués **.

Dans zig-0.4.0, le type de pointeur de tableau est représenté par le «type d'élément [*]». Le type de pointeur de tableau peut également pointer vers des éléments au milieu du tableau, et comme C, le nombre d'index de décalage peut être ajouté ou soustrait.

La variable de type pointeur du tableau peut accéder aux éléments du tableau avec la même description que la variable de type tableau.

Les autres types liés aux pointeurs de tableau sont:

-- * [Nombre d'éléments] Type d'élément: Un pointeur vers le tableau entier. Le nombre d'éléments (.len) est fixé au moment de la compilation -- [] Type d'élément: Le nombre d'éléments (.len) est obtenu au moment de l'exécution ** Slice **

Une tranche en zig est une structure de données intégrée en zig qui pointe vers une zone continue de la zone de tableau, et possède un pointeur de tableau (.ptr) pour l'adresse de départ et le nombre d'éléments ( .len).

Résumé,

--const a: [12] u8 = [] u8 {1} ** 12; : Variable de tableau. Le nombre d'éléments est fixé au moment de la compilation --const b: * const [12] u8 = & a; : variable de pointeur vers le tableau entier. Le nombre d'éléments est fixé au moment de la compilation --const c: [] const u8 = & a; : Type de tranche. Le nombre d'éléments est fixé à l'exécution --const d: [*] const u8 = & a; : variable de pointeur vers le tableau. N'a pas le nombre d'éléments

Les trois variables ci-dessus peuvent récupérer le nombre d'éléments avec «a.len», «b.len» et «c.len», mais «d» n'a pas ce moyen.

Voici la syntaxe pour l'attribution à d'autres tranches et pointeurs de tableau.

Pour créer une tranche à partir d'un pointeur de tableau (c3), vous devez spécifier l'index ** end point **.

les variables zig ne peuvent pas être masquées dans la portée du bloc

Les variables zig ont une portée de bloc, mais contrairement à de nombreux langages, vous ne pouvez pas définir une variable avec le même nom qu'une variable définie en dehors du bloc ** à l'intérieur du bloc.

La variable «i» est utilisée dans la première partie de la fonction «fftc» pour copier dans l'index inversé de bits. Si vous supprimez le { } qui entoure cette partie, vous obtiendrez une erreur de compilation car ʻi` est également utilisé dans la boucle ci-dessous.

boucle zig et bloc

zig a une boucle de «for» et «while».

La boucle while a les variations suivantes:

--while (expression conditionnelle) {...}: Equivalent àwhile (expression conditionnelle) {...}en langage C --while (expression conditionnelle): (expression post-traitement) {...}:for (; expression conditionnelle; expression post-traitement) {...}équivalent en langage C

Dans zig, ** les variables de compteur de boucle dans while doivent être déclarées en dehors de la boucle **.

En zig, for et while peuvent recevoir une expression dans la partie ʻelse` qui est traitée après le bouclage.

La boucle peut avoir une valeur de résultat comme expression. La partie ʻelseest requise dans l'expression de boucle, et la valeur transmise à la valeur debreak` est la valeur de l'expression.

Dans ces deux exemples, "v" est "1" si "cond" est "vrai" et "v" est "2" si "faux".

La partie ʻelse` dans l'exemple minimal 2 n'est pas une syntaxe de style bloc, mais une ** expression de bloc **, qui est l'une des syntaxes d'expression de zig. ** La valeur de l'expression de bloc doit être passée dans un «break» étiqueté **, pour lequel vous devez étiqueter l'expression de bloc comme quelque chose.

Appel de fonction en ligne

Vous pouvez également ajouter ʻinline à la fn de zig, ce qui force tous les appels à être insérés. Cependant, ʻinline et fn ont des restrictions d'utilisation. Par exemple, vous ne pourrez pas passer «fn» à une variable ou un argument. De plus, contrairement au langage C, en zig, ʻinline provoquera une erreur de compilation s'il y a une situation où même un seul ne peut pas être inséré dans fn`.

En zig, il est possible de spécifier ** force inline ** sur une fonction régulière sur l'appelant au lieu de spécifier tout en ligne dans la définition. C'est le «@ inlineCall» intégré.


3.7. Code de test FFT

L'extrait de code suivant est un code de test pour l'implémentation de la version en boucle de FFT.

`fft.zig(7)``


test "fft/ifft" {
    const warn = @import("std").debug.warn;
    const assert = @import("std").debug.assert;
    const abs = @import("std").math.fabs;
    const eps = 1e-15;

    const util = struct {
        fn warnCNs(n: u32, cns: [*]CN) void {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                warn("{} + {}i\n", cns[i].re, cns[i].im);
            }
        }
    };

    const n: u32 = 16;
    const v = [n]i32{ 1, 3, 4, 2, 5, 6, 2, 4, 0, 1, 3, 4, 5, 62, 2, 3 };
    var f: [n]CN = undefined;
    {
        var i: u32 = 0;
        while (i < n) : (i += 1) {
            f[i] = CN.rect(@intToFloat(f64, v[i]), 0.0);
        }
    }
    warn("\n[f]\n");
    util.warnCNs(n, &f);

    var F: [n]CN = undefined;
    var r: [n]CN = undefined;
    fft(n, &f, &F);
    ifft(n, &F, &r);

    warn("\n[F]\n");
    util.warnCNs(n, &F);
    warn("\n[r]\n");
    util.warnCNs(n, &r);

    {
        var i: u32 = 0;
        while (i < n) : (i += 1) {
            assert(abs(r[i].re - @intToFloat(f64, v[i])) < eps);
        }
    }
}

La définition de fonction dans test peut être effectuée dans struct

Dans zig-0.4.0, fn ne peut pas être décrit au niveau supérieur de la partie test, mais la fonction peut être décrite dans ** struct **, et il était possible de l'appeler comme une méthode statique.

Référence au type de pointeur

En zig, la syntaxe pour obtenir un pointeur à partir d'un type valeur est & variable. Contrairement au langage C, les variables de tableau ne peuvent pas être traitées implicitement comme des pointeurs de tableau et doivent être ** explicitement ajoutées avec & **.

La méthode de référencement d'un élément à partir d'un type pointeur est la même que celle d'un type valeur (ʻa [i] ʻet ʻa.m`).

En tant qu'expression spécifique au pointeur, il existe une ** déréférence de valeur entière *, qui est suivie d'un . * (V = a. * Ou ʻa. * = V). Cela équivaut à la déréférence du pointeur ptr` en langage C.

Sortie de la console par warn

Vous pouvez utiliser la bibliothèque standard warn pour avoir les caractères de sortie de la console (vers stderr) en utilisant une chaîne de format. Il est également généré en exécutant zig test.


3.8. Résultat de l'exécution du test zig

L'exécution de zig test fft.zig sur ce fft.zig donne les résultats suivants:

$ zig test fft.zig
Test 1/4 CN...OK
Test 2/4 CN array...OK
Test 3/4 revbit...OK
Test 4/4 fft/ifft...
[f]
1.0e+00 + 0.0e+00i
3.0e+00 + 0.0e+00i
4.0e+00 + 0.0e+00i
2.0e+00 + 0.0e+00i
5.0e+00 + 0.0e+00i
6.0e+00 + 0.0e+00i
2.0e+00 + 0.0e+00i
4.0e+00 + 0.0e+00i
0.0e+00 + 0.0e+00i
1.0e+00 + 0.0e+00i
3.0e+00 + 0.0e+00i
4.0e+00 + 0.0e+00i
5.0e+00 + 0.0e+00i
6.2e+01 + 0.0e+00i
2.0e+00 + 0.0e+00i
3.0e+00 + 0.0e+00i

[F]
1.07e+02 + 0.0e+00i
2.329589166141268e+01 + 5.1729855807372815e+01i
-5.35477272147525e+01 + 4.2961940777125584e+01i
-4.921391810443094e+01 + -2.567438445589562e+01i
3.612708057484687e-15 + -5.9e+01i
4.9799704542057846e+01 + -2.4260170893522517e+01i
3.554772721475249e+01 + 4.89619407771256e+01i
-1.9881678099039597e+01 + 5.314406936974591e+01i
-6.3e+01 + 0.0e+00i
-1.9881678099039586e+01 + -5.314406936974591e+01i
3.55477272147525e+01 + -4.8961940777125584e+01i
4.9799704542057846e+01 + 2.4260170893522528e+01i
-3.612708057484687e-15 + 5.9e+01i
-4.921391810443094e+01 + 2.567438445589561e+01i
-5.354772721475249e+01 + -4.29619407771256e+01i
2.329589166141269e+01 + -5.1729855807372815e+01i

[r]
1.0e+00 + 0.0e+00i
3.0e+00 + -3.497202527569243e-15i
3.999999999999999e+00 + -1.0143540619928351e-16i
1.9999999999999996e+00 + 8.465450562766819e-16i
5.0e+00 + 0.0e+00i
6.0e+00 + 0.0e+00i
2.0e+00 + 4.592425496802568e-17i
4.0e+00 + -7.077671781985373e-16i
0.0e+00 + 0.0e+00i
1.0e+00 + -5.551115123125783e-17i
3.000000000000001e+00 + 9.586896263232146e-18i
4.0e+00 + 5.134781488891349e-16i
5.0e+00 + 0.0e+00i
6.2e+01 + 3.552713678800501e-15i
2.0e+00 + 4.592425496802568e-17i
2.9999999999999996e+00 + -6.522560269672795e-16i
OK
All tests passed.
$

4. Code Zig du fichier exécutable en utilisant fft.zig

Remarque: dans zig, le ** processus de démarrage des commandes et des bibliothèques est implémenté ** dans la bibliothèque standard. La description ici est basée sur l'implémentation standard de la bibliothèque dans zig-0.4.0. Les spécifications ici peuvent changer considérablement à l'avenir.

Tout d'abord, nous expliquerons uniquement comment utiliser la fonction main qui est appelée depuis la bibliothèque standard zig lors de l'exécution d'une commande, puis nous expliquerons le programme zig qui appelle l'implémentation FFT mentionnée ci-dessus par @ import (" fft.zig ").


4.1 Aperçu de la fonction main de zig

Voici un exemple de code de la fonction de point d'entrée main dans l'exécution de la ligne de commande, qui ne gère que les variables d'environnement, les arguments de commande et le code de sortie:

example-main.zig


pub fn main() noreturn {
    // environment variable
    const pathOptional: ?[]const u8 = @import("std").os.getEnvPosix("SHELL");
    const path: []const u8 = pathOptional orelse "";
    @import("std").debug.warn("SHELL: {}\n", path);

    // command args
    var count: u8 = 0;
    var args = @import("std").os.args();
    const total = while (args.nextPosix()) |arg| {
        @import("std").debug.warn("{}: {}\n", count, arg);
        count += 1;
    } else count;

    // exit with code: `echo $?`
    @import("std").os.exit(total);
}

type de fonction main et code de sortie

La fonction ** main** sans argumentspub` est le point d'entrée de votre code utilisateur.

Les types de retour de main sont noreturn (quitter sans être exécuté par le nom de famille), ʻu8 (la valeur de retour devient le code de sortie) et void(le code de sortie est 0). ,! Void (erreur ou void`).

Dans le cas de ce code, main est réglé sur noreturn car ʻos.exit ()de la bibliothèque standard pour quitter utilisée dans est une fonctionnoreturn. Lorsque vous utilisez ʻos.exit () , passez le code de sortie comme argument (type ʻu8`).

Si la valeur de retour est ! Void, le code de sortie sera 1 s'il y a une erreur.

Type de jeu d'erreurs et type d'union d'erreur

Le type d'ensemble d'erreurs ** défini par la syntaxe ** ʻerror de zig est l'un des types personnalisés tels que struct et ʻenum, et est un type d'énumération similaire à ʻenum. Exemple: const MyError = error {Fail, Dead,};`

La différence avec ʻenum` est que le type d'ensemble d'erreurs gère l'erreur qui est son élément collectivement comme suit.

--Existence de ** type ʻany error ** qui reçoit tous les types de jeux d'erreurs personnalisés -Deux types de jeux d'erreurs**Type de jeu d'erreurs d'ensemble de somme**Peut être fait(const ErrorsC = ErrosA || ErrorsB;`)

Les types avec !, Tels que! U8 pour les types de retour et MyError! U8 pour les types de variables, sont les ** types d'union d'erreur ** de zig. Le type d'union d'erreur est une ** variable ou un type de retour qui peut accepter à la fois le type de valeur ** et le type «erreur». En faisant de ce type d'union d'erreur le type de retour, en zig, ** les erreurs seront également des valeurs return **. Le type de retour «! U8» est le type d'ensemble d'erreurs sur la gauche qui est déduit du code d'implémentation de la fonction, et est en fait un type d'union d'erreur tel que «MyError! U8».

Pour passer d'un type d'union d'erreur à une expression de type valeur, zig peut utiliser l'opérateur binaire catch et l'opérateur de terme unique try.

catchSection de capture de l'opérateur|err|Peut être omis si la valeur d'erreur n'est pas utilisée. La valeur de retour d'une fonction qui utilise l'opérateur try à l'intérieur doit être de type error union.

Il est également possible de trier l'heure d'erreur comme ʻelse du type d'union d'erreur en utilisant une expression conditionnelle de l'expression ʻif ou de l'expression switch.

Variables d'environnement et types facultatifs

Les variables d'environnement peuvent être récupérées avec la bibliothèque standard ʻos.getEnvPosix () `. La valeur de retour de cette fonction est «? [] Const u8».

Le type avec ce «?» Dans la tête est le ** type optionnel ** de zig. Le type facultatif est la ** valeur ou valeur de zig ** null. (Par conséquent, le type de pointeur ne peut pas être «null» sauf s'il s'agit d'un type facultatif.)

Comme pour le type d'union d'erreur, il peut s'agir d'un type valeur en donnant une valeur de repli avec l'expression ʻif ou l'expression switch, et lorsque null, avec ʻelse. Il a également la même position que l'opérateur binomial catch dans l'union d'erreur, avec l'opérateur binomial ʻorelse` pour les types optionnels.

Vous pouvez également le forcer à un type valeur (comme l'opérateur de terme unique try de type ʻerror) en ajoutant const v: u8 = nullOrInt ().? ʻAnd.?. Cependant, contrairement à «try», aucune erreur ou «null» n'est «return», et il semble que zig-0.4.0 entraînera une terminaison forcée intolérable.

Arguments de commande

Vous pouvez récupérer des arguments de ligne de commande avec ʻos.args () ʻ dans la bibliothèque standard. Ceci est un itérateur et peut être récupéré l'un après l'autre sous forme de chaîne optionnelle de type ? [] Const u8 en utilisant la méthode nextPosix ().

Pour récupérer séquentiellement à partir de l'itérateur jusqu'à ce qu'il devienne "null", utilisez une boucle while ** qui capture la ** valeur de la condition. S'il devient «nul», il entrera dans la partie «autre».

Lorsqu'il est converti en binaire, le ** 0e argument de commande est le nom de la commande ** et l'argument est le suivant.

Pour zig run, vous pouvez passer un argument de commande après -- dans zig run example-main.zig --a b c. Dans zig-0.4.0, dans ce cas, l'argument commence à 0e.


4.2.code zig principal qui utilise fft.zig comme @ import

Ce qui suit est un programme qui affiche les résultats FFT / IFFT avec les données utilisées dans le test, puis affiche le temps de référence pour l'exécution de FFT et IFFT de 1 million (2 à la 20e puissance) éléments.

fft-bench-zig.zig


const heap = @import("std").heap;
const warn = @import("std").debug.warn;
const sin = @import("std").math.sin;
const milliTimestamp = @import("std").os.time.milliTimestamp;

const CN = @import("./fft.zig").CN;
const fft = @import("./fft.zig").fft;
const ifft = @import("./fft.zig").ifft;

fn warnCNs(n: u32, cns: [*]CN) void {
    var i: u32 = 0;
    while (i < n) : (i += 1) {
        warn("{} + {}i\n", cns[i].re, cns[i].im);
    }
}

pub fn main() !void {
    { //example
        const n: u32 = 16;
        const v = [n]i32{ 1, 3, 4, 2, 5, 6, 2, 4, 0, 1, 3, 4, 5, 62, 2, 3 };
        var f: [n]CN = undefined;
        {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                f[i] = CN.rect(@intToFloat(f64, v[i]), 0.0);
            }
        }
        warn("\n[f]\n");
        warnCNs(n, &f);

        var F: [n]CN = undefined;
        var r: [n]CN = undefined;
        fft(n, &f, &F);
        ifft(n, &F, &r);

        warn("\n[F]\n");
        warnCNs(n, &F);
        warn("\n[r]\n");
        warnCNs(n, &r);
    }

    { //benchmark
        //NOTE: allocators in std will be changed on 0.5.0
        var direct_allocator = heap.DirectAllocator.init();
        var arena = heap.ArenaAllocator.init(&direct_allocator.allocator);
        const allocator = &arena.allocator;
        defer direct_allocator.deinit();
        defer arena.deinit();

        const n: u32 = 1024 * 1024;
        var f = try allocator.create([n]CN);
        {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                const if64 = @intToFloat(f64, i);
                f[i] = CN.rect(sin(if64) * if64, 0.0);
            }
        }

        var F = try allocator.create([n]CN);
        var r = try allocator.create([n]CN);

        const start = milliTimestamp();
        fft(n, f, F);
        ifft(n, F, r);
        const stop = milliTimestamp();
        warn("fft-ifft: {}ms\n", stop  -start);
    }
}

@ Importer à partir du fichier zig

Vous pouvez ** @ import et utiliser les variables et les fonctions de l'attribut pub ** pour votre propre fichier source zig ainsi que pour la bibliothèque standard.

En fait, la bibliothèque standard existe également en tant que fichier source zig. Vous pouvez vérifier le code d'implémentation en le développant sous lib / zig / std / dans le répertoire d'installation.

Utilisation d'une énorme mémoire

Dans le langage zig, fondamentalement, les variables utilisent la zone de la mémoire de pile (mémoire statique). Si vous utilisez un grand tableau, ** vous dépasserez la limite de la pile du système d'exploitation au moment de l'exécution **, votre programme doit donc utiliser une zone de mémoire hors pile, telle qu'un tas.

La bibliothèque standard fournit un ** allocateur de mémoire ** qui gère la mémoire du tas. Voici l'implémentation d'allocateur dans zig-0.4.0:

--heap.DirectAllocator: Allocateurs pour allouer la mémoire du tas (ʻinit, deinit, ʻalloc, shrink, realloc) --heap.ArenaAllocator: Un allocateur qui configure un pool de mémoire en utilisant d'autres allocateurs et libère toute la mémoire allouée jusqu'à présent avec deinit. --mem.Allocator: Interface pour allouer de la mémoire en tapant (create, destroy)

Dans zig-0.4.0, l'allocateur heap a l'interface mem.Allocator comme membre .allocator (dans ce code ʻarena.allocator`).

Notez que le type de valeur est passé dans l'argument create de mem.Allocator, mais la valeur de retour est un type de pointeur (type union d'erreur) vers ce type de valeur.

--ʻAllocator.create ([1048576] CN) Le type de retour de ʻis! * [1048576] CN --var f = essayez allocator.create ([1048576] CN); ʻa type est * [1048576] CN --f: * [1048576] CN peut être assigné à la variable [*] CN(le deuxième argument defft ()` dans le code) sans conversion explicite.

Déclaration defer

L'instruction defer décrit à l'avance le code qui sera exécuté lorsque le bloc s'échappe. S'il y a plusieurs différers dans le même bloc, ils seront exécutés ** dans l'ordre à partir de l'arrière **. En écrivant dans un endroit proche à l'aide de «différer», il est possible de décrire le processus d'initialisation et le processus de fin correspondant à l'initialisation en tant que paire.

Un similaire est ʻerrdefer. Le différer qui est exécuté lorsque le bloc s'échappe ** lorsque l'erreur est renvoyée. Lors de l'échappement d'un bloc avec l'erreur return, il est exécuté dans l'ordre de l'arrière dans une séquence mixte de ʻerrdefer et defer.


4.3. Construire le fichier exécutable

A partir de ce fft-bench-zig.zig, vous pouvez générer le fichier exécutable fft-bench-zig avec la ligne de commande suivante.

$ zig build-exe --release-fast fft-bench-zig.zig

Vous pouvez également exécuter votre code avec zig run fft-bench-zig.zig sans avoir à le construire.


5. Compilez fft.zig dans un module WebAseembly et utilisez-le en JavaScript

Lors de la génération d'un module WebAssembly à partir de code écrit dans un langage de programmation normal tel que Rust, afin d'utiliser le module wasm, il est nécessaire de prendre en compte l'implémentation de la bibliothèque d'exécution du langage de programmation, en particulier les fonctions liées à la mémoire du tas. Dans les importations et les exportations du module wasm, il est nécessaire d'avoir une méthode d'opération de mémoire, etc. dans cette langue, et de l'implémenter et de l'utiliser en fonction de son utilisation.

Cependant, si vous créez un module wasm à partir d'un programme zig, vous générerez un module wasm ** qui n'exposera aucune méthode liée à l'exécution du langage. La plus grande différence est que zig n'utilise fondamentalement pas de mémoire de tas (le langage C est fondamentalement le même, mais je l'écris sans utiliser stdlib.h qui a du tout une gestion de tas. C'est plus difficile).

Dans le fichier fft.zig précédemment implémenté, les fonctions`` fft et ʻifft sont ʻexport`, et il est possible de compiler vers un fichier wasm tel quel.

Vous pouvez générer fft.wasm avec la ligne de commande suivante.

 $ zig build-exe --release-fast -target wasm32-freestanding --name fft.wasm fft.zig

5.1. Code JavaScript qui utilise fft.wasm dans node.js

Le code JavaScript ci-dessous est le programme fft-bench-nodejs.js qui charge et utilise le fichier wasm généré à partir de ce code zig. Semblable au précédent fft-bench-zig.zig, il s'agit d'un benchmark qui affiche la conversion FFT / IFFT avec les données d'exemple, puis affiche le temps d'exécution de FFT / IFFT de 1 million de données d'élément.

fft-bench-nodejs.js


const fs = require("fs").promises;

(async function () {
  const buf = await fs.readFile("./fft.wasm");
  const {instance} = await WebAssembly.instantiate(buf, {});
  const {__wasm_call_ctors, memory, fft, ifft} = instance.exports;
  __wasm_call_ctors();
  memory.grow(1);
  
  {// example
    const N = 16, fofs = 0, Fofs = N * 2 * 8, rofs = N * 4 * 8;
    const f = new Float64Array(memory.buffer, fofs, N * 2);
    const F = new Float64Array(memory.buffer, Fofs, N * 2);
    const r = new Float64Array(memory.buffer, rofs, N * 2);

    const fr0 = [1,3,4,2, 5,6,2,4, 0,1,3,4, 5,62,2,3];
    fr0.forEach((v, i) => {
      [f[i * 2], f[i * 2 + 1]] = [v, 0.0];
    });

    fft(N, fofs, Fofs);
    ifft(N, Fofs, rofs);

    console.log(`[fft]`);
    for (let i = 0; i < N; i++) {
      console.log([F[i * 2], F[i * 2 + 1]]);
    }

    console.log(`[ifft]`);
    for (let i = 0; i < N; i++) {
      console.log([r[i * 2], r[i * 2 + 1]]);
    }
  }
  
  { // benchmark
    const N = 1024 * 1024;
    const fr0 = [...Array(N).keys()].map(i => Math.sin(i) * i);
    const f0 = fr0.map(n => [n, 0]);

    const BN = N * 2 * 8 * 3, fofs = 0, Fofs = N * 2 * 8, rofs = N * 4 * 8;
    while (memory.buffer.byteLength < BN) memory.grow(1);
    const f = new Float64Array(memory.buffer, fofs, N * 2);
    const F = new Float64Array(memory.buffer, Fofs, N * 2);
    const r = new Float64Array(memory.buffer, rofs, N * 2);
    fr0.forEach((v, i) => {
      [f[i * 2], f[i * 2 + 1]] = [v, 0.0];
    });

    console.time(`fft-ifft`);
    fft(N, fofs, Fofs);
    ifft(N, Fofs, rofs);
    console.timeEnd(`fft-ifft`);
  }
})().catch(console.error);

Vous pouvez mettre le fft.wasm généré et ce fft-bench-nodejs.js dans le même répertoire et l'exécuter avec node fft-bench-node.js.

Initialisation du module wasm généré par zig

Puisqu'il n'y a aucune fonction dans le runtime zig qui doit être ** importéede l'extérieur **, créez-la avec ʻawait WebAssembly.instantiate (buf, {}).

ʻExports contient __wasm_call_ctorsetmemmory en plus des fonctions ʻexport dans le code.

__wasm_call_ctors est une fonction, et si le code zig contient une initialisation, il sera initialisé en appelant ceci **__wasm_call_ctors ().

memory est un objet WebAssembly Memory initialement vide. Si vous souhaitez utiliser de la mémoire dans votre code, vous devez appeler la méthode memory.grow () pour allouer une zone mémoire. L'argument est le nombre de pages mémoire à développer, 64 Ko par page.

Mémoire dans WebAssembly

Memory.buffer dans ʻexports est un objet Typed Array ʻArrayBuffer. Du côté JavaScript, enveloppez ce memory.buffer avec Float64Array etc., entrez une valeur et faites-y référence.

Le ** pointeur est la valeur d'adresse **, et la valeur d'adresse mémoire WebAssembly utilise la valeur de décalage d'octet ** memory.buffer ** comme ʻArrayBuffer. Notez que zig a un alignement pour chaque nombre d'octets du type numérique. Pour f64, la valeur de l'adresse doit être un multiple de 8 et le deuxième argument du constructeur Float64Array` doit utiliser ce multiple de 8.

Référence: lors de la création d'un wasm qui accepte les «importations»

zig crée un wasm qui ne nécessite pas que le moteur d'exécution du langage soit fourni par un module externe, et il n'est pas impossible d '«importer» un module externe que vous avez préparé et de l'utiliser dans wasm.

Si vous voulez donner une fonction implémentée en externe à ʻimports et l'utiliser, vous pouvez déclarer un prototype de fonction ** ʻextern tel que ʻextern fn sin (x: f64) f64; et l'utiliser dans votre code **. Est possible. Dans zig-0.4.0, le nom du module externe dans le fichier wasm est fixé à " env "et peut être utilisé en le donnant commeWebAssembly.instantiate (buf, {env: {sin: Math.sin}})`. ..

Limite d'exportation de code pour Web Assembly

Il y a plus de restrictions sur les fonctions qui peuvent être «exportées» vers le côté JavaScript en tant que WebAssembly. Par exemple, dans zig-0.4.0, les ** fonctions ** avec une valeur de retour de type struct, telle que CN.rect (), sont soumises à cette limitation. Soyez prudent avec ʻexport dans votre code zig, car vous ne pourrez pas convertir en wasm s'il y a même un ʻexport pour cette limitation.

En guise de contre-mesure, il est possible de diviser le fichier zig du code d'implémentation uniquement pub dans le fichier zig uniquement @ import et d'ajouter l''export` approprié pour chaque cible. Je vais.

6. Faites de fft.zig un fichier objet et utilisez-le à partir du langage C

Le code zig peut être converti en un fichier objet disponible depuis C avec la commande zig build-obj. Un fichier d'en-tête C correspondant au fichier objet est également généré en même temps.

Ici, créez un programme en langage C qui utilise fft.zig, compilez-le et générez un fichier exécutable.

6.1 Génération de fichier objet et de fichier d'en-tête C

Générez le fichier objet fft.o et le fichier d'en-tête fft.h à partir de fft.zig avec la ligne de commande suivante:

$ zig build-obj --release-fast fft.zig

Le fichier d'en-tête généré fft.h a le contenu suivant.

fft.h


#ifndef FFT_H
#define FFT_H

#include <stdint.h>

#ifdef __cplusplus
#define FFT_EXTERN_C extern "C"
#else
#define FFT_EXTERN_C
#endif

#if defined(_WIN32)
#define FFT_EXPORT FFT_EXTERN_C __declspec(dllimport)
#else
#define FFT_EXPORT FFT_EXTERN_C __attribute__((visibility ("default")))
#endif

struct CN {
    double re;
    double im;
};

FFT_EXPORT void fft(uint32_t n, struct CN * f, struct CN * F);
FFT_EXPORT void ifft(uint32_t n, struct CN * F, struct CN * f);

#endif

Vous pouvez voir que ce fichier d'en-tête fft.h contient la fonction ʻexport et struct` dans le code zig.


6.2 Programme en langage C qui appelle l'implémentation FFT

Ce qui suit est le code C11 fft-bench-cc de la fonction main qui exécute et affiche FFT / IFFT avec le même exemple de données utilisé dans le test, puis exécute FFT / IFFT 10 millions d'éléments de données. est.

fft-bench-c.c


#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include "./fft.h"

int main() {
  {// example
    const int n = 16;
    const int v[16] = {1,3,4,2, 5,6,2,4, 0,1,3,4, 5,62,2,3};
    struct CN f[n], F[n], r[n];
    for (size_t i = 0; i < n; i++) f[i] = (struct CN) {.re = v[i], .im = 0.0};

    puts("[f]");
    for (size_t i = 0; i < n; i++) printf("%f + %fi\n", f[i].re, f[i].im);

    fft(n, f, F);
    ifft(n, F, r);

    puts("[F]");
    for (size_t i = 0; i < n; i++) printf("%f + %fi\n", F[i].re, F[i].im);
    puts("[r]");
    for (size_t i = 0; i < n; i++) printf("%f + %fi\n", r[i].re, r[i].im);
  }
  
  {// benchmark
    const int n = 1024 * 1024;
    struct CN * f = calloc(n, sizeof (struct CN));
    for (size_t i = 0; i < n; i++) {
      f[i] = (struct CN) {.re = sin(i) * i, .im = 0.0};
    }

    struct CN * F = calloc(n, sizeof (struct CN));
    struct CN * r = calloc(n, sizeof (struct CN));
    fft(n, f, F);
    ifft(n, F, r);
    free(f);
    free(F);
    free(r);
  }
  return 0;
}

Si vous utilisez des structures ou des fonctions simplement parce qu'elles sont implémentées dans zig, vous n'avez rien à faire de spécial du côté ** C **.

6.3. Construire

La ligne de commande pour générer le fichier exécutable fft-bench-c est la suivante (l'implémentation du compilateur C suppose clang ou gcc):

$ cc -Wall -Wextra -std=c11 -pedantic -O3 -o fft-bench-c fft-bench-c.c fft.o

(Cette option spécifie que le code doit être interprété par le standard C11 non étendu lui-même)

7. Lisez le fichier objet écrit en langage C à partir du programme zig

Contrairement au cas de fft-bench-c.c, il est également possible d'importer un fichier objet écrit en langage C avec le programme zig en utilisant le fichier d'en-tête C.

Ici, implémentez FFT avec le code C11 fft-c.c, et décrivez le fichier d'en-tête C fft-c.h pour utiliser l'implémentation de zig. Ensuite, avec fft-bench-cimport.zig, qui fait la même chose que fft-bench-zig.zig, lisez et implémentez fft-c.h.

7.1. Programme de mise en œuvre de la FFT par C11

Ce qui suit est l'implémentation FFT fft-c.c utilisant le nombre complexe double complexe dans la spécification C11.

fft-c.c


#include <complex.h> // complex, I, cexp
#include <math.h> // M_PI
#include <stdbool.h> // bool
#include <stddef.h> // size_t
#include <stdint.h> // uint64_t

typedef double complex CN;

static inline uint32_t reverse_bit(uint32_t s0) {
  uint32_t s1 = ((s0 & 0xaaaaaaaa) >> 1) | ((s0 & 0x55555555) << 1);
  uint32_t s2 = ((s1 & 0xcccccccc) >> 2) | ((s1 & 0x33333333) << 2);
  uint32_t s3 = ((s2 & 0xf0f0f0f0) >> 4) | ((s2 & 0x0f0f0f0f) << 4);
  uint32_t s4 = ((s3 & 0xff00ff00) >> 8) | ((s3 & 0x00ff00ff) << 8);
  return ((s4 & 0xffff0000) >> 16) | ((s4 & 0x0000ffff) << 16);
}

static inline uint32_t rev_bit(uint32_t k, uint32_t n) {
  return reverse_bit(n) >> (8 * sizeof (uint32_t) - k);
}

static inline uint32_t trailing_zeros(uint32_t n) {
  uint32_t k = 0, s = n;
  if (!(s & 0x0000ffff)) {k += 16; s >>= 16;}
  if (!(s & 0x000000ff)) {k += 8; s >>= 8;}
  if (!(s & 0x0000000f)) {k += 4; s >>= 4;}
  if (!(s & 0x00000003)) {k += 2; s >>= 2;}
  return (s & 1) ? k : k + 1;
}

static void fftc(double t, uint32_t N, const CN c[N], CN ret[N]) {
  uint32_t k = trailing_zeros(N);
  for (uint32_t i = 0; i < N; i++) ret[i] = c[rev_bit(k, i)];
  for (uint32_t Nh = 1; Nh < N; Nh <<= 1) {
    t *= 0.5;
    for (uint32_t s = 0; s < N; s += Nh << 1) {
      for (uint32_t i = 0; i < Nh; i++) { //NOTE: s-outside/i-inside is faster
        uint32_t li = s + i;
        uint32_t ri = li + Nh;
        CN re = ret[ri] * cexp(t * i * I);
        CN l = ret[li];
        ret[li] = l + re;
        ret[ri] = l - re;
      }
    }
  }
}

extern CN rect(double re, double im) {
  return re + im * I;
}
extern void fft(uint32_t N, const CN f[N], CN F[N]) {
  fftc(-2.0 * M_PI, N, f, F);
}
extern void ifft(uint32_t N, const CN F[N], CN f[N]) {
  fftc(2.0 * M_PI, N, F, f);
  for (size_t i = 0; i < N; i++) f[i] /= N;
}

L'algorithme est le même que «fft.zig».

De plus, puisque «double complexe» est une valeur dans laquelle la valeur «double» de la partie réelle et de la partie imaginaire sont disposées consécutivement, il a la même disposition de mémoire ** que le «CN» de ** «fft.zig». Nous fournissons également une fonction constructeur rect qui crée un nombre complexe à partir de la valeur double afin que vous n'ayez pas à opérer sur le double complexe sur zig.

7.2. Fichier d'en-tête C à utiliser avec zig

Le fichier d'en-tête C fft-c.h a un contenu qui correspond à la méthode du fichier d'en-tête écrit par build-obj de zig.

fft-c.h


#ifndef FFT_C_H
#define FFT_C_H
#include <stdint.h>

#ifdef __cplusplus
#define FFT_C_EXTERN_C extern "C"
#else
#define FFT_C_EXTERN_C
#endif

#if defined(_WIN32)
#define FFT_C_EXPORT FFT_C_EXTERN_C __declspec(dllimport)
#else
#define FFT_C_EXPORT FFT_C_EXTERN_C __attribute__((visibility ("default")))
#endif

struct CN {
    double re;
    double im;
};

FFT_C_EXPORT struct CN rect(double re, double im);
FFT_C_EXPORT void fft(uint32_t n, struct CN f[], struct CN F[]);
FFT_C_EXPORT void ifft(uint32_t n, struct CN F[], struct CN f[]);

#endif

Puisque zig n'a pas de type correspondant au type de nombre complexe, dans le fichier d'en-tête C lu par le programme zig, struct with double2 est utilisé à la place.

7.3. Programme Zig pour capturer les fichiers d'en-tête C

Semblable au précédent fft-bench-zig.zig, il s'agit d'un benchmark qui affiche la conversion FFT / IFFT avec les données d'exemple, puis affiche le temps d'exécution de FFT / IFFT de 1 million de données d'élément.

fft-bench-cimport.zig


const heap = @import("std").heap;
const warn = @import("std").debug.warn;
const sin = @import("std").math.sin;
const milliTimestamp = @import("std").os.time.milliTimestamp;

const libfft = @cImport({
    @cInclude("fft-c.h");
});
const CN = libfft.CN;
const rect = libfft.rect;
const fft = libfft.fft;
const ifft = libfft.ifft;

fn warnCNs(n: u32, cns: [*]CN) void {
    var i: u32 = 0;
    while (i < n) : (i += 1) {
        warn("{} + {}i\n", cns[i].re, cns[i].im);
    }
}

pub fn main() !void {
    { //example
        const n: u32 = 16;
        const v = [n]i32{ 1, 3, 4, 2, 5, 6, 2, 4, 0, 1, 3, 4, 5, 62, 2, 3 };
        var f: [n]CN = undefined;
        {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                f[i] = rect(@intToFloat(f64, v[i]), 0.0);
            }
        }
        warn("\n[f]\n");
        warnCNs(n, &f);

        var F: [n]CN = undefined;
        var r: [n]CN = undefined;
        fft(n, @ptrCast([*c]CN, &f), @ptrCast([*c]CN, &F));
        ifft(n, @ptrCast([*c]CN, &F), @ptrCast([*c]CN, &r));

        warn("\n[F]\n");
        warnCNs(n, &F);
        warn("\n[r]\n");
        warnCNs(n, &r);
    }
    
    { //benchmark
        //NOTE: allocators in std will be changed on 0.5.0
        var direct_allocator = heap.DirectAllocator.init();
        var arena = heap.ArenaAllocator.init(&direct_allocator.allocator);
        const allocator = &arena.allocator;
        defer direct_allocator.deinit();
        defer arena.deinit();

        const n: u32 = 1024 * 1024;
        var f: [*]CN = try allocator.create([n]CN);
        {
            var i: u32 = 0;
            while (i < n) : (i += 1) {
                const if64 = @intToFloat(f64, i);
                f[i] = rect(sin(if64) * if64, 0.0);
            }
        }

        var F: [*]CN = try allocator.create([n]CN);
        var r: [*]CN = try allocator.create([n]CN);

        const start = milliTimestamp();
        fft(n, f, F);
        ifft(n, F, r);
        const stop = milliTimestamp();
        warn("fft-ifft: {}ms\n", stop - start);
    }
}

@ cImport intégré

En utilisant ** @ cImport built-in **, il peut être importé en tant que structure zigou fonction à partir de l'en-tête C. Dans@ cImport, spécifiez le nom du fichier d'en-tête à interpréter par ** @ cInclude` intégré **.

Type de pointeur C

Le type pointeur de zig est divisé entre le type pointeur du type valeur et le type pointeur vers le tableau, mais le type pointeur de C ne fait pas la distinction entre eux. Par conséquent, si le type de pointeur est utilisé dans l'argument ou la valeur de retour de la fonction @ cImport, il est décrit par [* c] type de valeur qui peut correspondre à l'un ou l'autre type sur zig ** type de pointeur C * Interprété comme *.

Ici, la fonction fft de l'en-tête C est de type fft (n: i32, f: [* c] CN, F: [* c] CN).

Ce type de pointeur C [* c] CN est attribué à partir de * CN et de [*] CN sans conversion (code tardif). Cependant, dans zig-0.4.0, & f: * [16] CN, qui est assigné à [*] CN sans conversion, nécessite un cast explicite par @ ptrCast (première moitié du code).

-- * [16] CN = Aucune conversion requise => [*] CN = Aucune conversion requise => [* c] CN -- * [16] CN = Obligatoire @ ptrCast => [* c] CN

Référence: Comment utiliser un fichier objet sans @ cImport ʻextern fn`

Vous pouvez voir ce que vous faites avec @ cImport en exécutant la commande zig translate-c fft-c.h et en regardant le code zig pour cette sortie. Cependant, comme tout le contenu de la bibliothèque standard qui est «# include» est inclus, une grande quantité apparaîtra.

Si vous ne retirez que la partie nécessaire,

zig:fft-c.h.zig


pub const CN = extern struct {
    re: f64,
    im: f64,
};
pub extern "./fft-c.o" fn rect(re; f64, im: f64) CN;
pub extern "./fft-c.o" fn fft(n: u32, f: [*c]CN, F: [*c]CN) void;
pub extern "./fft-c.o" fn ifft(n: u32, F: [*c]CN, f: [*c]CN) void;

Ce sera le même contenu que. Si vous incorporez ce contenu, vous n'avez pas besoin de fft-c.h ou de l'option zig build`` -isystem . pour ajouter le chemin d'inclusion. De plus, si vous intégrez le chemin du fichier objet (. / Est obligatoire) immédiatement après ʻextern, --object fft-c.oest également inutile. (Si. / Est manquant, il essaiera de résoudre en tant que bibliothèque (" fft-c "ʻest traité comme -lfft-c).)

En zig, si vous avez un prototype de fonction «externe», il sera déclaré que ** utilise la fonction du même nom dans la bibliothèque ou le fichier objet **.

7.4. Construire

Vous pouvez le créer avec la ligne de commande suivante.

$ cc -Wall -Wextra -std=c11 -pedantic -O3 -c fft-c.c
$ zig build-exe --release-fast --object fft-c.o -isystem . fft-bench-cimport.zig

L'option --object fft-c.o spécifie le fichier objet, et -isystem . spécifie le répertoire pour lire le fichier d'en-tête avec @ cInclude.

Même dans ce cas, vous pouvez l'exécuter sans créer de fichier exécutable avec zig run --object fft.o -isystem .fft-bench-cimport.zig.

8. Construisez la description par build.zig

Il semble que zig vise à activer les builds multiplateformes avec la commande zig build sans utiliser un système de build externe tel que make.

Dans build.zig, qui décrit le contenu de la construction en exécutant zig build, décrivez la fonction ** pub`` build **.

En cela, le contenu à exécuter dans la construction est défini comme ** graphe d'étape **. Chaque compilation et exécution de commande est une étape. Pour chaque étape, vous pouvez spécifier les dépendances d'étape qui doivent être effectuées à l'avance.

Il existe également une ** étape nommée **, qui est la cible de construction, à laquelle vous pouvez ajouter tour à tour chaque étape de compilation et d'exécution de commande. Il y a une ** étape par défaut ** comme système de construction, et elle sert à décrire ce qui est exécuté par zig build sans spécifier le nom de l'étape. (Certaines des étapes nommées seront ajoutées aux étapes par défaut.)

Cependant, depuis zig-0.4.0, il semble que tout ce qui peut être décrit dans build.zig n'est pas complètement complet. Par exemple, pour compiler ou supprimer un fichier C, vous devez exécuter la commande dans l'environnement d'exécution directement avec un argument. (La documentation 0.4.0 a une étape appelée ʻaddCExecutable`, mais elle n'existe pas réellement dans zig-0.4.0)

build.zig


const builtin = @import("builtin");
const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    { //[case: WebAssembly wasm] build fft.wasm
        const wasmStep = b.step("fft.wasm", "build fft.wasm");
        const wasm = b.addExecutable("fft.wasm", "fft.zig");
        wasm.setTarget(builtin.Arch.wasm32, builtin.Os.freestanding, builtin.Abi.musl);
        wasm.setBuildMode(builtin.Mode.ReleaseFast);
        wasm.setOutputDir(".");
        wasmStep.dependOn(&wasm.step);
        b.default_step.dependOn(wasmStep);
    }
    { //[case: zig code only] build fft-bench-zig command
        const exeStep = b.step("fft-bench-zig", "build fft-bench-zig command");
        const zigExe = b.addExecutable("fft-bench-zig", "fft-bench-zig.zig");
        zigExe.setBuildMode(builtin.Mode.ReleaseFast);
        zigExe.setOutputDir(".");
        exeStep.dependOn(&zigExe.step);
        b.default_step.dependOn(exeStep);
    }
    { //[case: c exe with zig lib] build fft-bench-c command
        // build fft.h and fft.o
        const objStep = b.step("fft.o", "build fft.h and fft.o");
        const fftObj = b.addObject("fft", "fft.zig");
        fftObj.setBuildMode(builtin.Mode.ReleaseFast);
        fftObj.setOutputDir(".");
        objStep.dependOn(&fftObj.step);

        // build fft-bench-c command (cc: expected clang or gcc)
        const cExeStep = b.step("fft-bench-c", "build fft-bench-c command");
        cExeStep.dependOn(objStep);
        const cExe = b.addSystemCommand([][]const u8{
            "cc", "-Wall", "-Wextra", "-O3", "-o", "fft-bench-c", "fft-bench-c.c", "fft.o",
        });
        cExeStep.dependOn(&cExe.step);
        b.default_step.dependOn(cExeStep);
    }
    { //[case: zig exe with c lib] build fft-bench-cimport command
        const exeStep = b.step("fft-bench-cimport", "build fft-bench-cimport command");

        const cLibStep = b.step("fft-c", "build fft-c.o");
        const cLibObj = b.addSystemCommand([][]const u8{
            "cc", "-Wall", "-Wextra", "-std=c11", "-pedantic", "-O3", "-c", "fft-c.c",
        });
        cLibStep.dependOn(&cLibObj.step);
        exeStep.dependOn(cLibStep);

        const mainObj = b.addExecutable("fft-bench-cimport", "fft-bench-cimport.zig");
        mainObj.addIncludeDir("."); //NOTE: same as `-isystem .`
        mainObj.setBuildMode(builtin.Mode.ReleaseFast);
        mainObj.addObjectFile("fft-c.o");
        mainObj.setOutputDir(".");
        exeStep.dependOn(&mainObj.step);

        b.default_step.dependOn(exeStep);
    }
    
    { // clean and dist-clean
        const cleanStep = b.step("clean", "clean-up intermediate iles");
        const rm1 = b.addSystemCommand([][]const u8{
            "rm", "-f", "fft.wasm.o", "fft-bench-zig.o", "fft-bench-c.o", "fft-c.o", "fft-bench-cimport.o",
        });
        cleanStep.dependOn(&rm1.step);
        const rmDir = b.addRemoveDirTree("zig-cache");
        cleanStep.dependOn(&rmDir.step);

        const distCleanStep = b.step("dist-clean", "clean-up build generated files");
        distCleanStep.dependOn(cleanStep);
        const rm2 = b.addSystemCommand([][]const u8{
            "rm", "-f", "fft.wasm", "fft-bench-zig", "fft-bench-c", "fft.o", "fft.h", "fft-bench-cimport",
        });
        distCleanStep.dependOn(&rm2.step);
    }
}

En cela, nous faisons 6 étapes nommées. Du haut pour chaque bloc:

-- fft.wasm: wasm Étapes pour la génération de fichier --fft-bench-zig: étapes pour générer un fichier exécutable uniquement à partir du fichier zig --fft-bench-c: étapes pour faire d'un fichier zig un fichier objet et créer un fichier exécutable avec le fichier c qui l'utilise comme fichier principal --fft-bench-cimport: étapes pour faire d'un fichier c un fichier objet et créer un fichier exécutable avec le fichier zig qui l'utilise comme fichier principal --clean et dist-clean: étapes pour supprimer les fichiers générés par la construction

À l'exception de «clean» et «dist-clean», ils ont été ajoutés aux étapes par défaut.

Vous pouvez voir quelles étapes nommées sont disponibles avec zig build --help.

Je pense que ce sera un grand changement dans la future mise à niveau de la version, mais cela peut être une référence pour savoir comment le configurer en tant que description de construction dans chacun des quatre cas à construire.

9. Résultats de référence

Voici le résultat du benchmark du dernier fichier exécutable généré. C'est le temps nécessaire pour FFT / IFFT de 1 million d'éléments dans la seconde moitié.

command time (ms) runtime
node fft-bench-nodejs.js 945.458ms node-12.6.0
./fft-bench-zig 856ms zig-0.4.0
./fft-bench-cimport 802ms Apple LLVM version 10.0.1 (clang-1001.0.46.4)

De plus, pour référence dans wasm, le benchmark FFT / IFFT avec le même contenu effectué dans "Circonstances de l'assemblage Web en 2019" a été exécuté dans le même environnement. Les résultats sont également répertoriés.

--Source: https://gist.github.com/bellbind/36b7e71b3203e868c10405a61cdd2d09

command time (ms) runtime
node call-js-fft.js 1074.170ms node-12.6.0
node --experimental-modules call-fft-dynamic.js (fast) 1117.932ms node-12.6.0
node --experimental-modules call-fft-dynamic.js (slow) 2177.661ms node-12.6.0

Le fait qu'il ne soit pas nécessaire d'appeler sin ou cos par ʻimport` lors de l'incorporation d'une fonction mathématique avec zig peut être important en termes de vitesse d'exécution même avec l'ajout de l'optimisation mathématique intrinsèque v8 ( Dans le wasm basé sur zig, les fonctions sin et cos elles-mêmes étaient incorporées).

Recommended Posts

Résumé du langage de programmation open source zig
Langage de programmation recommandé
Classement des langages de programmation populaires
Classement des langages de programmation populaires
Résumé des bases du langage Java
[Langue de programmation] Liste des noms d'utilisateurs
À propos du langage de programmation Crystal