ワンライナーでdotfilesをインストールする

ワンライナーでdotfilesを設定するとはどういうことなのか簡単に説明すると

bash -c "$(curl -fsSL https://raw.githubusercontent.com/kisqragi/dotfiles/master/install.sh)"

上記のコマンドを実行するだけで.vimrcや.bashrcなどの設定ファイルがインストールされるということです。

中には上記のコマンドをさらに簡単にして、githubのURLではなく自身のドメインにすることでコマンドを短くする方法を取っている方もいました。
ですが今回はその方法はとらず、とりあえずワンライナーでインストールが行えることを目標に頑張ります。

curl -sL dot.hoge.com | sh で自分専用環境を構築する方法(かっこいい) - 若くない何かの悩み

ドメインを用いた管理法について興味のある方は上記を参考にしていただければと思います。

dotfiles

現在の私のディレクトリを載せておきます。

github.com

はじめに

まずはじめに管理するdotfilesがなければ意味がありません。

dotfilesを管理しよう - Qiita

上記のサイトを参考に自身が管理するdotfileをgithub上にupしましょう。

簡単にコマンド群だけ載せておきます。

mkdir ~/dotfiles
cd ~/dotfiles
git init
echo "#dotfiles" > README.md
git add .
git commit -m "first commit"
~ github上でリポジトリ作成 ~
git remote add <URL>
git push origin master

あとは上記で作ったディレクトリに.vimrcなどを追加していけば良いです。

install.shの作成

bashスクリプトでinstall.shというインストール用のスクリプトを書きます。

基本形の作成

#!/bin/bash

DOT_DIR="$HOME/dotfiles"
for f in *;
do
    [[ "$f" == ".git" ]] && continue
    [[ "$f" == ".gitignore" ]] && continue
    [[ "$f" == ".DS_Store" ]] && continue
    [[ "$f" == "README.md" ]] && continue
    [[ "$f" == "install.sh" ]] && continue

    ln -snf $DOT_DIR/"$f" $HOME/".$f"
    echo "Installed .$f"
done

これが基本形です。
for文で現在ディレクトリのファイルを取得し変数fに格納します。
それを.ファイル名という形に変換してシンボリックリンク を貼ります。
コピーではなくリンクを貼ることで編集後にpushするのが楽になります。
ちなみに私はgithub.vimrcではなくvimrcというファイル名で管理しているのでドットファイルに変換していますが、.vimrcというファイルで管理する場合は変換の必要はありません。
$HOME/".$f"ではなく$HOME/$fで大丈夫です。
あと$HOMEではなく~を使っても同じです。

[[ "$f" == "hoge" ]] && continueという文がいくつかありますが、これは不必要なファイルをドットファイルとして設定しないための処理です。
他にも設定しないファイルが出てきた際はhogeの部分を変えて処理を追加すれば良いです。

ファイルが既に存在する場合実行しないようにする

今度はdotfilesが既に存在する場合はインストール処理を行わないようにします。
既に存在する場合は別で管理するか、git pullで新バージョンを持ってくれば良いので。
存在するかどうかの判定処理を追加します。

#!/bin/bash
DOT_DIR="$HOME/dotfiles"
if if [ ! -d ${DOT_DIR} ]; then
    for f in *
    do
        [[ "$f" == ".git" ]] && continue
        [[ "$f" == ".gitignore" ]] && continue
        [[ "$f" == ".DS_Store" ]] && continue
        [[ "$f" == "README.md" ]] && continue
        [[ "$f" == "install.sh" ]] && continue

        ln -snf $DOT_DIR/"$f" $HOME/".$f"
        echo "Installed .$f"
    done
else
    echo "dotfiles already exists"
    exit 1
fi

追加したif if [ ! -d ${DOT_DIR} ]; thenの処理でディレクトリが存在していなければ内部処理に移るようにします。
存在した場合はメッセージを表示して終了します。

ファイルのダウンロードを行う

今までの処理はユーザディレクトリ以外の場所にdotfilesディレクトリがある場合を想定しての処理でした。
ですが新規にインストールする場合(OS再設定後など)ではそもそも管理しているdotfilesディレクトリをダウンロードしてくる必要があります。
その処理を追加します。

#!/bin/bash

DOT_DIR="$HOME/dotfiles"

has() {
    type "$1" > /dev/null 2>&1
}

