2

C コードとアセンブリの両方からアクセスされるヘッダー ファイルを作成しています。このため、アセンブリ コードは C プリプロセッサで前処理されます。

inline問題は、これらのヘッダー ファイルに多くの関数があることです。アセンブラは、オブジェクト ファイル内のシンボルではない関数 (static inline関数と同様) を処理できないため、使用できません。私はこれこの非常に貴重な投稿を読み、今までにexternstaticを組み合わせて使用​​する方法を理解しましたが、関数をC コードとアセンブリの両方でアクセスできるinlineようにする方法がわかりません。inline

私の現在のアプローチは、ヘッダー ファイルにinline関数を記述し (>= GNU99 を使用し-O3、関数をインライン化し、明示的に定義する必要がある関数の外部定義を呼び出します)、実装ファイルに外部定義を記述することです。C コードには、 でコンパイルされるヘッダー ファイル (inline関数)が含まれている-O3ため、インライン バージョンが使用されます。アセンブリ コードは外部定義を使用します。

質問:

  1. アセンブリ コードは関数を呼び出すことしかできず、インライン化は現在不可能です。アセンブリ コードはインライン化を利用できますか? .Sつまり、インライン アセンブリではなく、ファイル内のようです。

  2. extern inline私の現在の方法と同様に良いですが、それは1つの定義に要約されるため(外部定義は自動的に発行されます)、ヘッダーとソースファイルに分割できません。これは、Cコード(ヘッダー)にアクセスできるようにするために重要です。アセンブリ (ソース)。

  3. 私がやろうとしていることを達成するためのより良い方法はありますか?

4

2 に答える 2

3

callほとんどのレジスタが破壊されていると仮定することを強制することのオーバーヘッドはかなり高くなります。高いパフォーマンスを得るには、すべてを完全に最適化できるように、関数を手動で asm にインライン化する必要があります

コンパイラにスタンドアロンの定義を発行させ、それを呼び出すことは、パフォーマンスが重要ではないコードに対してのみ考慮する必要があります。あなたは asm で何を書いているのか、またはその理由を言いませんでしたが、パフォーマンスが重要であると想定しています。それ以外の場合は、C で記述します (特別な命令にはインライン asm を使用しますか?)。

手動でインライン化するのではなく、これらの小さなインライン C 関数をループ内で使用する場合は、すべてを C で記述した方がパフォーマンスが向上する可能性があります。これにより、コンパイラはより多くのコードを最適化できます。 .

x86-64 で使用される register-arg 呼び出し規則は優れていますが、call-clobber されたレジスタが多数あるため、計算中に呼び出しを行うと、できるだけ多くのデータをレジスタに保持できなくなります。


アセンブリ コードはインライン化を利用できますか? つまり、インライン アセンブリではなく、.S ファイルのようです。

いいえ、inline-asm の逆の構文はありません。もしあれば、それは次のようなものです: 入力が入っているレジスター、出力を入れたいレジスター、および上書きできるレジスターをコンパイラーに伝えます。

手書きの asm とコンパイラ出力の間の共通部分式の除去やその他の重要な最適化は、手書きの asm を本当に理解するコンパイラ、またはそれをソース コードとして扱い、最適化されたバージョンの全部。

コンパイラ出力を asm に最適にインライン化するには、通常、asm を調整する必要があります。これが、それを行うプログラムがない理由です。


私がやろうとしていることを達成するためのより良い方法はありますか?

コメントであなたの目標が何であるかを説明したので、その逆ではなく、使用したい特別な命令のために C で小さなラッパーを作成します。

#include <stdint.h>
struct __attribute__((packed)) lgdt_arg {
    uint16_t limit;
    void * base;    // FIXME: always 64bit in long mode, including the x32 ABI where pointers and uintptr_t are 32bit.
                    // In 16bit mode, base is 24bit (not 32), so I guess be careful with that too
                    // you could just make this a uint64_t, since x86 is little-endian.
                    //  The trailing bytes don't matter since the instruction just uses a pointer to the struct.
};

inline void lgdt (const struct lgdt_arg *p) {
    asm volatile ("lgdt %0" : : "m"(*p) : "memory");
}

// Or this kind of construct sometimes gets used to make doubly sure compile-time reordering doesn't happen:
inline void lgdt_v2 (struct lgdt_arg *p) {
    asm volatile ("lgdt %0" : "+m"(*(volatile struct lgdt_arg *)p) :: "memory");
}
// that puts the asm statement into the dependency chain of things affecting the contents of the pointed-to struct, so the compiler is forced to order it correctly.


void set_gdt(unsigned size, char *table) {
  struct lgdt_arg tmp = { size, table };
  lgdt (&tmp);
}

set_gdt -O3( godbolt のgcc 5.3) にコンパイルします

    movw    %di, -24(%rsp)
    movq    %rsi, -22(%rsp)
    lgdt -24(%rsp)
    ret

を含むコードを書いたことはありませんlgdt。コンパイル時にロード/ストアが並べ替えられないようにするために、私が行ったように「メモリ」クロバーを使用することをお勧めします。これにより、それが指している GDT が実行前に完全に初期化されていることが確認されますLGDT。( も同様LIDT)。コンパイラはインライン asm に GDT への参照を与えることに気づき、baseその内容が同期していることを確認するかもしれませんが、私にはよくわかりません。ここで「メモリ」クロバーを使用するだけのマイナス面はほとんどまたはまったくないはずです。

