TCG基本原理

加速器 -> DBT(dynamic binary translation) 动态二进制转换,在程序运行时,讲Guest指令转化为Host指令。一般翻译程序执行过的路径不会全量翻译,可以对热点代码进行深度优化。

TCG IR -> 兼容性产物 类似llvm 优势在于兼容性更好,可以轻松支持新前端 只需要实现source -> IR 易于流程化:类似llvm,可以引入各种pass,对不同环节进行优化 For bad: 性能普遍不高 因为引入了中间层

Translation Block TCG的二进制转换是以代码块(Basic Block)为基本单元的,翻译产物为Translation Block Basic Block的划分依据是:分支跳转,特权指令/异常,代码段跨页 Translation Block结构类似如下,由三部分组成 prologue + Translation Block + epilogue 前言+翻译块+末言 前言和末言主要负责上下文的切换

为了解决频繁的上下文切换问题,引入了Direct block chaining这个技术 执行过的代码块,在执行好之后将这个代码块的Translation Block的末尾链接到下一个翻译好的代码块的前面,可以节省一个epilogue + prologue 注意两个 chaining block需要在同一个Guest page

Code Buffer这是一个用来存储翻译好的TB块的地方 在qemu启动的早期会执行一个叫tcg_init_machine,在这个函数里会完成code_buffer的初始化操作

code_buffer结构: prologue + epilogue + TB.struct + TB.code + ... 其中有一个TCGContext.code_ptr指针,这个指针指向下一个空闲的TB.code地址,类似数组的idx 这里TB.code开始就是TB块存储的地方,这里保存着prologue和epilogue的代码/指针,由于prologue和epilogue的代码都一致所以只需要翻译一次 通常查找buffer只需要有TB.struct的指针,因为后面偏移处就可以找到TB.code 后续所有代码的翻译和执行的工作都围绕着code_buffer展开 TCGContext的后端管理工作也围绕着code_buffer展开

Code Buffer details

先来看tcg_init_machine

static int tcg_init_machine(MachineState *ms)
{
    TCGState *s = TCG_STATE(current_accel()); // TCG state
#ifdef CONFIG_USER_ONLY
    unsigned max_cpus = 1;
#else
    unsigned max_cpus = ms->smp.max_cpus;
#endif

    tcg_allowed = true;
    mttcg_enabled = s->mttcg_enabled; // muti thread tcg

    page_init(); // relate with MUU
    tb_htable_init(); // translation block hash table init
    tcg_init(s->tb_size * MiB, s->splitwx_enabled, max_cpus);

#if defined(CONFIG_SOFTMMU)
    /*
     * There's no guest base to take into account, so go ahead and
     * initialize the prologue now.
     */
    tcg_prologue_init(); // prologue init
#endif

#ifdef CONFIG_USER_ONLY
    qdev_create_fake_machine();
#endif

    return 0;
}

这是一个初始化流程,分别初始化page,tb_hashtable,tcg_prologue等东西

在这其中还有一个tcg_init

传入的参数有三个tb_sizesplitwxmax_cpus

void tcg_init(size_t tb_size, int splitwx, unsigned max_cpus)
{
    tcg_context_init(max_cpus); // 跟多线程有关 跳过
    tcg_region_init(tb_size, splitwx, max_cpus);
}

跟进tcg_region_init ,这个很可能就是我们初始化的code_buffer

截取前面的一小部分:

void tcg_region_init(size_t tb_size, int splitwx, unsigned max_cpus)
{
    const size_t page_size = qemu_real_host_page_size();
    size_t region_size;
    int have_prot, need_prot;
    // Calculating translation block size base on host physical memory
    /* Size the buffer.  */
    if (tb_size == 0) {
        size_t phys_mem = qemu_get_host_physmem();
        if (phys_mem == 0) {
            tb_size = DEFAULT_CODE_GEN_BUFFER_SIZE;
        } else {
            tb_size = QEMU_ALIGN_DOWN(phys_mem / 8, page_size);
            tb_size = MIN(DEFAULT_CODE_GEN_BUFFER_SIZE, tb_size);
        }
    }
    if (tb_size < MIN_CODE_GEN_BUFFER_SIZE) {
        tb_size = MIN_CODE_GEN_BUFFER_SIZE;
    }
    if (tb_size > MAX_CODE_GEN_BUFFER_SIZE) {
        tb_size = MAX_CODE_GEN_BUFFER_SIZE;
    }

    have_prot = alloc_code_gen_buffer(tb_size, splitwx, &error_fatal); // use mmap to alloc memory
    assert(have_prot >= 0);

这里的alloc_code_gen_buffer 最后会去调用alloc_code_gen_buffer_anon

static int alloc_code_gen_buffer_anon(size_t size, int prot,
                                      int flags, Error **errp)
{
    void *buf;

    buf = mmap(NULL, size, prot, flags, -1, 0);
    if (buf == MAP_FAILED) {
        error_setg_errno(errp, errno,
                         "allocate %zu bytes for jit buffer", size);
        return -1;
    }

    region.start_aligned = buf; // static struct tcg_region_state region;
    region.total_size = size;
    return prot;
}

这里的size是指整个translation block的大小

他会用这样一个结构体去记录申请好的内存和大小

/*
 * We divide code_gen_buffer into equally-sized "regions" that TCG threads
 * dynamically allocate from as demand dictates. Given appropriate region
 * sizing, this minimizes flushes even when some TCG threads generate a lot
 * more code than others.
 */
struct tcg_region_state {
    QemuMutex lock;

    /* fields set at init time */
    void *start_aligned;
    void *after_prologue;
    size_t n;
    size_t size; /* size of one region */
    size_t stride; /* .size + guard size */
    size_t total_size; /* size of entire buffer, >= n * stride */

    /* fields protected by the lock */
    size_t current; /* current region index */
    size_t agg_size_full; /* aggregate size of full regions */
};

这个空间是code gen buffer

CPU exec loop过程