if if [ ! -d ${DOT_DIR} ]; then
    if has "git"; then
        git clone https://github.com/kisqragi/dotfiles.git ${DOT_DIR}
    else
        echo "git required"
        exit 1
    fi
    cd ${DOT_DIR}
    for f in *
    do
        [[ "$f" == ".git" ]] && continue
        [[ "$f" == ".gitignore" ]] && continue
        [[ "$f" == ".DS_Store" ]] && continue
        [[ "$f" == "README.md" ]] && continue
        [[ "$f" == "install.sh" ]] && continue

        ln -snf $DOT_DIR/"$f" $HOME/".$f"
        echo "Installed .$f"
    done
else
    echo "dotfiles already exists"
    exit 1
fi

処理がいくつか増えたので一つずつ説明します。
まずは先頭に追加されたhas関数です。
これはコマンド名を受け取ってそのコマンドが存在するかどうかを判定する関数です。
これを利用してその後の処理ではgitコマンドが存在する場合処理をするよう判定しています。

if has "git"; then
    git clone https://github.com/kisqragi/dotfiles.git ${DOT_DIR}
else
    echo "git required"
    exit 1
fi

gitコマンドが存在すればホームディレクトリにdotfilesという名前でディレクトリをダウンロード。
なければメッセージを表示して終了という形にしています。
その後cdコマンドでdotfilesへ移動しています。cd ${DOT_DIR}
URLは自身のリポジトリのURLにしてください。

gitコマンド以外でインストールする

上記のスクリプトでももうワンライナーでインストールはできます。
ただそのままだとgitコマンドがインストールされていない環境では実行ができません。
なので搭載されている可能性の高い(?)curlwgetを用いたインストールの処理も追加します。

#!/bin/bash

DOT_DIR="$HOME/dotfiles"

has() {
    type "$1" > /dev/null 2>&1
}

if [ ! -d ${DOT_DIR} ]; then
    if has "git"; then
        git clone https://github.com/kisqragi/dotfiles.git ${DOT_DIR}
    elif has "curl" || has "wget"; then
        TARBALL="https://github.com/kisqragi/dotfiles/archive/master.tar.gz"
        if has "curl"; then
            curl -L ${TARBALL} -o master.tar.gz
        else
            wget ${TARBALL}
        fi
        tar -zxvf master.tar.gz
        rm -f master.tar.gz
        mv -f dotfiles-master "${DOT_DIR}"
    else
        echo "curl or wget or git required"
        exit 1
    fi

    cd ${DOT_DIR}
    for f in *;
    do
        [[ "$f" == ".git" ]] && continue
        [[ "$f" == ".gitignore" ]] && continue
        [[ "$f" == ".DS_Store" ]] && continue
        [[ "$f" == "README.md" ]] && continue
        [[ "$f" == "install.sh" ]] && continue

        ln -snf $DOT_DIR/"$f" $HOME/".$f"
        echo "Installed .$f"
    done
else
    echo "dotfiles already exists"
    exit 1
fi

これが今回の完成系になります。
大きく変わった部分を説明します。

elif has "curl" || has "wget"; then
    TARBALL="https://github.com/kisqragi/dotfiles/archive/master.tar.gz"
    if has "curl"; then
        curl -L ${TARBALL} -o master.tar.gz
    else
        wget ${TARBALL}
    fi
    tar -zxvf master.tar.gz
    rm -f master.tar.gz
    mv -f dotfiles-master "${DOT_DIR}"
else
    echo "curl or wget or git required"
    exit 1
fi

まずこの処理の流れとしてはcurlwget存在する方のコマンドで圧縮されたdotfilesをダウンロードします。
その後解凍し、圧縮ファイルの削除、解凍したディレクトリを名前を変えてホームディレクトリへ配置。となっています。
tarballというのはtar.gz拡張子のファイルのことをいいます。
tarballに設定するURLの取得方法ですがまずgithubリポジトリへ移動します。
Clone or downloadをクリック
Download Zipがあるので右クリックか何かでリンクのアドレスをコピーします。
するとまずzipファイルのURLが取得できます。
そしたら.zip拡張子を.tar.gzに変更するだけです。
https://github.com/kisqragi/dotfiles/archive/master.zip

https://github.com/kisqragi/dotfiles/archive/master.tar.gz
f:id:kisaragi211:20200217222153p:plain

zip拡張子のままダウンロードしてきてunzipで解凍しても良いのですがunzipがあってtarがないというパターンは少ないと思います(多分)
tarはあるけどunzipはないという環境の方が多い気がします。
なので今回はわざわざtar.gz拡張子で行っています。

全体ソース

改めて全体ソースを載せます。

#!/bin/bash

DOT_DIR="$HOME/dotfiles"

has() {
    type "$1" > /dev/null 2>&1
}

