• [lab]csapparchlab


    archlab

    该lab 要求我们在自制指令集 Y86-64 上进行编码, 并且提供一个简单的汇编器和模拟器实现.

    由于是虚拟环境, 我们解压sim文件夹后要make 构建各个目标文件, 子目录如下

    sim
      - misc    # 包含了 指令集(isa)/汇编器(yas)/模拟器(yis)
      - seq     # 顺序执行模式 hcl 的实现
      - pipe    # 流水线执行模式 hcl 的实现
      - ptest   # 测试脚本
      - y86-code # Y86-64 指令集的示例代码
    

    PartA

    要求将 example.c 中的三个函数用汇编实现, 要求实现带有main函数和设置栈顶, 我们首先从 y86-code 里拷贝出一份作为模版, 修改函数和数据即可.

    先看c语言源码

    /* linked list element */  typedef struct ELE {
     long val;
     struct ELE *next;
     } *list_ptr;
    /* sum_list - Sum the elements of a linked list */  
    long sum_list(list_ptr ls)
    {
     long val = 0;
     while (ls) {
     val += ls->val;
     ls = ls->next;
     }
     return val;
    }
    
    /* rsum_list - Recursive version of sum_list */
    long rsum_list(list_ptr ls)
    {
     if (!ls)
     return 0;
      else {
     long val = ls->val;
     long rest = rsum_list(ls->next);
     return val + rest;
     }
    }
    
    /* copy_block - Copy src to dest and return xor checksum of src */
    long copy_block(long *src, long *dest, long len)
    {
     long result = 0;
     while (len > 0) {
     long val = *src++;
     *dest++ = val;
     result ˆ= val;
     len--;
     }
     return result;
    }
    

    sum_list 顺序遍历链表求和, rsum_list 递归遍历链表求和, copy_block 拷贝数组, 并计算异或和.

    经过前面几个lab的洗礼, 还是很简单的, 大概从书上查阅一下(图4-2,4-3)支持的指令和意义就可以

    
    # long sum_list(list_ptr ele)
    # ele in %rdi
    sum_list:
      irmovq $8, %r8    # Constant 8
    	xorq %rax,%rax		# sum = 0
    	andq %rdi,%rdi		# Set condition codes
    	jmp  test
    loop:
    	mrmovq (%rdi),%r9	      # tmp = val
      addq %r9,%rax           # sum += tmp
    	addq %r8,%rdi           # ele = ele++
      mrmovq (%rdi), %rdi     # ele = [ele]
    	andq %rdi,%rdi		# Set condition codes
    test:
    	jne    loop             # Stop when 0
    	ret
    
    
    # long rsum_list(list_ptr ele)
    # ele in %rdi
    rsum_list:
      irmovq $8, %r8    # Constant 8
    	xorq %rax,%rax		# sum = 0
    	andq %rdi,%rdi		# Set condition codes
    test:
    	je    end             # Stop when 0
    	pushq %rdi						# save ele
    	addq %r8,%rdi           # ele = ele++
      mrmovq (%rdi), %rdi     # ele = [ele]
    	call rsum_list					# rsum_list(ele)
    	popq %rdi								# restore ele
    	mrmovq (%rdi),%r9	      # tmp = val
      addq %r9,%rax           # sum += tmp
    end:
    	ret
    
    # long copy_block(long *src, long* dest, long len)
    # ele in %rdi
    copy_block:
      irmovq $8, %r8    # Constant 8
    	irmovq $1, %r10	  # Constant 1
    	xorq %rax,%rax		# result = 0
    	andq %rdx,%rdx		# Set condition codes
    	jmp  test
    loop:
    	mrmovq (%rdi),%r9	      # tmp = *src
    	addq %r8,%rdi           # src++
      xorq %r9,%rax           # sum ^= tmp
    	rmmovq %r9,(%rsi)				# *dest = tmp
    	addq %r8,%rsi						# dest++
    	subq %r10,%rdx					# len--
    test:
    	jne    loop             # Stop when 0
    	ret
    
    

    PartB

    在顺序流水线中实现 iaddq, 这个指令已经在isa.c/seq-full.hcl 有定义了, 我们只需要实现一遍 iaddq 在每个指令步骤的逻辑(图4-18)即可.

    ##	iaddq V, rB
    ## 
    ##	Fetch Stage
    ##		icode: ifunc <- m_1[PC]
    ##		rA:rB <- m_1[PC+1]
    ##		valC <- m_8[PC+2]
    ##	 	valP <- PC + 10
    ##	Decode Stage
    ##		valB <- R[rB]
    ## 	Execute Stage
    ##		valE <- valB + valC
    ## 	Memory Stage  
    ##		R[rB] <- valE 
    ## 	Program Counter Update
    ##		PC <- valP
    

    在修改文件时可以对照IIRMOVQ指令, 因为IIADDQ与其十分相似, 只需要改动 aluB 和添加 set_cc 即可, alufun 因为已经默认时 ALUADD, 所以我们不需要改变它.

    之后我们可以使用编译出的ssim(顺序执行指令模拟器)来测试我们的实现是否正确.

    PartC

    要对一个c函数对应的指令代码在流水线执行环境下手动优化.

    流水线环境就是4.4中的内容, 我们还需要再实现一遍 iaddq, pipe 版多了状态传递的内容, 不过我们实现的思路可以照抄 seq 版.

    然后我们来看需要优化的函数和他的初始实现

     /** ncopy - copy src to dst, returning number of positive ints
     * contained in src array.
     */  
    word_t ncopy(word_t *src, word_t *dst, word_t len)
    { 
      word_t count = 0;
      word_t val;
      while (len > 0) {
        val = *src++;
        *dst++ = val;
        if (val > 0)
          count++;
        len--;
      }
      return count;
    }
    
    
    # You can modify this portion
    # Loop header
          xorq %rax,%rax # count = 0;
          andq %rdx,%rdx # len <= 0?
          jle Done # if so, goto Done:
    Loop: mrmovq (%rdi), %r10 # read val from src...
          rmmovq %r10, (%rsi) # ...and store it to dst
          andq %r10, %r10 # val <= 0?
          jle Npos # if so, goto Npos:
          irmovq $1, %r10
          addq %r10, %rax # count++
    Npos: irmovq $1, %r10
          subq %r10, %rdx # len--
          irmovq $8, %r10
          addq %r10, %rdi # src++
          addq %r10, %rsi # dst++
          andq %rdx,%rdx # len > 0?
          jg Loop # if so, goto Loop:
    

    首先, 因为addq需要取寄存器两次, 我们将addq全部替换成iaddq.

    在修改之后使用 make VERSION=full 编译, 然后使用 psim(并行执行指令模拟器)跑样例 sdriver.yo ldriver.yo 来测试我们的实现是否正确.

    然后我们跑脚本 correctness.pl 来过大样例, 跑 benchmark.pl 来评分, 此时的分数应该还是0分, 还需要使用循环展开(5.8)和指令重排(5.9)来加速.

    循环展开主要是并行的读取相邻元素, 降低循环的次数, 从而减少了循环变量计算和比较的次数.

    指令重拍也是为了提升指令并行执行的数量, 比如我们想将rdi的内容拷贝到rsi中, 很自然的可以写 mrmovq (%rdi), %r10rmmovq %r10, (%rsi), 但这两条指令是有依赖的, 后面需要上一条指令将%10写入后才能开始. 我们在循环展开的条件下, 可以在中间再插入一条读取指令 mrmovq 8(%rdi), %11 有效避免了依赖造成的时钟浪费.

    尽管有了思路, 但也不一定能写得出最优答案 , 在参照了blog后, 得到的代码如下

    # You can modify this portion
    	# Loop header
    	xorq %rax,%rax		# count = 0;
    	iaddq $-3, %rdx
    	jle BeforeTail		# len <= 0? if so, goto Tail:
    Extended4Loop:
    	mrmovq (%rdi), %r9 	# read val1 from src...
    	mrmovq 8(%rdi), %r10 	# read val2 from src...
    	mrmovq 16(%rdi), %r11 	# read val3 from src...
    	mrmovq 24(%rdi), %r12 	# read val4 from src...
    	rmmovq %r9, (%rsi)	# store val to dst
    	rmmovq %r10, 8(%rsi)	# store val to dst
    	rmmovq %r11, 16(%rsi)	# store val to dst
    	rmmovq %r12, 24(%rsi)	# store val to dst
    	andq %r9, %r9		# val1 <= 0?
    	jle Npos2		# if so, goto Npos:
    	iaddq $1, %rax		# count++
    Npos2:
    	andq %r10, %r10		# val2 <= 0?
    	jle Npos3		# if so, goto Npos:
    	iaddq $1, %rax		# count++
    Npos3:
    	andq %r11, %r11		# val3 <= 0?
    	jle Npos4		# if so, goto Npos:
    	iaddq $1, %rax		# count++
    Npos4:
    	andq %r12, %r12		# val4 <= 0?
    	jle Npos5		# if so, goto Npos:
    	iaddq $1, %rax		# count++
    Npos5:
    	iaddq $32, %rdi		# src+=4
    	iaddq $32, %rsi		# dst+=4
    	iaddq $-4, %rdx		# len-=4
    	jg Extended4Loop
    BeforeTail:
    	iaddq $3, %rdx
    	jle Done		#  if so, goto Done:
    	mrmovq (%rdi), %r9 	# read val from src...
    	mrmovq 8(%rdi), %r10 	# read val from src...
    	rmmovq %r9, (%rsi)	# store val to dst
    	andq %r9, %r9		# val <= 0?
    	jle Npos6		# if so, goto Npos:
    	iaddq $1, %rax		# count++
    Npos6:	
    	iaddq $-1, %rdx		# len--
    	jle Done			# if so, goto Done:
    	rmmovq %r10, 8(%rsi)	# store val to dst
    	andq %r10, %r10		# val <= 0?
    	jle Npos7		# if so, goto Npos:
    	iaddq $1, %rax		# count++
    Npos7:
    	iaddq $-1, %rdx		# len--
    	jle Done			# if so, goto Done:
    	mrmovq 16(%rdi), %r11 	# read val from src...
    	rmmovq %r11, 16(%rsi)	# store val to dst
    	andq %r11, %r11		# val <= 0?
    	jle Done		# if so, goto Npos:
    	iaddq $1, %rax		# count++
    

    结果和博客一样都是48.6, 自己在实现的过程中忽略了movq可以变址读取, 没有利用到循环展开的优势, 且计算循环展开的剩余部分比较复杂, 直接-3, 再加3, 这样可以保证进入结束部分时len就是原来 len 对4的余数, 能比直接计算余数减少2-3个指令.

    What‘s more

    这个lab提供的文件给出了一个完成的指令集设计和实现, 也是非常好的学习示例.

    我们来看PartA, 利用Lex&Yaccyas,hcl2c相关的部分和实现.

    Lex&Yacc 是一组生成软件, 它接受自定义的语法规则, 生成对应规则的解析器. Flex&Bison 是Linux环境中对他们的一组实现, 更多知识可以参考这个, 简单来说 Lex就是分词器, Yacc则接受分词, 根据自定义的语法规则生成解析代码(就是我们的编译器).

    我们来列举下misc中需要关注的文件

    Makefile # 编译 target 的命令
    
    
    isa.h/c
    
    yas.h/c
    yas-grammar.lex # 用于生成yas
    
    yis.h/c         # yis
    
    node.h/c        # 编译节点定义
    outgen.h/c      # 打印输出的代码
    hcl.lex/y       # hcl
    
    

    isa 里有

    • 寄存器定义
    • 指令编码
    • 指令描述表
    • 内存操作
    • 寄存器操作
    • ALU操作 : 计算/更新和获取状态字
    • 指令状态 stat_t
    • 虚拟机状态: state_rec, 指令跳转, 指令执行

    首先是 yas, 它是Y86的汇编器, 负责从汇编代码到机器指令(ys->yo)这一步, 依赖了yas-grammar , isa

    yas-grammar.o: yas-grammar.c
    	$(CC) $(LCFLAGS) -c yas-grammar.c
    
    yas-grammar.c: yas-grammar.lex
    	$(LEX) yas-grammar.lex
    	mv lex.yy.c yas-grammar.c
    
    isa.o: isa.c isa.h
    	$(CC) $(CFLAGS) -c isa.c
    
    yas.o: yas.c yas.h isa.h
    	$(CC) $(CFLAGS) -c yas.c
    
    yas: yas.o yas-grammar.o isa.o
    	$(CC) $(CFLAGS) yas-grammar.o yas.o isa.o ${LEXLIB} -o yas
    
    

    yas.h 内容如下,

    void save_line(char *);
    void finish_line();
    void add_reg(char *);
    void add_ident(char *);
    void add_instr(char *);
    void add_punct(char);
    void add_num(long long);
    void fail(char *msg);
    unsigned long long atollh(const char *);
    
    
    /* Current line number */
    int lineno;
    
    

    yas-grammar.lex 内容如下,

    /* Grammar for Y86-64 Assembler */
     #include "yas.h"
    
    Instr         rrmovq|cmovle|cmovl|cmove|cmovne|cmovge|cmovg|rmmovq|mrmovq|irmovq|addq|subq|andq|xorq|jmp|jle|jl|je|jne|jge|jg|call|ret|pushq|popq|"."byte|"."word|"."long|"."quad|"."pos|"."align|halt|nop|iaddq
    Letter        [a-zA-Z]
    Digit         [0-9]
    Ident         {Letter}({Letter}|{Digit}|_)*
    Hex           [0-9a-fA-F]
    Blank         [ \t]
    Newline       [\n\r]
    Return        [\r]
    Char          [^\n\r]
    Reg           %rax|%rcx|%rdx|%rbx|%rsi|%rdi|%rsp|%rbp|%r8|%r9|%r10|%r11|%r12|%r13|%r14
    
    %x ERR COM
    %%
    
    ^{Char}*{Return}*{Newline}      { save_line(yytext); REJECT;} /* Snarf input line */
    #{Char}*{Return}*{Newline}      {finish_line(); lineno++;}
    "//"{Char}*{Return}*{Newline}     {finish_line(); lineno++;}
    "/*"{Char}*{Return}*{Newline}   {finish_line(); lineno++;}
    {Blank}*{Return}*{Newline}      {finish_line(); lineno++;}
    
    {Blank}+          ;
    "$"+              ;
    {Instr}           add_instr(yytext);
    {Reg}             add_reg(yytext);
    [-]?{Digit}+      add_num(atoll(yytext));
    "0"[xX]{Hex}+     add_num(atollh(yytext));
    [():,]            add_punct(*yytext);
    {Ident}           add_ident(yytext);
    {Char}            {; BEGIN ERR;}
    <ERR>{Char}*{Newline} {fail("Invalid line"); lineno++; BEGIN 0;}
    %%
    
    unsigned int atoh(const char *s)
    {
        return(strtoul(s, NULL, 16));
    }
    
    

    我们可以看出yas的编译逻辑是按行编译, 解析出token就将他们加入到当前行中, 当前行结束时调用finish_line 产生一条指令.

    hcl略微复杂, 它通过.lex文件先生成token流, 但是交给 .y 文件定义的语法规则来处理, 从而能处理更复杂的hcl语法, 将他们生成c/Verilog文件.

  • 相关阅读:
    Linux之网络ping(unknown host)故障及yum no more mirrors to try
    vim 中与编码有关的选项
    linux系统时间设定
    linux查看物理cpu的核数,个数,逻辑cpu的个数
    简单的api实现以及动态函数调用
    python2.7 urllib和urllib2
    四级菜单实现二
    四级菜单实现一
    url请求特殊字符转换
    tomcat配置
  • 原文地址:https://www.cnblogs.com/xxrlz/p/16073560.html
Copyright © 2020-2023  润新知