Linux (カーネル) は、この種のラッパーをあらゆる場所で 1 つか 2 つの命令に使用し、asm でできるだけ少ないコードを記述します。必要に応じて、インスピレーションを探してください。


re: あなたのコメント: はい、ブート セクタを asm で書きたいと思うでしょう。gcc の -m16 コードはばかげているので (基本的には 32 ビット コードです)、他の 16 ビット コードも書きたいと思うでしょう。

いいえ、C コンパイラの出力を手動で asm にインライン化する方法はありません。同じ理由で、アセンブリを最適化するプログラムはありません。(つまり、asm ソースの読み取り、最適化、別の asm ソースの書き込み)。

そのようなプログラムが何をしなければならないかを考えてみてください:手書きの asm を壊さずに何を変更できるかを知るには、手書きの asm を理解する必要があります。ソース言語としての Asm は、オプティマイザーに多くの作業を提供しません。

于 2016-03-29T13:14:59.207 に答える
2

リンク先の回答では、C99 インライン関数がどのように機能するかを説明していますが、定義が風変わりな理由は説明していません。関連する標準パラグラフは、ISO 9899:2011 §6.7.4 ¶6-7 (ISO 9899:1999 同上) です。

6inline関数指定子で宣言された関数はインライン関数です。関数をインライン関数にすることは、関数の呼び出しが可能な限り高速であることを示唆しています。138)そのような提案が有効である範囲は、実装によって定義されます。139)

7 内部リンケージを持つ関数はすべてインライン関数にすることができます。外部リンケージを持つ関数の場合、次の制限が適用されます。関数がinline関数指定子で宣言されている場合、同じ翻訳単位でも定義される必要があります。翻訳単位内の関数のすべてのファイル スコープ宣言にinlineなしの関数指定子が含まれている場合extern、その翻訳単位内の定義はインライン定義です。. インライン定義は関数の外部定義を提供せず、別の翻訳単位での外部定義を禁止しません。インライン定義は、翻訳者が同じ翻訳単位内の関数への呼び出しを実装するために使用できる外部定義の代替手段を提供します。関数の呼び出しがインライン定義を使用するか、外部定義を使用するかは指定されていません。140)


138) たとえば、「インライン置換」など、通常の関数呼び出しメカニズムの代替手段を使用する。インライン置換はテキスト置換ではなく、新しい関数も作成しません。したがって、たとえば、関数の本体内で使用されるマクロの展開では、関数が呼び出される場所ではなく、関数の本体が表示される時点での定義が使用されます。識別子は、本体が発生するスコープ内の宣言を参照します。同様に、関数は、外部定義に加えて発生するインライン定義の数に関係なく、1 つのアドレスを持ちます。

139) たとえば、実装はインライン置換をまったく実行しないか、inline宣言のスコープ内の呼び出しに対してインライン置換のみを実行する可能性があります。

140) インライン定義は、対応する外部定義および他の翻訳単位の他の対応するインライン定義とは異なるため、静的ストレージ期間を持つすべての対応するオブジェクトも、それぞれの定義で異なります。

の定義はどのように機能しinlineますか? inline関数の宣言のみ(externまたはなしstatic) が翻訳単位に存在する場合、関数のコードは発行されません。inlineただし、インライン関数として定義されている場合でも、なしまたは付きの単一の宣言externが存在する場合は、関数のコードが発行されます。この設計の側面により、実装を複製することなく、インライン関数のマシン コードを含むモジュールを記述できます。

ヘッダー ファイルに、インライン定義を配置します。

fast_things.h

/* TODO: add assembly implementation */
inline int fast_add(int a, int b)
{
    return (a + b);
}

inline int fast_mul(int a, int b)
{
    return (a * b);
}

このヘッダーは、すべての変換モジュールに含めることができ、 および のインライン定義を提供しfast_addますfast_mul。これら 2 つのマシン コードを生成するには、次のファイルを追加します。

fast_things.c

#include "fast_things.h"
extern inline int fast_add(int, int);
extern inline int fast_mul(int, int);

いくつかのマクロ マジックを使用して、これらすべてを入力することを避けることができます。次のように変更fast_things.hします。

#ifndef EXTERN_INLINE
#define EXTERN_INLINE_UNDEFINED
#define EXTERN_INLINE inline
#endif
EXTERN_INLINE int fast_add(int a, int b)
{
    return (a + b);
}

EXTERN_INLINE int fast_mul(int a, int b)
{
    return (a * b);
}
#ifdef EXTERN_INLINE_UNDEFINED
#undef EXTERN_INLINE
#undef EXTERN_INLINE_UNDEFINED
#endif

次にfast_things.c、単に次のようになります。

#define EXTERN_INLINE extern inline
#include "fast_things.h"

コードはインライン関数に対して発行されるため、アセンブリから問題なく呼び出すことができます。ただし、アセンブラは C を話さないため、アセンブリでそれらをインライン化することはできません。

static inline常にインライン化されていることを合理的に確認できる場合、目的により適した関数 (つまり、小さなヘルパー関数) もあります。

GNU アセンブラは、カスタム マクロ言語でマクロをサポートします。1 つの可能性は、インライン アセンブリを受け取り、C およびガス マクロ用の gcc スタイルのインライン アセンブリの両方を発行するカスタム プリプロセッサを作成することです。これは、sed、m4、または awk (難易度の高い順) で可能です。#これには、C プリプロセッサの stringify( ) 演算子を悪用することもできます。具体例を教えていただければ、何かをまとめてみることができます。

于 2016-03-29T21:24:55.790 に答える