LoginSignup
1
1

More than 5 years have passed since last update.

セルフホスティングコンパイラ作成記事を写経したメモ

Posted at

一日でできるセルフホスティングForthコンパイラ

上記記事に触発されて18時頃から(途中で飯や風呂を挟みながら)
3~4時間程度で作業した結果のメモです。理解することは後回しに、とりあえず作業をしました。

環境

$ uname -s
MSYS_NT-6.1
$ uname -m
x86_64
$ gcc --version
gcc (GCC) 6.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

記事を読みながらひたすら写経をしました。コピペなどはしてないので途中入力ミスなどがあったかもしれません。

記事途中「data stack」の項ですが

test.c
#include"forth0.c"

int main() {
  init();

  // 42 を push する word
  begin_def("lit42", 0);
  B(0x48),B(0x83),B(0xeb),B(0x08); // SUB RBX, 8
  B(0x48),B(0xc7),B(0x03),D(42);   // MOV QWORD PTR [RBX], 42
  B(0xc3); // RET
  end_def();

  execute(find_word("lit42"));
  printf("%"PRId64, sp[0]);

  return 0;
}

上記コードをコンパイルしてみるとセグフォが起きました。

$ ./a.exe
Segmentation fault (core dumped)

ここだけ一旦コードをコピペして再度コンパイルしてみましたが、セグフォの結果は変わりませんでした。
コンパイラのバージョン違いが怪しい気がしますが、原因探ると時間が掛かりそうなので後回しにしています。

次、「text interpreter を作る」の項目ですが、

$ gcc test.c && echo hello world | ./a

何も出ませんでした。
これも原因は時間が無くて探れてませんが、また週末にでも続きをやってみます。

最後に、ここまでのコードを貼っておきます。

forth0.c
#include<stdio.h>
#include<stdlib.h>
#include<inttypes.h>
#include<windows.h>
#include<sys/stat.h>

#define import  (mem+0x200)
#define import_limit    (mem+0x300)
#define startup (mem+0x300)
#define startup_limit   (mem+0x320)
#define c_to_ft (mem+0x320)
#define c_to_ft_limit   (mem+0x330)
#define word_definitions        (mem+400)

#define ftmain  (*(uint64_t *)(mem+0x3a8))
#define state   (*(uint64_t *)(mem+0x3b0))
#define fin     (*(FILE **)(mem+0x3b8))
#define token   ((char *)(mem+0x3c0))
#define mrd1    (*(uint8_t **)(mem+0x3e0))
#define mrd2    (*(uint8_t **)(mem+0x3e8))
#define ep      (*(uint8_t **)(mem+0x3f0))

#define WORD_SIZE(word) (((uint64_t *)(word))[-1])
#define WORD_HEAD(word) ((uint8_t *)(word)-WORD_SIZE(word))
#define WORD_NAME(word) ((char *)WORD_HEAD(word))
#define WORD_IMMEDIATE(word) (*(uint64_t *)(WORD_HEAD(word)+32))
#define WORD_BODY(word) (WORD_HEAD(word)+40)
#define WORD_PREV(word) ((uint8_t *)(word)-WORD_SIZE(word))

#define B(b) (*(uint8_t *)ep=(uint8_t)(b),ep+=1)
#define D(d) (*(uint32_t *)ep=(uint32_t)(d),ep+=4)
#define Q(q) (*(uint64_t *)ep=(uint64_t)(q),ep+=8)

static uint8_t *mem;
static uint8_t *sp;

static void begin_def(const char *name, int immediate) {
    ep = mrd2;
    strncpy((char *)ep, name, 32); ep +=32;
    Q(immediate);
}

static void end_def(void) {
    Q(ep - mrd2 + 8);
    mrd2 = ep;
    ep = 0;
}

static uint8_t *find_word(const char *name) {
    uint8_t *word = mrd2;
    while (WORD_SIZE(word)) {
        if (!strcmp(WORD_NAME(word), name)) return word;
        word = WORD_PREV(word);
    }

    return 0;
}

static void def_cfun(const char *name, void *cfun, int immediate) {
    begin_def(name, immediate);
    B(0x48), B(0x89), B(0xe5);
    B(0x48), B(0x83), B(0xec), B(0x20);
    B(0x48), B(0x83), B(0xe4), B(0xf0);
    B(0x48), B(0xb8), Q(cfun);
    B(0xff), B(0xd0);
    B(0x48), B(0x89), B(0xec);
    B(0xc3);
    end_def();
}

static void execute(uint8_t *word) {
    // RCXに native code へのポインタを代入し、
    // RDXに stack pointer を代入し、
    // c_to_ft を実行する。
    // 返ってきたあとは sp に RAX の値を代入する。
    sp = ((uint8_t *(*)(uint8_t *,uint8_t *))c_to_ft)(WORD_BODY(word), sp);
}

static void write_hex(uint8_t *outp, uint8_t *limit, const char *data) {
    for(int i = 0; data[i]; i += 3, ++outp) {
        if (limit <= outp) {
            printf("error: too many data: write_hex\n");
            exit(EXIT_FAILURE);
        }
        *outp = strtol(&data[i], 0, 16);
    }
}

static void parse_name(void) {
    token[0] = '\0';
    fscanf(fin, "%31s%*[^ \t\n\r]", token);
    getc(fin);
}

static void perform_compilation_semantics(uint8_t *word) {
    if (WORD_IMMEDIATE(word)) {
        execute(word);
    } else {
        B(0xe8), D(WORD_BODY(word) - (ep + 4));
    }
}

static void perform_interpretation_semantics(uint8_t *word) {
    execute(word);
}

static void text_interpreter(void) {
    while (1) {
        parse_name();
        if (token[0] == '\0') return;

        uint8_t *word = find_word(token);
        if (word) {
            if(state) {
                perform_compilation_semantics(word);
            } else {
                perform_interpretation_semantics(word);
            }
            continue;
        }

        char *p;
        long long i = strtoll(token, &p, 0);
        if (!*p) {
            if (state) {
                B(0x48), B(0x83), B(0xeb), B(0x08);
                B(0x48), B(0xb8), Q(i);
                B(0x48), B(0x89), B(0x03);
            } else {
                sp -= 8;
                *(int64_t *)sp = i;
            }
            continue;
        }

        printf("undefined word: %s\n", token);
        exit(EXIT_FAILURE);
    }
}

void init() {
    mem = VirtualAlloc(0, 640 * 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    mrd2 = word_definitions;
    sp = mem + 640 * 1024;
    static const char *c_to_ft_image =
        "53 "
        "55 "
        "48 89 d3 "
        "ff d1 "
        "48 89 d8 "
        "5d "
        "c3 "
        ;
    write_hex(c_to_ft, c_to_ft_limit, c_to_ft_image);
}
test.c
#include"forth0.c"

static void hello(void) { printf("hello, "); }
static void world(void) { printf(", "); }

int main() {
  init();

  def_cfun("hello", hello, 0);
  def_cfun("world", hello, 0);

  fin = stdin;
  text_interpreter;

  return 0;
}
1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1