if [ ! -d ${DOT_DIR} ]; then
    if has "git"; then
        git clone https://github.com/kisqragi/dotfiles.git ${DOT_DIR}
    elif has "curl" || has "wget"; then
        TARBALL="https://github.com/kisqragi/dotfiles/archive/master.tar.gz"
        if has "curl"; then
            curl -L ${TARBALL} -o master.tar.gz
        else
            wget ${TARBALL}
        fi
        tar -zxvf master.tar.gz
        rm -f master.tar.gz
        mv -f dotfiles-master "${DOT_DIR}"
    else
        echo "curl or wget or git required"
        exit 1
    fi

    cd ${DOT_DIR}
    for f in *;
    do
        [[ "$f" == ".git" ]] && continue
        [[ "$f" == ".gitignore" ]] && continue
        [[ "$f" == ".DS_Store" ]] && continue
        [[ "$f" == "README.md" ]] && continue
        [[ "$f" == "install.sh" ]] && continue

        ln -snf $DOT_DIR/"$f" $HOME/".$f"
        echo "Installed .$f"
    done
else
    echo "dotfiles already exists"
    exit 1
fi

インストールコマンドをREADMEへ追加する

最後の締めとして、インストールを行うコマンドをREADMEへ追加します。
こうすることでリポジトリを開くとすぐにコピーが行えて楽になります。
READMEを編集して以下を追加します。

bash -c "$(curl -fsSL https://raw.githubusercontent.com/kisqragi/dotfiles/master/install.sh)"

curlの引数に指定するURLは自身のリポジトリを参照してください。
f:id:kisaragi211:20200217223534p:plain 今回作ったinstall.shのページを開きます。
そうするとソースコードの帯にRawというタブがあるのでそこをクリックします。
そのページのURLをコピーして使ってください。

まとめ

参考にさせていただいた方のページを最後の方に掲載しますので、わからない部分はそちらを参考にしていただければ良いと思います。

参考

dotfilesをGitHubで管理 - Qiita
優れた dotfiles を設計して、最速で環境構築する話 - Qiita
『GitHub+dotfiles』は環境構築を一瞬で終わらせるすごいやつ | vdeep

基本的なアセンブリ命令(Intel記法)

基本的なアセンブリ命令について勉強したのでアウトプットします。

前提

今回はIntel記法で書いていきます。
他にもAT&T記法があるのですが、より直感的に理解しやすい方を優先的に覚えることにしました。
一応差分を

Intel記法
  mov eax, 1
AT&T記法
  mov $1, %eax

基本命令

代入命令

mov dst, src

srcの値をdstにコピーします。
アセンブリではなくプログラミング言語で書くとdst = srcといったところでしょうか。

アドレス計算

lea dst, src
例:
lea eax, [esp+0x10]

srcのアドレス計算を行った後にdstに値を格納するという命令です。
例で言えばespに0x10を加算した値をeaxへ格納します。

交換

xchg op1, op2

op1とop2の値を交換します。

加減算

add dest, src
sub dest, src

addが加算。subが減算です。
dest = dest + src dest = dest - src になります。
例:add eax, 1 -> eax = eax + 1

乗算

mul src

これは加減算と違い引数が一つしかありません。
かける数はsrcに依存したAレジスタ(al, ax, eax)となります。というのも以下の表をみてください。

サイズ 上位bit 下位bit
8bit AH AL
16bit DX AX
32bit EDX EAX
64bit RDX RAX

例えばmul ebxの場合、ebxは32bitレジスタです。
なのでebxにeaxをかけます。
計算の結果上位32bitをEDXへ、下位32bitをEAXへ格納します。
これがmulの計算になります。
srcのレジスタのアドレス幅によってかける値が変わるので覚えるのが大変そうです。

除算

div src

これもsrcのアドレス幅に依存するという意味では同じです。
例をあげます。

div rbx

この場合rbxは64bitなのでrbx / raxの計算が行われます。
その後、商をrax, 剰余をrdxへ格納されます。

分岐命令

フラグレジスタ

分岐命令はフラグレジスタと呼ばれるもので判断します。
そのため先に書いておくことで分岐の仕組みが少し理解しやすくなるかもしれません。
代表的なもの(?) を取り上げます。

フラグレジスタ bit 名前 内容
ZF 6 ゼロフラグ 演算結果がゼロなら1が立つ
SF 7 サインフラグ 演算結果の正負(正なら0, 負なら1)
OF 11 オーバーフローフラグ 桁溢れがあれば1

フラグレジスタは他のレジスタと違い各bitに意味があります。
それが上記の表のbitの部分です。
後で紹介するcmpなどの演算結果を用いて各bitに値を設定していくことになります。
分岐処理はそれらの各bitを見て判断していきます。
では実際に各命令をみていきます。

