動機
原始的なアセンブラしか無い原始的な CPU のプログラミングをすることになった.でアセンブリプログラミングの一番の萎えポイントとして,例えば C 言語だと
if(a == b){
  c = d;
}else{
  e = f;
}
  cmp a, b
  jnz label0
  mov c, d
  jmp label1
label0:
  mov e, f
label1:
例えば,C++ で "r0 = r1;" と書いてコンパイル・実行すれば,"mov r0, r1" というテキストが得られる,みたいなイメージ.
まずは代入演算
//////////////////////////////////////////////////////////////////////////////
// Register
class RegisterObject {
public:
  RegisterObject(const char* szName) : m_szName(szName){}
  const char *Name(void) const {return m_szName;}
  
private:
  const char* m_szName;
};
class GpReg : public RegisterObject {
public:
  GpReg(const char* szName) : RegisterObject(szName){}
  
  GpReg& operator=(const GpReg& src){
    printf("\tmov\t%s, %s\n", Name(), src.Name());
    return *this;
  }
};
//////////////////////////////////////////////////////////////////////////////
// Register インスタンス
GpReg r0("r0");
GpReg r1("r1");
GpReg r2("r2");
GpReg r3("r3");
//////////////////////////////////////////////////////////////////////////////
// アセンブリプログラム
int main(int argc, char **argv){
  r0 = r1 = r2;
  return 0;
}
代入演算のキモは言うまでもなく "operator=" で,= が呼ばれたら mov 命令のテキストを出力する.
で実行結果:
        mov     r1, r2
        mov     r0, r1
比較演算
次に,if-else-endif の構造化をやる前に比較演算子を定義する.対象 CPU は,== なら cmpeq みたいに比較演算子毎に比較命令があり,その結果をフラグレジスタ f0 にセットする.条件分岐命令は f0 の値をみて分岐するかどうか決める.
_if ではフラグレジスタを受取り,必要な分岐命令を生成する.また if-else-endif はネストするので,分岐先ラベルの情報はスタックに push / pop する必要がある.class FlagReg : public RegisterObject {
public:
  FlagReg(const char* szName) : RegisterObject(szName){}
};
//////////////////////////////////////////////////////////////////////////////
// Register インスタンス
FlagReg f0("f0");
//////////////////////////////////////////////////////////////////////////////
// global な operator
FlagReg& operator==(const GpReg& a, const GpReg& b){
  printf("\tcmpeq\t%s, %s, f0\n", a.Name(), b.Name());
  return f0;
}
//////////////////////////////////////////////////////////////////////////////
// アセンブリプログラム
int main(int argc, char **argv){
  r0 == r1;
  return 0;
}
で実行結果:
        cmpeq   r0, r1, f0
if-else-endif
そしてこの取り組みの一番の目的である,if-else-endif の構造化をやってみる.
//////////////////////////////////////////////////////////////////////////////
// 構造化構文
int g_LabelCnt = 0;
std::vector<int> g_Label;
void _if(FlagReg& f){
  printf("\tjnset\t%s, _L%d\n", f.Name(), g_LabelCnt);
  g_Label.push_back(g_LabelCnt);
  ++g_LabelCnt;
}
void _else(void){
  printf("\tjmp\t_L%d\n", g_LabelCnt);
  printf("_L%d:\n", g_Label[g_Label.size() - 1]);
  
  g_Label.pop_back();
  g_Label.push_back(g_LabelCnt);
  ++g_LabelCnt;
}
void _endif(void){
  printf("_L%d:\n", g_Label[g_Label.size() - 1]);
  g_Label.pop_back();
}
//////////////////////////////////////////////////////////////////////////////
// アセンブリプログラム
int main(int argc, char **argv){
  _if(r0 == r1);
    r0 = r2;
  _else();
    r1 = r3;
  _endif();
  return 0;
}
で実行結果:
        cmpeq   r0, r1, f0
        jnset   f0, _L0
        mov     r0, r2
        jmp     _L1
_L0:
        mov     r1, r3
_L1:
コード:
//////////////////////////////////////////////////////////////////////////////
// アセンブリプログラム
int main(int argc, char **argv){
  _if(r0 == r1);
    _if(r1 == r2);
      r2 = r0;
    _else();
      r3 = r1;
    _endif();
  _else();
    r1 = r3;
  _endif();
  return 0;
}
        cmpeq   r0, r1, f0
        jnset   f0, _L0
        cmpeq   r1, r2, f0
        jnset   f0, _L1
        mov     r2, r0
        jmp     _L2 ←※ここ
_L1:
        mov     r3, r1
_L2:
        jmp     _L3
_L0:
        mov     r1, r3
_L3:
この最適化をやるためには,直接アセンブリテキストを出力するのではなく,一旦中間言語とかでメモリ上に溜めておき,最後に最適化フェーズを流す,等しないといけないということがわかった.
それを解決して,あとはメモリアクセスとかラベルへのサブルーチンコールとかを実装すれば,普通に使えそう.
それを解決して,あとはメモリアクセスとかラベルへのサブルーチンコールとかを実装すれば,普通に使えそう.
 
0 件のコメント:
コメントを投稿