mips tlb refill的编译器设计

mips的tlbrefill和tlb load exception处理函数,都是用自己的一套编译器来实现的。

这么做官方的解释是为了兼容,想想也是,只需要修改一下指令表就可以做到了。

编译器的核心在arch/mips/mm/uasm.c

包括关键指令码生成辅助函数build_insn,以及关键数据insn_table

build_insn的作用是根据传入的op操作指示,以及op附带的参数,比照模板insn_table,去生成一个实际的指令码。

 

以addu为例,预备数据结构:

enum opcode {
 insn_invalid,
 insn_addu,

 

struct insn {
 enum opcode opcode;
 u32 match;
 enum fields fields;
};

 

/* This macro sets the non-variable bits of an instruction. */
#define M(a, b, c, d, e, f)     \
 ((a) << OP_SH      \
  | (b) << RS_SH      \
  | (c) << RT_SH      \
  | (d) << RD_SH      \
  | (e) << RE_SH      \
  | (f) << FUNC_SH)

static struct insn insn_table[]

{ insn_addiu, M(addiu_op, 0, 0, 0, 0, 0), RS | RT | SIMM },
 { insn_addu, M(spec_op, 0, 0, 0, 0, addu_op), RS | RT | RD },

 

首先,I_u3u1u2(_addu)定义了一个函数

uasm_i_addu(u32 **buf, unsigned int a, unsigned int b, unsigned int c)

{

build_insn(buf, insn_addu, b, c, a);

}

来看build_insn的实现

首先,是从insn_table里找出数组结构体成员的第一个分量等于insn_addu的元素ip;

接着,取ip的第二个分量的初始值op = ip->match,这个值的保存最终的指令码,先填充初始值的意思是,

先把addu这条指令需要的辨别码写入,而其余的RS,RT,RD位则根据传入b,c,a来填充;

具体的填充流程即一个分支判断,

由于addu所在的数组元素具有RS | RT | RD属性,则会走下面的流程

if (ip->fields & RS)
  op |= build_rs(va_arg(ap, u32));
 if (ip->fields & RT)
  op |= build_rt(va_arg(ap, u32));

 if (ip->fields & RD)
  op |= build_rd(va_arg(ap, u32));

build_rd等函数则将参数写入具体的指令码位置,最后保存在编译器缓冲里

**buf = op;

(*buf)++;

使用的时候,则调用uasm_i_addu,按照格式填写即可。

 

假如我们希望在tlb refill里添加每cpu统计计数,则可以在

build_r4000_tlb_refill_handler添加:

unsigned long cpu_tlb_miss[NR_CPU];

 uasm_i_dmfc0(p, K0 C0_CONTEXT);
 uasm_i_dsrl(p, K0, K0, 23);

UASM_i_LA(p, K1, cpu_tlb_miss);

uasm_i_daddu(p, K1, K0, K1);

uasm_i_ld(p, K0, 0, K1);

uasm_i_daddiu(p, K0, K0, 1);

uasm_i_sd(p, K0, 0, K1);

 

 

 

你可能感兴趣的:(数据结构,exception,struct,table,Build,编译器)