比較命令

cmp op1, op2

cmpは比較を行う命令です。
少し詳しく言うとこの命令ではop1 - op2という減算が行われます。
しかし、その結果は保持されません。結果は保持されませんが、フラグレジスタの変更は行われます。
これがsub命令とは違う点です。あくまで目的はフラグレジスタの変更となっています。
演算の結果が0であればZFに1が設定されます。
つまりop1とop2の値が同じであればゼロであることを判断するZFにbitが立つということです。
また、op1の方が大きい場合場合(op1 - op2 > 0)、負数を表すSFには0が入ります。
その逆でop2の方が大きい場合(op1 - op2 < 0)、SFには1が入ります。
c言語風に書くと以下のような形です。

if ((op1 - op2) > 0) {
  SF = 0
}
else if ((op1 - op2) < 0) {
  SF = 1
} else {
  ZF = 1
}

あくまでイメージなので書き方が汚いのは気にしないでください...

cmpは減算処理を行ってフラグを設定しますが、実は同じようにsubでも設定することができます。
その場合演算結果は保持されますが、フラグレジスタも設定されます。

無条件分岐命令

jmp addr

これは一番シンプルな分岐命令で引数に指定したアドレスへ処理を移します。
これは条件なく分岐するため、無条件分岐命令とも呼ばれます。

条件分岐命令

条件分岐命令ではフラグレジスタを元に分岐を行っていきます。
表形式で基本的な命令を書きます。

命令 意味 内容
JE Jump if Equal ZF=1のとき分岐
JZ Jump if Zero ZF=1のとき分岐
JNE Jump if Not Equal ZF=0の時分岐
JNZ Jump if Not Zero ZF=0の時分岐
JG Jump if Grater ZF=0 && SF=OF
JL Jump if Less SF != OF

JE, JZ

ZF = 1のときとはどういうときか。
それはcmp op1, op2という命令があったときop1とop2の値が同じ時です。
op1 - op2 = 0のときZFにはビットが立つという話は比較命令のときにもしましたので、振り返ってもらえれば良いと思います。
JNE, JNZはその逆でZF=0のときに分岐するのでop1, op2の値が違った場合に分岐します。

JG

ZF=0 かつ SF=OFとはどういう場合か。
簡単にいえば演算結果が負の数ではない、かつオーバーフローを起こしていないということになります。
op1 > op2 のときと言ってよいと思います。

JL

これはJGの逆で演算結果が負数で、かつオーバーフローを起こしていないときということになります。

条件分岐を簡易言語で書く

if (op1 == op2)
  (JE|JZ)処理
if (op1 > op2)
  (JG|JNE|JNZ)処理
if (op1 < op2)
  (JL|JNE|JZE)処理

他言語風に書くとこんな感じでしょうか。

まとめ

今回の記事ではpush, pop, call, retなどの重要な命令が書いてありません。
それには理由があって、次に各記事で関数呼び出し処理と照らし合わせながらまとめていこうと考えているからです。
アドバイス・補足等ありましたらTwitterまでお願いします。

ミニキャン言語でグローバル変数を実装した話

はじめに

ミニキャン言語でグローバル変数を実装したのでそれについて書きます。
ミニキャン言語は何かっていう方は以下を参照してください。

github.com

私が実際に作成してるプログラムは以下になります。

github.com

この記事は、こう実装したというメモ的な形になります。 あまり解説らしい解説は書けませんが、こういった実装があるんだな程度に参考にしてもらえればと思います。

実装の前にIRの標準出力について

今までのミニキャン言語の実装ではファイル出力ではない方、標準出力の方には関数のIRしか出力されません。
なので、今回グローバル変数を出力するにあたってそこを少し修正します。
codegen.hのHandleDefinition関数では以下のようにしてIRが標準出力されていると思います。

if (auto *FnIR = FnAST->codegen()) {
    FnIR->print(stream);
}

これはFunctionクラスのprintメソッドを使っているのですが、実際に確認するときにグローバル変数が出力されず結果が確認できません。
".ll" ファイルも出力されていれば別ですが、ミニキャン言語では".o"ファイルでoutputファイルが生成されているので、別に確認する手段が必要となります。
なので上記の部分を

auto *FnIR = FnAST->codegen();

と変更し、mc.cppにModuleクラスのprintメソッド処理を追加してあげましょう。

MainLoop();
myModule->print(llvm::outs(), nullptr);
write_output();

