Post

Support multi-letter local variables

这个commit是上一个《Support single-letter local variables》的升级完善。上一个commit基本已经搭好了大体的框架,此次修改也不是很大。

知识点

支持多字母变量面临主要的问题还是:如何分配变量地址、如何寻址。还好基本他在 parse解决了。

一.tokenize修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Token *tokenize(void) {
    //...
    // Identifier
    if (is_alpha(*p)) {
      char *q = p++;
      while (is_alnum(*p))
        p++;
      cur = new_token(TK_IDENT, cur, q, p - q);
      continue;
    }

    // Multi-letter punctuators
    //...
}

Identifier典型的字母开头后续允许数字、字母、下划线的

二.parse、main修改

允许多字母变量那么就不能再用固定分配26个变量的方式,需要构建一张变量表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//parse.c
Var *locals;//变量链表
static Var *find_var(Token *tok) {
  for (Var *var = locals; var; var = var->next)
    if (strlen(var->name) == tok->len && !strncmp(tok->str, var->name, tok->len))
      return var;
  return NULL;
}
static Node *new_var_node(Var *var) {
  Node *node = new_node(ND_VAR);
  node->var = var;                      //Node节点持有Var信息(上次commit中Node节点的name替换成var)
  return node;
}

static Var *new_lvar(char *name) {      //构建变量链表
  Var *var = calloc(1, sizeof(Var));
  var->next = locals;
  var->name = name;
  locals = var;
  return var;
}
static Node *primary(void) {
  //...

  Token *tok = consume_ident();
  if (tok) {
    //查找变量是否在变量表里,不在则新建并添加到链表里
    Var *var = find_var(tok);
    if (!var)
      var = new_lvar(strndup(tok->str, tok->len));
    return new_var_node(var);
  }

  return new_num(expect_number());
}

那么又是怎么计算出变量在栈上的偏移和需要分配多少栈空间呢? 第一个疑问的解决方案是在Var 持有offset通过其在链表的位置赋值一个n*8的值;分配的栈空间大小那么就是链表总变量数*8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//chibi.h
struct Var {
  Var *next;
  char *name; // Variable name
  int offset; // Offset from RBP
};

//main.c
int main(int argc, char **argv) {
  //...
  Function *prog = program();

  int offset = 0;
  for (Var *var = prog->locals; var; var = var->next) {
    offset += 8;
    var->offset = offset;
  }
  prog->stack_size = offset;

  // Traverse the AST to emit assembly.
  codegen(prog);
  return 0;
}

上面的函数program改成返回的是Function struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//chibi.h
struct Function {
  Node *node;
  Var *locals;
  int stack_size;
};

//parse.c
Function *program(void) {
  locals = NULL;
  //...
  Function *prog = calloc(1, sizeof(Function));
  prog->node = head.next;
  prog->locals = locals;
  return prog;
}

Function除了持有parse出来的node链表、还有变量表和计算出来的要分配的栈空间大小。

三.codegen修改

由于 node节点持有变量分配在栈上的offset、且要分配的栈大小在 main中都提前计算好了,这次 codegen 修改实际非常小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void gen_addr(Node *node) {
  //...
    printf(" lea rax, [rbp-%d]\n", node->var->offset);
  //...
}

void codegen(Function *prog){
  //...
  printf("  sub rsp, %d\n", prog->stack_size);

  // Emit code
  for (Node *node = prog->node; node; node = node->next)
    gen(node);

  //..
}
This post is licensed under CC BY 4.0 by the author.