デジタルちんぶろぐ

デジタルな話題

ホーム 連絡をする 同期する ( RSS 2.0 ) Login
投稿数  268  : 記事  0  : コメント  4382  : トラックバック  79

ニュース


技術以外は
ちんぶろぐ

記事カテゴリ

書庫

日記カテゴリ

10分で終わるだろうと思っていたセッション内容も予想を反して1時間も喋ってしまい後片付けを急がせる結果になってしまい申し訳なかったです。

中々思う通りにはいきませんね。

 

メインセッションで出したプログラムはここここの内容をもっと簡略化したものです。

ただ、僕個人の意見としては

「CPUがどうやって動くの?

とか

「CPUはこんな命令があるんだ」

ということを知っていれば良くて、必要性が無ければアセンブラを覚える必要は無いと思ってます。

 

そして、数名の方からご質問いただいた気持ち的なメインセッション。CとC++で同じサイズのコードが出た部分についての解説です。

以降使用しているコンパイラはgcc3.4.4でコンパイルオプションは-O2 -mh -mint32です。

前述とは裏腹にアセンブラのコードを出していますが、行数が多いってことは出力コードがデカくて遅い程度の認識で良いです。

 

最初にCのプログラムです。

C (元のプログラム)

アセンブラ

typedef unsigned char    byte;
typedef unsigned short    word;
typedef unsigned long    dword;

#define PADDR 0xffffd1
#define PADR  0xffffd3
#define PBDDR 0xffffd4
#define PBDR  0xffffd6

int main(void)
{
    byte data;
    volatile byte* paddr = (byte*) PADDR;
    volatile byte* padr = (byte*) PADR;
    volatile byte* pbddr = (byte*) PBDDR;
    volatile byte* pbdr = (byte*) PBDR;

    *padr = 0x00;
    *paddr = 0x00;

    *pbdr = 0x00;
    *pbddr = 0x0f;

    while(1) {
        *pbdr = *padr;
    };

    return 0;
}

    .file    "main.c"
    .h8300h
    .section .text
    .align 1
    .global _main
_main:
    mov.l    er6,@-er7
    mov.l    er7,er6
    sub.b    r2l,r2l // 0を作ってる
    mov.b    r2l,@16777171:8
// *padr = 0x00;
    mov.b    r2l,@16777169:8
// *paddr=0x00;
    mov.b    r2l,@16777174:8 // *pbdr = 0x00;
    mov.b    #15,r2l
    mov.b    r2l,@16777172:8
// *pbddr = 0x0f;

.L2:
// while(1) {
    mov.b    @16777171:8,r2l
    mov.b    r2l,@16777174:8
// 上の行と合わせて*pbdr = *padr;
    bra    .L2
// }

    .end
    .ident    "GCC: (GNU) 3.4.4"

これが元のCのプログラムです。元のソースが短いこともありいいコード出してくれてますね。

 

素直にクラス化してみます。DDRは最初に設定したらその後の書き換えはする必要がないので、コンストラクタのパラメータにして、データレジスタへの書き込みは*オペレータをオーバーロードしてクラスオブジェクトがポートであるように扱えるようにしてみます。

C++

アセンブラ

// 型定義とマクロ割愛

class pio {
private:
    volatile byte* ddr;
    volatile byte* dr;
public:
    pio(dword ddr_val, dword dr_val, byte dir, byte init = 0) :
        ddr(reinterpret_cast<volatile byte*>(ddr_val)),
        dr(reinterpret_cast<volatile byte*>(dr_val)) {
        *dr = init;
        *ddr = dir;
    }
    volatile byte& operator *() { return *dr; }
};

int main(void)
{
    pio pa(PADDR, PADR, 0x00);
    pio pb(PBDDR, PBDR, 0x0f);

    while(1) {
        *pb = *pa;
    };

    return 0;
}

    .file    "main.cpp"
    .h8300h
    .section .text
    .align 1
    .global _main
_main:
    mov.l    er6,@-er7
    mov.l    er7,er6
    add.l    #-16,er7
    mov.l    er6,er0
    subs    #4,er0
    subs    #4,er0
    sub.b    r3l,r3l
    mov.l    #16777169,er2
    mov.l    er2,@er0
    adds    #2,er2
    mov.l    er2,@(-4,er6)
    mov.b    r3l,@16777171:8
    mov.l    @er0,er2
    mov.b    r3l,@er2
    subs    #4,er0
    subs    #4,er0
    mov.l    #16777172,er2
    mov.l    er2,@er0
    adds    #2,er2
    mov.l    er2,@(-12,er6)
    mov.b    r3l,@16777174:8
    mov.l    @er0,er3
    mov.b    #15,r2l
    mov.b    r2l,@er3
