go:noescape

goのソース読んでるとたまに見かける go:noescape についてメモ.

go:noescapeはGoコンパイラエスケープ解析へのヒント。

エスケープとは、関数内でメモリに割り当てられた値が関数が終了したあとも、呼び出し元の関数や別のスレッドから値が参照されること

Goのコンパイラは値をヒープよりもスタックに割り当てようとする。スタックに割り当てると関数を抜けるとメモリ領域が廃棄されるので、ヒープ領域の取得やGCの回収するコストがかからずヒープに割り当てられるよりコストが低い。

だが、スタックに割り当てられた値のポインタがエスケープして参照されると、ダングリングポインタ(dangling pointer)が発生するので、エスケープ解析によってエスケープされないことがわかる値だけをスタックに割り当てる。

しかし、コンパイラはGoで書かれていない関数はエスケープ解析ができない。そのような関数でも、引数として渡されたポインタがエスケープしないことをgo:noescapeディレクティブの指定によってコンパイラに教えることができる。

Goの標準ドキュメントによると:

https://golang.org/cmd/compile/

The //go:noescape directive specifies that the next declaration in the file, which must be a func without a body (meaning that it has an implementation not written in Go) does not allow any of the pointers passed as arguments to escape into the heap or into the values returned from the function. This information can be used during the compiler’s escape analysis of Go code calling the function.

(超訳)//go:noescapeディレクティブはファイル内で次の関数宣言(ボディーの無い関数(つまり実装がGoで書かれていない関数)でなければならない)の、引数として渡るポインタがヒープやその関数の返り値にエスケープされることを一切許可しないことを明示する。この情報はコンパイラがその関数の呼び出すGoコードのエスケープ解析を行う際に利用することができる。

Go Assembler メモ

GoのアセンブラPlan 9のものを引き継いでいる。

疑似レジスタ

  • FP: Frame pointer
    • arguments and locals.
  • PC: Program counter
    • jumps and branches.
  • SB: Static base pointer
    • global symbols.
  • SP: Stack pointer
    • top of stack.

引数

引数はFP擬似レジスタからのオフセットで指定する

第一引数 0(FP) 第二引数 8(FP) ※ 64bit環境の場合

変数

変数はSP疑似レジスタからのオフセットで指定する。 SPはローカルフレームスタックの戦闘を指すので、参照するときは負のオフセットを指定する。

SPというマシンレジスタがある場合は、名前プレフィックスの有無で区別する。

x-8(SP) は疑似レジスタ -8(S) はマシンレジスタ

名前プレフィックス

引数や変数を指すときは名前つけができる。 Goのプロトタイプを持っている関数なら、go vetでオフセットが正しいかチェックできる。

// func add(x, y int) int
TEXT ·add(SB),NOSPLIT,$0
    MOVQ x+0(FP), AX
    ADDQ y+8(FP), AX
    MOVQ AX, ret+16(FP)
    RET

返り値が名前付きじゃない場合、 暗黙的にretが返り値の名前になる

シンボル名

Goのオブジェクトファイルとバイナリではシンボルの名前は、fmt.Printf, math/rand.Int のようにパッケージ名を.でつないだ名前になる。

アセンブラ./を区切り文字として使うので、中黒文字(MIDDLE DOT: U+00B7)と割り算文字(DIVISION SLASH: U+2215)がピリオドとスラッシュに置換される。

fmt.Printf → fmt·Printf math/rand.Int → math∕rand·Int

関数

関数はTEXTディレクティブで宣言する。

シンボルのあとには、フラグとフレームサイズの定数を書く。 フレームサイズの後ろには引数のサイズをフレームサイズのあとにマイナス記号で区切ってから指定する。NOSPLITを指定していない場合に引数のサイズは必須。

$24-$8が指定された場合、24バイトのローカルフレームを持ち、8バイトの引数を持つ。

シンボルの宣言のあとに、関数の中身を書く。関数は最後にジャンプ命令でなければならない。

// func add(x, y int) int
TEXT ·add(SB),NOSPLIT,$0-24
    MOVQ x+0(FP), AX
    ADDQ y+8(FP), AX
    MOVQ AX, ret+16(FP)
    RET