場所的にはこの辺りで良いと思います。
自分が正しいと思う場所へ追加しましょう。
myModuleは今回使うModuleクラスのインスタンスです。
Moduleクラスのprintメソッドを使うことでグローバル変数も一緒に出力されます。
結果も確認できるようになったので、実装に移ります。

グローバル宣言の実装

mc.cpp

今回はGlobalVariableクラスを使ってグローバル変数を実装していくのでGlobalVariable.hをインクルードしておきます。

#include "llvm/IR/GlobalVariable.h"

parser.h

はじめに

グローバル変数の実装や処理の流れについては通常の変数の実装について理解しておく必要がある場合があります。
その場合は以下の公式チュートリアルを参照してください。

llvm.org

実装

まずコードを載せます。

static std::pair<std::string, std::unique_ptr<ExprAST>> ParseGVarExpr() {
    getNextToken();

    std::string Name = lexer.getIdentifier();
    getNextToken();

    std::unique_ptr<ExprAST> Init = nullptr;

    if (CurTok == '=') {
        getNextToken();

        Init = ParseExpression();
    }


    if (CurTok != tok_in)
        fprintf(stderr, "expected 'in' keyword after var");
    getNextToken();

    return std::make_pair(Name, std::move(Init));
}

この関数では変数名と初期値を解析します。

通常の変数ではASTを作成したのですが、今回は処理の都合であえて作らず変数名と初期値のpairを呼び出し元に返すだけにしました。

処理内容は見たままだと思います。

getNextToken();
std::string Name = lexer.getIdentifier();
getNextToken();

最初のgetNextToken()でvar x inのvarからxにトークンを進めています。
進めた後で変数名を保持しておいて、トークンを進めます。

std::unique_ptr<ExprAST> Init = nullptr;
if (CurTok == '=') {
    getNextToken();
    Init = ParseExpression();
}

ここの処理ではvar x = 4 inのように初期値も宣言されている場合の処理を書いています。
宣言されていない場合はInitはnullptrになります。

return std::make_pair(Name, std::move(Init));

最後に変数名と初期値のpairを作成してreturnします。
この関数はcodegenで呼び出します。

codegen.h

今回仕様としては

var x in

def func() {
    x = 4
}

上のような関数外で変数が宣言された場合グローバル変数とみなす形式にしました。
他にはglobalというトークンがあったらグローバル変数にするといった実装もあるかもしれません。

今回は上のような形式なので、関数定義と同じレベルにvarトークンがあればグローバル変数の生成処理を行うという方法を行いました。

MainLoop関数にtok_varのケースを追加します。

static void MainLoop() {
  ~ 省略 ~
    case tok_def:
        HandleDefinition();
        break;
     case tok_var:
        GVarDeclaration();
        break;
  ~ 省略 ~
}

GvarDeclaration関数はグローバル変数の宣言処理を行う関数です。
中身を載せます。

static void GVarDeclaration() {
    std::pair<std::string, std::unique_ptr<ExprAST>> body = ParseGVarExpr();
    auto name = body.first.c_str();

    Constant *Init;
    if (body.second != nullptr) {
        auto Val = body.second->codegen();
        Init = (Constant *) Val;
    } else {
        Init = ConstantInt::get(Context, APInt(64, 0, true));
    }

    IntegerType *IntegerTy = IntegerType::get(Context, 64);
    auto gvar = new GlobalVariable(
        *myModule,
        IntegerTy,
        false,
        GlobalValue::InternalLinkage,
        Init,
        name
    );
    GlobalNamedValues[name] = gvar;
}

ParseGVarExpr()で解析した変数名と初期値のペアを受け取ります。
初期値が存在しない場合は今回はInt型の0を初期値としました。
初期値が存在した場合はcodegenを行って初期値とします。

後半の部分がグローバル変数の宣言になります。
GlobalVariableクラスのインスタンスを生成するとそれが自動的にグローバル変数としてIRを生成してくれます。
グローバル変数のIRは@変数名という形で出力されます。
例:
C言語ソース

int H = 0;
int main() {
    H = 5;
    return 0;
}

上記のIR

@H = global i32 0, align 4
define i32 @main() #0 {
  %1 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 5, i32* @H, align 4
  ret i32 0
}

上の例だと@Hがグローバル変数です。
ではそれぞれのコードについて書きます。

IntegerType *IntegerTy = IntegerType::get(Context, 64);

このソースはInteger64tyを取得する1行です。
これはインスタンスを作成するときに用い入ります。
なので今回実装するグローバル変数はすべてこの型になります。

auto gvar = new GlobalVariable(
    *myModule,
    IntegerTy,
    false,
    GlobalValue::InternalLinkage,
    Init,
    name
);

