实现简易的C语言编译器(part 11)

        上一部分,我们算是对汇编语言开了个头,介绍了基本操作指令相关的内容。这一部分,我们重点研究汇编语言的另外一块内容:栈帧结构

7.3 栈帧结构

        大多数机器只提供简单的指令,数据传递、局部变量的分配和释放需要通过操纵程序栈来实现,而为单个过程分配的那部分栈就成为栈帧。先来一张典型的结构图,直观感受一下:


图7-2 栈帧结构

        在函数体内部,我们将本地变量和临时变量存储在当前帧下,也将被调用的函数的参数放进当前函数的栈帧中,并按照顺序往上生长。当调用其它函数时,按照如图所示的顺序传进实际参数,返回地址则被压入栈中,形成调用者栈帧的末尾。对于调用者帧区而言,它代表了对应的调用函数的定义所在的帧区,同当前帧的结构是完全相同的。具体的细节,大家可以参见任何一本讲解汇编语言的书籍。
        有了这些知识之后,我们重点关注几类寄存器以及特殊情况的处理。

7.3.1 参数寄存器

        x86-64架构下,前六个参数对应着具体的寄存器,如图7-1所示,只需要往对应的寄存器传入实际的参数即可。对于超过六个参数的函数而言,多余的参数则需要按照图7-2所示的结构,逐个的分配地址并传递实际参数,相当于参数1成为了第7个函数参数,往上依次类推。
        由于寄存器有限,参数寄存器也会被当成常规寄存器使用,为了避免数据丢失,对于当前仍然被使用着的参数寄存器而言,需要先行保存,调用结束之后再恢复原来的数据。这时,也用栈来保存当前的数据。

7.3.2 调用者和被调用者保存寄存器

        在函数体中,我们经常会调用别的函数,这些函数在自己函数体内部的处理过程中,往往也只有这些寄存器可以利用,势必会出现调用者和被调用者使用同一个寄存器的问题。为了防止数据被覆盖,我们还是会提前保存这些寄存器,调用结束之后再恢复。于是,我们约定俗成了两组寄存器,称为调用者保存寄存器和被调用者保存寄存器,分别由调用者和被调用者单独保存。为此,我们需要在操作数管理器中体现这样的一个特点:

    def __init__(self):
        ...
        self.callee_save_regs_used = []

        self.caller_save_regs = [R10, R11]
        self.callee_save_regs = [Rbx, Rbp]

    def save_caller_saves(self):
        for reg in self.caller_save_regs:
            if reg not in self.regs_free:
                self.copy_reg_to_temp(reg)
                self.regs_free.append(reg)

    def save_callee_saves(self):
        for reg in self.callee_save_regs_used:
            return f"push {reg.to_str('pointer')}"

    def restore_callee_saves(self):
        for reg in self.callee_save_regs_used:
            return f"pop {reg.to_str('pointer')}"

    def get_free_reg(self, valid_regs=None, preferred_reg=None):
        if len(self.regs_free) > 0:
           ...
            if reg is not None:
                self.remove_free_reg(reg)
                return reg

    def remove_free_reg(self, reg):
        self.regs_free.remove(reg)
        if reg in self.callee_save_regs and \ 
                       reg not in self.callee_save_regs_used:
            self.callee_save_regs_used.append(reg)

remove_free_reg中,我们会将当前被使用的被调用者寄存器加入到callee_save_regs_used中保存。此时,如果我们调用函数,通过在前后调用save_callee_savesrestore_callee_saves就能实现寄存器被被调用者保存和恢复的功能。对于调用者负责保存的寄存器而言,我们会将正在使用的寄存器通过copy_reg_to_temp函数将对应的内容先保存到临时地址上(我们会在下面详细介绍),然后再让寄存器变成可以使用的状态。

7.3.3 寄存器的特殊处理

        为了减少寄存器的使用,防止可能存在的寄存器缺乏引起的数据迁移,我们会为每个用到的局部变量分配地址,这些地址具有相对于帧指针具体的偏移量。因此,可以直接使用帧指针的相对偏移量进行操作,减少了栈指针的移动。但是,由于汇编语言规定,操作指令的两个操作数不能同时为存储器。这时候,就需要将存储器的内容临时提取到寄存器,再进行操作。如果寄存器满负荷运转,则需要使用栈指针后(往低地址方向)的地址存储寄存器中的值,之后再恢复数据。为此,我们需要修改一下操作数管理器来实现这样的一个目的:

class FrameManager:
    WORD_SIZE = 8

    def __init__(self, parent):
        ...
        self.mem_free = []
        self.next_temp = 0

    def set_base_fp(self, base_fp):
        self.next_temp = base_fp - self.WORD_SIZE

    def copy_reg_to_temp(self, valid_reg):
        if len(self.mem_free) == 0:
            self.mem_free.append(MemoryFrame(f"(%rsp)", self.next_temp))
            self.next_temp -= self.WORD_SIZE

        mem = self.mem_free.pop()

        find_reg = False
        for index, item in enumerate(self.stack):
            if item == valid_reg:
                self.stack[index] = mem
                find_reg = True
                break
        if not find_reg:
            comment = f"No free registers inside OR outside of stack!"
            raise CompilerError(comment)

        return valid_reg

    def get_free_reg(self, preferred_reg=None):
        if len(self.regs_free) > 0:
            ...
        return self.copy_reg_to_temp(self.regs_almost_free[0])

    def get_max_fp(self):
        return self.next_temp + self.WORD_SIZE

get_free_reg函数中,如果我们无法得到一个可用的寄存器,就会将已经使用的某个寄存器中的值放进临时区域,并替换栈中对应的寄存器。这里,我们使用next_temp来偏移得到存储寄存器中临时变量的地址,并保存到mem_free
        至此,我们扩展和完善了操作数管理器。有了这些准备工作,我们将在下一部分为源代码生成汇编代码。

实现简易的C语言编译器(part 0)
实现简易的C语言编译器(part 1)
实现简易的C语言编译器(part 2)
实现简易的C语言编译器(part 3)
实现简易的C语言编译器(part 4)
实现简易的C语言编译器(part 5)
实现简易的C语言编译器(part 6)
实现简易的C语言编译器(part 7)
实现简易的C语言编译器(part 8)
实现简易的C语言编译器(part 9)
实现简易的C语言编译器(part 10)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容