NOSPLIT

スタックオーバーフローのチェックを外す指定。

https://golang.org/cmd/compile/

The //go:nosplit directive specifies that the next function declared in the file must not include a stack overflow check. This is most commonly used by low-level runtime sources invoked at times when it is unsafe for the calling goroutine to be preempted.

値はDATAディレクティブで宣言する。

シンボルのあとにスラッシュで区切って、データサイズをバイト数で指定する。 カンマまで区切ったあとには、シンボルに束縛される値を書く。

DATA ・spam(SB)/4, $1

GLOBLディレクティブ

GLOBLディレクティブはシンボルがグローバルであることを宣言する。 GLOBLディレクティブは対応するDATAディレクティブのあとに宣言しなければならない。

DATA    array+0(SB)/4, $"abc\z"
GLOBL   array(SB), $4

javaでファイル読み込み

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Main {

    public static void main(String[] args) {
        if (args.length < 1) {
            System.err.println("Please specify a file name.");
            System.exit(-1);
        }
        try (InputStream in = new FileInputStream(args[0])) {
            byte[] buf = new byte[4096];
            int len;

            while ((len = in.read(buf)) != -1) {
                System.out.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

CacooのAWSアイコン

Cacoo Storeで購入すると使えるようになる。 購入と言っても、0ポイントなので実際はタダ。

cacoo.com

AWSテンプレートの方を入れても、アイコンは使えるようにならなかった。

Cでディレクトリの中身を見る

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <limits.h>

#define INITSIZE 100


typedef struct entry {
    struct stat stat;
    char name[NAME_MAX];
} entry_t;

typedef struct dir {
    entry_t **entries;
    size_t count;
    size_t max_name_len;
    char *path;
} dir_t;


dir_t *dir = NULL;


dir_t* look_dir(const char* path) {
    DIR *dp;
    struct dirent *dirent;
    dir_t *dir;
    entry_t *entry;
    size_t n = INITSIZE;

    dir = malloc(sizeof(dir_t));
    dir->entries = malloc(sizeof(entry_t *) * n);
    dir->count = 0;
    dir->max_name_len = 0;
    dir->path = malloc(strlen(path)+1);
    strcpy(dir->path, path);

    dp = opendir(path);
    if (dp == NULL) {
        perror("opendir");
        exit(1);
    }
    while ((dirent = readdir(dp)) != NULL) {
        size_t len;

        len = strlen(dirent->d_name);
        if (len > dir->max_name_len) 
            dir->max_name_len = len;

        if (dir->count >= n) {
            n *= 2;
            dir->entries = realloc(dir->entries, sizeof(entry_t *) * n);
        }
        entry = malloc(sizeof(entry_t));
        strcpy(entry->name, dirent->d_name);
        if (stat(entry->name, &entry->stat) < 0) {
            perror("stat");
            exit(1);
        }
        dir->entries[dir->count++] = entry;
    }
    dir->entries[dir->count] = NULL;

    closedir(dp);
    return dir;
}

void free_dir(dir_t *dir) {
    if (dir == NULL) return;

    entry_t **pentry;
    for (pentry = dir->entries; *pentry != NULL; pentry++) {
        free(*pentry);
    }
    free(dir->entries);
    free(dir->path);
    free(dir);
}

int main(int argc, char **argv) {
    char *dirpath;
    entry_t **pentry;

    dirpath = getcwd(NULL, 0);
    dir = look_dir(dirpath);
    if (dir == NULL) {
        exit(1);
    }
    for (pentry = dir->entries; *pentry != NULL; pentry++) {
        entry_t *entry = *pentry;
        printf("%d\t%lld\t%s\n", entry->stat.st_mode, entry->stat.st_size, entry->name);
    }

    free_dir(dir);
    free(dirpath);

    return 0;
}

メモ

  • getcwd()でカレントディレクトリのパスを取得
    • バッファにNULLを渡すと、getcwdがメモリを確保して文字列を返す
      • 確保されたメモリは呼び出し側の責任でfreeしないと行けない
  • readdir()の返り値はfreeしてはいけない
  • NAME_MAXはシステムの最大のファイル名の長さ