GlobalVariableの引数はドキュメントを参照してください。

llvm.org

何だこれ?と自分が思ったところを書きます。
ですが、自分自身正確には把握しきれていないので間違いがありましたら指摘いただけるとありがたいです。
まずisConstantの部分ですが、定数かどうかを聞かれているのだと思います。
今回は変数なのでfalseにしました。
LinkageTypesについてはこれも公式ドキュメントを参考にしていただいて、目的にあった物を選べば良いと思います。
私はそれぞれの違いについてよく理解できなかったのでInternalにしました。
InternalかPrivateにすれば良いと思います。

GlobalNamedValues[name] = gvar;

最後のこの部分は変数の参照と値の変更の際に使います。
これはこの後に説明します。

グローバル変数の参照と変更の実装

ここからは変数の宣言だけでなく、参照と変更(代入)の実装について書きます。

変数の参照

codegen.h

グローバル変数を参照するために値を保持しておくmapをNamedValuesとは別に作成します。
NamedValuesはミニキャン言語の実装通りいくと関数のコード生成とともに新しくされてしまうので別のmap配列が必要になります。

static std::map<std::string, GlobalVariable *> GlobalNamedValues;

mapの組み合わせは変数名とGlovalVariableのインスタンスにします。
このmap配列にはインスタンス作成時に格納します。
グローバル宣言処理の最後の一行になります。

GlobalNamedValues[name] = gvar;

次にグローバル変数の参照ですが以下のような関数を実装しました。

tatic Value *getNamedValues(std::string name) {
    if (NamedValues.count(name)) {
        return NamedValues[name];
    }
    if (GlobalNamedValues.count(name)) {
        return GlobalNamedValues[name];
    }
    return nullptr;
}

これをVariableExprASTで呼び出します。
最初の実装では

Value *V = NamedValues[variableName];

となっていますが、そこを

Value *V = getNamedValues(variableName); 

に変更します。
ローカル変数や引数を優先的にreturnし、次の優先度でグローバル変数をreturnします。
ローカル変数を実装しているとNamedValuesはstringとAllocaInstのmap配列になっています。
AllocaInst
と GlobalVariable では値が違ってダメではないかと最初思いましたが、どちらもValue の継承先なのでValue *を受け手にすることでその差異を吸収してくれます。
継承って便利ですね。

以上の実装により変数の参照ができるようになりました。

変数の変更

変数の変更ですが、どうすれば良いのかClangが出力するIRを参考にしました。

@H = global i32 0, align 4
define i32 @main() #0 {
  %1 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 5, i32* @H, align 4
  ret i32 0
}

これを見るとグローバル変数への代入はstoreを使って行われていることがわかりました。
なので、通常の変数と同じ要領で変更できることがわかりました。
以下の実装をBinaryASTのcodegenの一番最初に追加します。
詳しい処理説明は公式ドキュメントの変数の実装を参考にしてください。

    if (Op == '=') {
        VariableExprAST *LHSE = dynamic_cast<VariableExprAST *> (LHS.get());
        if (!LHSE)
            return LogErrorV("destinatin of '=' must be a variable");

        Value *R = RHS->codegen();
        if (!R)
            return nullptr;

        //Value *Variable = NamedValues[LHSE->getName()];
        Value *Variable = getNamedValues(LHSE->getName());
        if (!Variable)
            return LogErrorV("Unknown variable name");

        Builder.CreateStore(R, Variable);

        return R;
    }

今回でいうstoreの処理は

Builder.CreateStore(R, Variable);

この部分に該当します。
この処理で変数をRHSで上書きします。
以上で変数の変更もできるようになりました。

まとめ

公式ドキュメントを参照する部分が多くて申し訳ないです。

llvm.org

再度にリンクを張らせていただきますが、こちらを参考にローカル変数を実装した後で読んでいただければ理解しやすいかと思います。

ご指摘等ありましたら、お願いします。

2020年の目標

はじめに

明けましておめでとうございます。
kaitoさんのありがたいお言葉もあり、今更ですが今年の目標を記事にしてみました。

注意

それ今すぐやれば達成できるけど?というレベルの事も書きます。
小さな目標でも「達成する」という事は自分のモチベーションに繋がると思っているので、そういった理由です。

目標

  • セキュリティキャンプ応募・参加
  • ミニキャン言語関係
    • ミニキャン言語発展課題完了
    • ミニキャン言語でグローバル変数を実装した記事を書く
    • ミニキャン言語を作り続けての感想を記事にする
    • ミニキャン言語に配列等を実装し、使える言語にしていく
  • SC合格
  • コンパイラ作成
  • 「Go言語でつくるインタプリタ」を読んでインタプリタ作成
  • CTFに挑戦する
  • アセンブリを勉強する
  • バグバウンティについて情報収集
  • ARTxTECHラボで作品を作る
  • セキュリティ系のイベントに参加
  • 参加して交流を深める(自分から積極的に話しかけにいく)

CS関係ないやつ

思ったこと

こうやって書くと、お前には無理だ的な難易度から今やれ的な難易度と極端な目標になった気がします。
目標負けしないように頑張っていくのが大事だなと、既に感じています。
この1年間は勝負の年だと勝手に思っています。
強い人に成長して、強い人とお話ができれば良いなと思っています。

何かアドバイス(これに参加した方が良い、こうやって勉強したら良い)などありましたら教えてください。

いつも目標は立てずに1年間だらだら過ごしてきましたが、いざこうやって宣言すると強制力あるなあと思いました。
以上です。

説明会&第1回勉強会を終えての反省

11/28, 12/5に学内で勉強会の説明会と勉強会を行いました。
それについての反省点等を自分メモとして残したいと思います。
勉強会は自作言語を作るという内容で説明・実施しました。

反省点

説明会での反省点

勉強会の説明としてスライドを見せながらの発表を行いました。
今回は個人情報が多かったので公開はしません。

1. どういうコードを書いているのか見せる

実際に作った自作言語の例は載せてあったが、どういうソースを書いていくのかという例がなかったため、いまいちイメージがしにくかったという声をいただいた。
-1 + -1 のような負数の実装などを例にして見せたが、これを書くだけかと少し思ってしまったという意見もいただいた。

2. 使うOSの指定がなかった

今回はvirtualboxLinuxの環境を作ってもらいます、という指示はしていたが、その時にOSの指定がなかったのは反省点である。
Slack内の勉強会チャンネルで後々指定すれば良いと思っていたり、Unix系であれば基本どれでも良いとも思っていたのもあったが、次回の改善点である。
LinuxのOSがそもそも何があるのかわからない、という視点が欠けていたように思う。

3.Linuxの環境作成も勉強会に含める

学内での勉強会でネットワークの速度はそれほど期待できないため、各自第1回の勉強会までにVirtualboxLinuxの環境を作成するようにお願いしていた。
しかし、Virtualboxを触れることも初めてのような参加者もいたので、そういった人のために環境作成も勉強会に含めたら良いと感じた。
isoやvirtualboxだけ予め用意していただくのでもよいかもしれない。

第1回勉強会の反省点

1. 時間把握

30分・10分(休憩)・30分が守れていなかった。
次回はタイマーを使うなどして、守っていきたい。

勉強会全体の反省点

1.指示が少ない

何から手をつけて良いかわからない、と困ってる参加者の人もいたので何から始めたら良いか。
どう進めたら良いかはきちんと指示した方が良い。
自主性も大事だが、自主性が芽生えるまでをきちんとサポートする。
質問の仕方も指示する。(エラーメッセージを載せる、画像を見せる、ソースを見せるなど)

2. 対象レベルが不明確

様々な人に参加してもらいたく、特に必要スキル・レベルは設定していなかった。
指定しないにせよ、全くの初心者の方が参加してくれるということも考えて、かなり初歩的なところからやる観点も必要だった。

まとめ

初めての勉強会ということもあり、様々な反省点が出た。
反省点を活かし、開催の度にレベルアップしたないようにしていきたい。

wordpressを導入した話

勉強用のサーバーにwordpressを導入したのでパーミッションやらの設定はどうしたのか、という話を書きます。

環境

Ubuntu 18.10

権限とパーミッションの設定

参考サイトを真似しながら設定します。

所有者の変更

/var/www/以下のディレクトリ・ファイルの所有者はapacheが持つように設定するのが良いらしいです。
wordpressではその変更が重要らしくテーマの設定だったり、様々な変更がしやすくなります。
では変更 -Rオプションをつけることで再帰的に、対象ディレクトリ以下を全て設定してくれます。

sudo chown -R www-data:www-data /var/www

CentOSとかではapacheはそのままapache:apacheで設定できるらしいのですが、Ubuntuではwww-dataというユーザー・グループ名なのでこれで設定します。
/etc/apache2/envvarsに設定があるのでそこを変えると変更もできます。

export APACHE_RUN_USER=www-data
export APACHE_RUN_GROUP=www-data

16, 17行目に上記のような設定があるので書き換えれば良いと思います。
今回はデフォルトのままいきます。

パーミッションの設定

権限の設定はできたので、次はパーミッションの設定をします。
参考サイトによるとファイルには664, ディレクトリは775のパーミッションを設定した方が良いとのことでした。
下記のコマンドを実行

sudo find /var/www/ -type f -exec chmod 664 {} \;
sudo find /var/www/ -type d -exec chmod 775 {} \;

実行権限を与えないとファイルに移動ができないため、ディレクトリの設定は775にしています。

/var/www/はroot管理へ

/var/www/はroot管理にしておくことで、万が一サイトを乗っ取られても、他のファイルに影響がないようにします。

sudo chown root:root /var/www/

まとめ

サイトを参考にしながら設定しました。
もっとこうした方が良いというアドバイスございましたらお願いします。

参考

CentOS 6 LAMP permissions - the right way - What Sam Knows

WordPressのファイルのパーミッションはどうすべきなのか(WordPress the Right Way) | ニートエンジニアの日記

ミニキャン言語で負数に対応した話

はじめに

ミニキャン言語で発展課題である負数の実装ができたのでアウトプットします。
兎にも角にもコードだという人は以下を参照してください。

github.com

実装

今回負数の実装をしたのですが、最初は定数のみ(-1や-2など)にしか対応しておらず、その後変数/引数(-xなど)に対応させました。
なので、最初に定数のみの実装について書いた後に引数に対応した処理を書きます。

負の定数の実装

定数のみの実装はこんな感じです。

static std::unique_ptr<ExprAST> ParseNegNumberExpr() {
    getNextToken();
    if (CurTok != tok_number) {
        return LogError("expected number after '-'");
    } else {
        auto Result = llvm::make_unique<NumberAST>(-lexer.getNumVal());
        getNextToken();
        return std::move(Result);
    }
}

これは他のミニキャン参加者の実装を真似させていただきました。
要となっている処理は以下の処理です。

auto Result = llvm::make_unique<NumberAST>(-lexer.getNumVal());

NumberASTにセットする値を読み込んできた際に、その場で負数の扱いにしてASTを作るという処理です。
この処理をどこで呼び出すのかというとParsePrimaryで呼び出します。

case '-':
    return ParseNegNumberExpr();

上記のcase文を追加することで負数のParseを行います。
ParseのタイミングはNumberAST(正数)と同じで良いでのここで呼び出します。

で、ここまでで負の定数なら処理することができるようになりました。
なので今の状態なら1 + -1などが計算できます。
しかし、1 + -xは計算できませんので、そこに対応していきます。

負の変数の実装

負数の実装は色々方法があるかもしれませんが私は0 - xという処理をすることで負数にするという方法にしました。
前半の実装ではそのままNumberASTを使っていましたが今回は別にNegNumberASTというのを用意しました。

    class NegNumberAST : public ExprAST {
        std::unique_ptr<ExprAST> LHS, RHS;

        public:
        NegNumberAST(std::unique_ptr<ExprAST> LHS,
                std::unique_ptr<ExprAST> RHS)
            : LHS(std::move(LHS)), RHS(std::move(RHS)) {}

        Value *codegen() override;
    };

LHSは0で固定なのですがcodegenの実装が楽になると思いフィールドを用意しました。
主に使うのはRHSです。
そして重要なParseの部分です。

static std::unique_ptr<ExprAST> ParseNegNumberExpr() {
    getNextToken();
    auto RHS = ParsePrimary();
    auto LHS = llvm::make_unique<NumberAST>(0);
    return llvm::make_unique<NegNumberAST>(std::move(LHS), std::move(RHS));
}

ParseNegNumberを読む時のCurTokは-なのでTokenを進めます。
RHSはその後の数字や識別子をParseしたものを保持します。
LHSは0 - xなので0のNumberASTを作成します。

codegenは以下のようになります。

Value *NegNumberAST::codegen() {
    Value *L = LHS->codegen();
    Value *R = RHS->codegen();
    if (!L || !R)
        return nullptr;

    return Builder.CreateSub(L, R, "subtmp");
}

LHSとRHSをそれぞれcodegenしてからLHS - RHSの式をcodegenします。
これはBinaryASTのcodegenをそのまま真似した式です。

これでようやく実装できましたので、処理結果を載せます。
f:id:kisaragi211:20191203193121p:plain f:id:kisaragi211:20191203193136p:plain
二つ目の画像で0 - xの中間コードが出ているのがわかります。

まとめ

負数の実装は時間はかかりましたが、想像より簡単な処理で行うことができました。
BinaryASTのcodegenやParseBinOpRHSを真似すれば誰でも実装できそうだなという感想です。

アドバイス等ございましたらツイッター等でメッセージをください。