.L4:
    mov.l    @(-4,er6),er2
    mov.b    @er2,r3l
    mov.l    @(-12,er6),er2
    mov.b    r3l,@er2
    bra    .L4
    .end
    .ident    "GCC: (GNU) 3.4.4"

コンストラクタ・オペレータの部分がインライン展開されているところはいいのですが、DR/DDRのアドレスをクラスメンバで持っており、それらのオブジェクトの初期化コード、及びDRのアクセスをする際にクラスメンバであるアドレスを一旦er2に入れてからアクセスしていることで長いコードになっています。

但し、このコードの場合最適化によってはCと同等のコードを出す可能性はあります。

 

テンプレートを使ってCと同等なコードを出すように細工してみました。

(中さんから「何回書き直しました?」と聞かれたとき「鋭い!」と思った)

C++

アセンブラ

// 型定義とマクロ割愛

struct input {
    enum { VAL = 0 };
};

struct output {
    enum { VAL = 1 };
};

template<dword DDR, dword DR,
         typename d0 = input, typename d1 = input,
         typename d2 = input, typename d3 = input,
         typename d4 = input, typename d5 = input,
         typename d6 = input, typename d7 = input>
class pio {
private:
    enum { DIR = d7::VAL << 7 | d6::VAL << 6 | d5::VAL << 5 | d4::VAL << 4 |
           d3::VAL << 3 | d2::VAL << 2 | d1::VAL << 1 | d0::VAL };
public:
    pio(byte init = 0) {
        *reinterpret_cast<volatile byte*>(DR)  = init;
        *reinterpret_cast<volatile byte*>(DDR) = static_cast<byte>(DIR);
    }
    volatile byte& operator *() { return *reinterpret_cast<volatile byte*>(DR); }
};

int main(void)
{
    pio<PADDR, PADR> pa;
    pio<PBDDR, PBDR, output, output, output, output> pb;

    while(1) {
        *pb = *pa;
    };

    return 0;
}

    .file    "main.cpp"
    .h8300h
    .section .text
    .align 1
    .global _main
_main:
    mov.l    er6,@-er7
    mov.l    er7,er6
    sub.b    r2l,r2l
    mov.b    r2l,@16777171:8
    mov.b    r2l,@16777169:8
    mov.b    r2l,@16777174:8
    mov.b    #15,r2l
    mov.b    r2l,@16777172:8
.L4:
    mov.b    @16777171:8,r2l
    mov.b    r2l,@16777174:8
    bra    .L4
    .end
    .ident    "GCC: (GNU) 3.4.4"

C++のコードは長くなってしまいましたが、利点はDDR初期設定値をinput/outputという型で指定できるので初期値がわかりやすくなること。

書いていて気付いたんですが、出力ビットのビット位置を指定するようにした方が良かったですね。

 

普段C++でtemplateを使っている方なら既に気付かれることと思いますが、見ての通りマクロに毛が生えた程度のコードです。

例えば

*pb=*pa;

の部分は

*reinterpret_cast<volatile byte*>(PBDR) = *reinterpret_cast<volatile byte*>(PADR);

と同じです。

つまり、

*((volatile byte*) PBDR) = *((volatile byte*) PADR);

と同じですね。

さて、元となっているCのソースでは変数宣言

    volatile byte* padr = (byte*) PADR;
    volatile byte* pbdr = (byte*) PBDR;

をしているにも関わらず

    *pbdr = *padr;
部分がレジスタやローカルフレームによる変数領域を使用せず

    mov.b    @16777171:8,r2l
    mov.b    r2l,@16777174:8

と最適化されて出力されています。これはCで書けば

*((byte*) PBDR) = *((byte*) PADR);

になるわけですね。これは先ほどテンプレートを展開したコードと同等です。

C++のtemplate版の方は上記のとおり、変数を使用せずアドレスを指す整数値をポインタへキャストしてアクセスしていますから。

このような理由でCとC++のコードのコンパイル結果が同じものになっています。

もし、Cのソースの方のコンパイル結果が

    mov.l    #16777171,er2
    mov.l    #16777174,er3
.L2:
   
mov.b   @er2,@er3
    bra   .L2

となっていたら同等な結果は得られなかったかもしれません。

 

# templateにはtemplateなりの問題もありますが、少々姑息と思いながらも今までC++は遅くなる、大きくなるからダメと言われてずーっとC++を使えなかったから、そのはけ口として出してしまいました^^;

 

そして、どうしても言えなかったことですが、-O2付けないとtemplate版でもC++の方がデカくなりますw

そして、パワポ資料はここ

投稿日時 : 2008年7月29日 0:54

コメント

# わんくま名古屋#3アンケート結果 2008/08/01 21:36 The beast of halfpace
わんくま名古屋#3アンケート結果

Post Feedback

タイトル
名前
Url:
コメント: