2016-12-18 3 views
2

J'ai une routine C simple qui prend quatre mots et retourne quatre mots, et pour lequel gcc peut optimiser et émettre des primops que GHC ne supporte pas. J'essaie de comparer différentes façons d'appeler cette procédure, et j'ai du mal à essayer d'adapter la technique described here pour utiliser foreign import prim.Utilisation de `import import prim` avec une fonction C utilisant la convention d'appel STG

Ce qui suit est juste pour ajouter 1 à chaque mot d'entrée, mais segfaults.

Main.hs:

{-# LANGUAGE GHCForeignImportPrim #-} 
{-# LANGUAGE ForeignFunctionInterface #-} 
{-# LANGUAGE MagicHash #-} 
{-# LANGUAGE UnboxedTuples #-} 
{-# LANGUAGE UnliftedFFITypes #-} 
import Foreign.C 
import GHC.Prim 
import GHC.Int 
import GHC.Word 

foreign import prim "sipRound" 
    sipRound_c# :: Word# -> Word# -> Word# -> Word# -> (# Word#, Word#, Word#, Word# #) 

sipRound_c :: Word64 -> Word64 -> Word64 -> Word64 -> (Word64, Word64, Word64, Word64) 
sipRound_c (W64# v0) (W64# v1) (W64# v2) (W64# v3) = case sipRound_c# v0 v1 v2 v3 of 
    (# v0', v1', v2', v3' #) -> (W64# v0', W64# v1', W64# v2', W64# v3') 

main = do 
    print $ sipRound_c 1 2 3 4 

sip.c:

#include <stdlib.h> 
#include <stdint.h> 
#include <stdbool.h> 



// define a function pointer type that matches the STG calling convention 
typedef void (*HsCall)(int64_t*, int64_t*, int64_t*, int64_t, int64_t, int64_t, int64_t, 
         int64_t, int64_t, int64_t*, float, float, float, float, double, double); 

extern void 
sipRound(
    int64_t* restrict baseReg, 
    int64_t* restrict sp, 
    int64_t* restrict hp, 

    uint64_t v0, // R1 
    uint64_t v1, // R2 
    uint64_t v2, // R3 
    uint64_t v3, // R4 
    int64_t r5, 
    int64_t r6, 

    int64_t* restrict spLim, 
    float f1, 
    float f2, 
    float f3, 
    float f4, 
    double d1, 
    double d2) 
{ 

    v0 += 1; 
    v1 += 1; 
    v2 += 1; 
    v3 += 1; 

    // create undefined variables, clang will emit these as a llvm undef literal 
    const int64_t iUndef; 
    const float fUndef; 
    const double dUndef; 

    const HsCall fun = (HsCall)sp[0]; 
    return fun(
      baseReg, 
      sp, 
      hp, 

      v0, 
      v1, 
      v2, 
      v3, 
      iUndef, 
      iUndef, 

      spLim, 
      fUndef, 
      fUndef, 
      fUndef, 
      fUndef, 
      dUndef, 
      dUndef); 
} 

Je ne sais pas vraiment ce que je fais. Existe-t-il un moyen d'adapter la technique à partir de ce blog? Et est-ce une mauvaise idée?

+1

Ceci est très, très bas niveau. Avez-vous vraiment besoin de ce niveau de performance? AFAICS, l'article de blog génère LLVM en utilisant clang, puis corrige la sortie en changeant la convention d'appel C en LLVM cc10 (le GHC), puis compile le résultat avec llc. Effrayant. Ceci est bien au-delà de la zone de confort (c'est-à-dire que j'ai très peu de connaissances sur ce qui se passe à ce niveau), mais l'utilisation de la convention d'appel cc10 semble être cruciale! – chi

+0

@chi Je suis en train de faire un benchmarking en retournant une structure de 4 mots d'un ccall étranger normal, mais je m'attends à ce que les frais généraux soient trop importants pour en valoir la peine (mais ils pourraient être surpris); Je passe par tout cela pour essayer d'obtenir des instructions de rotation générées en n'utilisant pas le backend LLVM, pour une bibliothèque sur laquelle je travaille. Mais c'est aussi par curiosité – jberryman

+2

Droit, cela ne fonctionnera pas. Comme le dit l'article du blog: "C'est toujours une fonction ccall mais nous y remédierons plus tard, il n'y a actuellement aucun moyen de définir ceci comme cc10 (nom interne de LLVM pour la convention d'appel de GHC) dans clang." La convention d'appel C est différente de celle de GHC. Par exemple, C pense que le premier argument 'baseReg' devrait être dans' rdi' (en supposant que x86_64) mais que GHC passe 'baseReg' dans' r13'. –

Répondre

3

Si vous êtes prêt à écrire à la main, vous pouvez le faire comme ceci (pour x86_64). Mettez ceci dans un fichier avec une extension .s et fournissez-le comme argument sur la ligne de commande ghc.

.global sipRound 
sipRound: 
    inc %rbx 
    inc %r14 
    inc %rsi 
    inc %rdi 
    jmp *(%rbp) 

La mise en correspondance entre les registres du STG et les registres de la machine est défini dans https://github.com/ghc/ghc/blob/master/includes/stg/MachRegs.h#L159. Notez qu'il y aura toujours un appel de fonction impliqué, donc il ne sera pas aussi efficace que le code que vous obtenez de LLVM.

+0

Je comprends mieux maintenant, merci! Deux suivis rapides: pour exposer cela à ghc, devons-nous utiliser inline asm dans un fichier ac, comme 'void sipRound() {asm (...)}' ou y a-t-il une meilleure façon de le faire (et c'est pourquoi dire "il y aura un appel de fonction impliqué"?)? Et puis-je considérer cela comme une API stable? Il semble que ce mappage n'a pas beaucoup changé et doit rester stable pour être coordonné avec llvm etc. – jberryman

+0

Vous ne pouvez pas utiliser 'void sipRound() {asm (...)}' car cela ajoutera une fonction C prologue. Fournissez simplement l'entrée de l'assembleur comme n'importe quelle autre entrée. L'appel de fonction dont je parle est l'appel que vous écrivez à 'sipRound_C#'. Le fait est que GHC ne peut pas "aligner" 'sipRound', puisqu'il est implémenté dans l'assemblage. (Au contraire, lorsque vous appelez '+ #', GHC ne génère pas d'appel à la fonction de niveau supérieur, mais émet une instruction add.) –

+1

L'ABI ne change pas beaucoup mais n'est pas garanti stable; il n'y a pas de compatibilité ABI entre les différentes versions de GHC et chaque version de GHC ne fonctionne qu'avec une seule version de LLVM, il ne serait donc pas difficile d'obtenir la convention d'appel modifiée dans la prochaine version de LLVM. –