Skip to content

Commit 53becba

Browse files
committed
完成运行时内容
1 parent 8133e97 commit 53becba

24 files changed

+203
-249
lines changed

04_运行时刻环境/README.md

Lines changed: 55 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
```
2-
3-
41
*前言:
52
从词法分析,到语法分析,到三地址代码生成,这个几个阶段的本质还只是对字符串的转换,是静态编译阶段。而运行时指的是程序执行顺序、执行环境、内存动态分配等内容。
63
运行时刻跟编译时刻虽然是两个不同的阶段,但是由于运行时刻用到的指令是由编译器生成的,要使编译器生成正确指令,必需对程序运行时有足够的了解。如此才能解决在前面一节《03_中间代码生成》的三地址表达式实例2中提出的问题,才能生成能够精确翻译为机器码的中间码。
@@ -18,13 +15,23 @@
1815
-- 这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求
1916
-- 常用的静态存储分配方法:
2017
-- 顺序分配法:
21-
假设一个程序生成了6个过程,树表示过程间的调用关系:https://www.pianshen.com/article/13011737518/
18+
假设一个程序生成了6个过程,树表示过程间的调用关系:
2219
1/22
23-
/ \
24-
2/15 3/18
25-
/ \ / \
26-
27-
-- 层次分配法
20+
/ \
21+
2/15 3/18
22+
/ \ / \
23+
4/17 6/10 5/23
24+
过程 存储区域
25+
1 0~21
26+
2 22~36
27+
3 37~54
28+
4 55~71
29+
5 72~94
30+
6 95~104
31+
特点:按照过程出现的先后顺序逐段分配存储空间;各过程的活动记录占用互不相交的存储空间
32+
优点:处理上简单
33+
缺点:对内存空间的使用不够经济合理
34+
-- 层次分配法:https://www.pianshen.com/article/13011737518/
2835
-- 实现静态存储分配:
2936
-- 编译程序对源程序进行处理时,对每个变量在符号表中创建一个记录,保存该变量的属性,其中包括为变量分配的存储空间地址即目标地址
3037
-- 由于每个变量需要的空间大小已知,则可将数据区开始位置的地址A分配给第一个变量,设第一个变量占n1个字节,则A + n1分配给第二个变量。同理,A + n1 + n2分配给第三个变量等等
@@ -79,6 +86,9 @@
7986
-- 内核:内核区是所有进程共享的
8087

8188

89+
90+
91+
8292
*栈式存储分配:
8393
·活动记录
8494
-- 使用过程(或函数、方法)作为用户自定义动作的单元的语言,其编译器通常以过程为单位分配存储空间
@@ -88,6 +98,7 @@
8898
-- 当一个过程被调用时,该过程的活动记录被压入栈;当过程结束时,该活动记录被弹出栈。
8999
-- 栈式存储不仅允许活跃时段不交叠的多个过程调用之间共享空间,而且允许以如下方式为一个过程编译代码:它的非局部变量的相对地址总是固定的
90100
·寄存器与函数栈帧
101+
-- 栈帧:函数调用经常是嵌套的,在同一时刻,堆栈中会有多个函数的信息。每个未完成运行的函数占用一个独立的连续区域,称作栈帧(Stack Frame)
91102
-- 每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。Win32系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。
92103
-- ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶,当我们往栈内添加数据时,ESP就会往上移动该数据的大小。
93104
-- EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部
@@ -134,7 +145,6 @@
134145
-- 数据类型:
135146
-- 基本类型:基本数据类型都储存在栈中
136147
-- 引用类型:引用类型的值是储存在堆中,栈内存中保存着一个堆内存的对象的引用
137-
138148
·作用域
139149
-- 作用域分为:词法作用域(也叫静态作用域)和环境作用域(也叫执行时作用域/动态作用域)。
140150
-- js遵循词法作用域
@@ -160,33 +170,36 @@
160170
var b=a+1 p1=p0+1 p1 a sp+4
161171
a=p1 a p1 sp+8
162172
b=a+1 b p2 sp+12
163-
164173
·三地址表达式实例2(斐波那契函数)栈的活动记录图示:
165-
程序执行时,首先将opcode码(为了易于理解,我们这里讨论的opcode用三地址码表示)载入内存,存到代码段。代码开始执行时,cpu至上而下读取opcode,直到读取到call f这一行,此时将5压栈,代码段的指针指向代码段的f函数体的第一行(即f行)并开始逐行往下执行。通过栈的指针偏移和代码段的指针偏移,就可以拿到各个数据的地址了
166-
栈空间(通过指针偏移拿到数据) 代码段(通过行号值拿到数据)
167-
| link7 |
168-
//link7会指向数字2 | f |
169-
| 2 | | t1=n==1 |
170-
| link6 | | t2=n==2 |
171-
| link5 | |t3=t1 or t2|
172-
|其他临时变量| | |
173-
| 返回值3 | |branch goto|
174-
| 3 | | ... |
175-
| link4 | | |
176-
| link3 | | |
177-
|其他临时变量| | |
178-
| 返回值2 | | |
179-
| 4 | | |
180-
| link2 | | |
181-
//link2会指向数字5 | |
182-
| link1 | | |
183-
//link1会指向代码call f | |
184-
|其他临时变量| | |
185-
| 返回值1 | | |
186-
//返回值可根据5的偏移量计算拿到 | |
187-
//通过push空为返回值1占位 | call f |
188-
| 5 | //call f将指向f函数体的第一行
189-
------------ --------------
174+
栈空间(通过指针偏移拿到数据) 代码段(通过行号值拿到数据)
175+
| link7 |
176+
//link7会指向数字2 | f |
177+
| 2 | | t1=n==1 |
178+
| link6 | | t2=n==2 |
179+
| link5 | |t3=t1 or t2|
180+
|其他临时变量| | |
181+
| 返回值3 | |branch goto|
182+
| 3 | | ... |
183+
| link4 | | |
184+
| link3 | | |
185+
|其他临时变量| | |
186+
| 返回值2 | | |
187+
| 4 | | |
188+
| link2 | | |
189+
//link2会指向数字5(即上一个栈帧 ) | |
190+
| link1 | | |
191+
//link1会指向代码call f | |
192+
|其他临时变量| | |
193+
| 返回值1 | | |
194+
//返回值可根据5的偏移量计算拿到 | |
195+
//通过push空为返回值1占位 | call f |
196+
| 5 | //call f将指向f函数体的第一行
197+
------------ --------------
198+
运行时分析:
199+
程序执行时,操作系统首先会开辟一个内存空间(具体参考《递归语言的运行时内存划分.jpg》),并将二进制机器码(为了易于理解,我们这里讨论的机器码用三地址码表示)载入存到开辟的内存的代码段,此时CS(Code Segment,代码段寄存器)指向该代码段的基址。
200+
通过CS:IP(即Instruction Pointer,指令指针寄存器,即代码段CS对应的偏移指针)的指向从内存中取出下一条执行的指令,产生如下流程:取出的指令装入CPU的指令寄存器 → 执行指令 → IP++(即指向下一条指令了)或 通过JMP等跳转指令重新修改CS:IP而该表下一条指令的位置 → 重复第一步(循环)。
201+
当然,在前面运行存储分配讲到,对于JS这种复杂的语言不会这么简单执行,而是要依赖栈进行临时数据和执行流程进行管理。根据ecma-262标准,JS引擎会先创建一个全局环境,并将全局上下文压入栈底。当遇到函数执行时,会创建函数执行上下文,并将该上下文压入栈顶。而栈中的指令可以根据EBP:ESP(ESP是堆栈指针寄存器,存放执行函数对应栈帧的栈顶地址(也是系统栈的顶部),且始终指向栈顶;EBP是栈帧基址指针寄存器,存放执行函数对应栈帧的栈底地址)获取当前的内存地址空间,以指导生成准确操作数的机器码。
202+
通过栈的指针偏移和代码段的指针偏移,两者配合就可以完成运行时的内存分配,并记录下各个数据的地址。
190203
·设计一个三地址码生成规则:
191204
-- @:表示作用域
192205
-- section:执行上下文
@@ -199,13 +212,13 @@
199212
-- call:函数调用
200213
-- pass:表示传参
201214
·源码 → 三地址
202-
function fibonacci(n){
203-
if(n==1 || n==2){
204-
return n
215+
function fibonacci(n){
216+
if(n==1 || n==2){
217+
return n
218+
}
219+
return fibonacci(n-1)+fibonacci(n-2)
205220
}
206-
return fibonacci(n-1)+fibonacci(n-2)
207-
}
208-
print(fibonacci(5))
221+
print(fibonacci(5))
209222
转换后的三地址码:
210223
section fibonacci@2
211224
set %TOP% %SP%
@@ -229,4 +242,3 @@
229242
call fibonacci@1
230243
pass $t2@1
231244
call print@1
232-
·算法:

05_目标代码生成/README.md

Lines changed: 51 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,57 @@
1-
## 微机原理
2-
### 指令:
3-
* 指令格式:操作码 [操作数] [操作数]
4-
<br/>
5-
~&emsp;操作数:执行对象。第一个操作数 :目标操作数;第二个操作数:源操作数
6-
<br/>
7-
~&emsp;[]表示可以缺省
8-
* 指令格式:
9-
<br/>
10-
~&emsp;零操作数指令:操作码
11-
<br/>
12-
~&emsp;单操作数指令:操作码 [操作数]
13-
<br/>
14-
~&emsp;双操作数指令:操作码 [操作数] [操作数]
15-
* 指令中的操作数
16-
<br/>
17-
~&emsp;立即数:是一个常数
18-
<br/>
19-
~&emsp;寄存器操作数:是一个地址,计算速度最快
20-
<br/>
21-
~&emsp;存储器操作数:是一个地址,计算速度最慢
22-
* 立即数:立即数本身是参加操作的本身,可以是8位或者16位。只能作为源操作数
23-
```
24-
mov AX,1234H
25-
mov BL,22H
26-
```
27-
* 寄存器操作数:参加运算的数存放在指令给出的寄存器中,可以是8位,16位。
28-
```
29-
mov AX,BX
30-
mov DL,CH
31-
```
32-
* 存储器操作数:
33-
<br/>
34-
~&emsp;参与运算的数据存放在存储器的某一个或两个单元中
35-
<br/>
36-
~&emsp;表现形式:【 】:方括号里面是地址(偏移地址)
37-
```
38-
MOV AL,[1200H]
39-
```
40-
41-
### 指令的寻址方式(8种):
42-
* 立即寻址
43-
```
44-
由指令直接给出运算数据。操作数为立即数
45-
MOV AX,1200H
46-
结果:AL=00H,AH=12H
47-
常数1200H存放在代码段
48-
```
49-
* 寄存器寻址
50-
```
51-
参加的操作数在CPU的通用寄存器中
52-
mov AX,BX
53-
```
54-
* 存储器操作数的寻址方式
55-
* 直接寻址
56-
```
57-
方括号里面直接是偏移地址
58-
MOV AX,[1200H]
59-
```
60-
* 寄存器间接寻址
61-
```
62-
偏移地址为通用寄存器中的内容,换句话说:偏移地址放在通用寄存器中
63-
```
64-
* 寄存器相对寻址
65-
```
66-
相对寻址主要用于一维数组的操作
67-
```
68-
* 基址,变址,相对寻址
69-
```
70-
操作数的偏移地址=基址寄存器中的数据+变址寄存器中的数据+偏移地址
71-
主要运用二维数组
72-
```
73-
* 隐含寻址
74-
```
75-
操作数在默认的地址中
76-
MUL BL
77-
指令执行:AL*BL,结果放在AX中
78-
```
79-
80-
81-
## 代码生成器
82-
83-
* 代码生成器:将优化后的三地址码转化为目标码
84-
* 目标码的三种形式:
85-
<br/>
86-
~&emsp;绝对指令代码:能够立即执行的二进制代码,所有地址已经定位
87-
<br/>
88-
~&emsp;可重新定位指令代码:待装配的机器语言模块,执行时由链接器把他们和某些程序连接起来,转换为可执行的二进制机器语言代码
89-
<br/>
90-
~&emsp;汇编指令代码:尚未经过汇编器汇编成二进制的汇编代码
91-
* 代码生成主要考虑的问题:
92-
<br/>
93-
~&emsp;如何使生成目标代码较短
94-
<br/>
95-
~&emsp;如何充分利用寄存器,减少目标代码中访问存储单元次数
96-
<br/>
97-
~&emsp;如何充分利用不同cpu指令系统的特点
98-
* 代码生成器的主要任务:
99-
<br/>
100-
~&emsp;指令选择:代码生成器将中间码转换成目标机器码。一个中间码可以有多种机器码转换,所以代码生成器负责选择指令。
101-
<br/>
102-
~&emsp;寄存器申请:程序执行过程中可能需要保存一系列值。目标机器架构可能不允许所有的值都保存在CPU内存或寄存器。代码生成器决定寄存器保存哪些值。同样,也决定寄存器保存哪些值。
103-
~&emsp;指令顺序:一个代码生成器决定指令执行的顺序,它创建指令调度来执行它。
104-
* 一个目标原型非常复杂,我们不可能描述出全部细节,所以我们通常会将其简化为一个简单目标机原型。
105-
* 一个简单目标机原型:
106-
<br/>
107-
~ 加载、保存、运算、跳转等操作
108-
<br/>
109-
~ 内存按字节、寄存器、指针指向寻址或其他间接寻址
110-
<br/>
111-
~ n个通用寄存器R0,R1,....,Rn-1
112-
<br/>
113-
~ 所以运算分量都是整数
114-
<br/>
115-
~ 指令之间可能有一个标号
116-
* 目标通常一个机器指令有几十上百个指令,为了简化通常只选取一些典型指令:
117-
<br/>
118-
~ 加载指令:LD r, x
119-
<br/>
120-
~ 保存指令:ST x,r
121-
<br/>
122-
~ 运算指令:OP dst,src1,src2
123-
<br/>
124-
~ 无条件跳转指令:BR L
125-
<br/>
126-
~ 条件跳转指令:Bcond r,L
127-
<br/>
128-
~ 压栈操作指令:
129-
```
130-
push #1 将数字1压栈
131-
push TOP 将寄存器TOP压栈
132-
push @sp 将指针sp指向的值压栈
133-
```
134-
<br/>
135-
~ 移动指令:
136-
```
137-
MOVE R0,R1 将寄存器R0的值移入到R1中
138-
MOVE #1 R0 将数字1移入寄存器R0中
139-
MOVE #1 @TOP 将数字1移到寄存器指向的位置
140-
MOVE #1 @(TOP+4) 将数字1移到寄存器指向的位置基础上再加4位的位置
141-
```
142-
<br/>
143-
~ 比较指令:CMP R0,R1 比较两个寄存器中值的大小
144-
* 运算语句的三地址转目标代码:
145-
```
1+
2+
*代码生成基本概念:
3+
·代码生成器:将优化后的三地址码转化为目标码的翻译程序
4+
·目标码的三种形式:
5+
-- 绝对指令代码:能够立即执行的二进制代码,所有地址已经定位
6+
-- 可重新定位指令代码:待装配的机器语言模块,执行时由链接器把他们和某些程序连接起来,转换为可执行的二进制机器语言代码
7+
-- 汇编指令代码:尚未经过汇编器汇编成二进制的汇编代码
8+
·代码生成主要考虑的问题:
9+
-- 如何使生成目标代码较短
10+
-- 如何充分利用寄存器,减少目标代码中访问存储单元次数
11+
-- 如何充分利用不同cpu指令系统的特点
12+
·代码生成器的主要任务:
13+
-- 指令选择:代码生成器将中间码转换成目标机器码。一个中间码可以有多种机器码转换,所以代码生成器负责选择指令。
14+
-- 寄存器申请:程序执行过程中可能需要保存一系列值。目标机器架构可能不允许所有的值都保存在CPU内存或寄存器。代码生成器决定寄存器保存哪些值。同样,也决定寄存器保存哪些值。
15+
-- 指令顺序:一个代码生成器决定指令执行的顺序,它创建指令调度来执行它。
16+
·一个目标原型非常复杂,我们不可能描述出全部细节,所以我们通常会将其简化为一个简单目标机原型。
17+
·一个简单目标机原型:
18+
-- 加载、保存、运算、跳转等操作
19+
-- 内存按字节、寄存器、指针指向寻址或其他间接寻址
20+
-- n个通用寄存器R0,R1,....,Rn-1
21+
-- 所以运算分量都是整数
22+
-- 指令之间可能有一个标号
23+
·寄存器:
24+
-- TOP:存放作用域的基址
25+
-- ZF:零标志ZF(Zero Flag)用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。
26+
·目标通常一个机器指令有几十上百个指令,为了简化通常只选取一些典型指令:
27+
-- 加载指令:LD r, x
28+
-- 保存指令:ST x,r
29+
-- 运算指令:OP dst,src1,src2
30+
SUB R1,R1,R2 // R1=R1-R2
31+
-- 无条件跳转指令:BR L
32+
-- 条件跳转指令:Bcond r,L
33+
-- 压栈操作指令:
34+
push #1 将数字1压栈
35+
push TOP 将寄存器TOP压栈
36+
push @sp 将指针sp指向的值压栈
37+
-- 移动指令:
38+
MOVE R0,R1 将寄存器R0的值移入到R1中
39+
MOVE #1 R0 将数字1移入寄存器R0中
40+
MOVE #1 @TOP 将数字1移到寄存器指向的位置
41+
MOVE #1 @(TOP+4) 将数字1移到寄存器指向的位置基础上再加4位的位置
42+
-- 比较指令:CMP R0,R1 比较两个寄存器中值的大小
43+
-- 代码段行号跳转:JMP @(TOP-8)
44+
·运算语句的三地址转目标代码:
14645
三地址码:
147-
x=y-z
46+
x=y-z
14847
目标代码:
149-
LD R1,y //R1=Y
150-
LD R2,z //R2=Z
151-
SUB R1,R1,R2 //R1=R1-R2
152-
ST x,R1 //X=R1
48+
LD R1,y //R1=Y
49+
LD R2,z //R2=Z
50+
SUB R1,R1,R2 //R1=R1-R2
51+
ST X,R1 //X=R1
15352
解析:优秀的代码生成器应该避免使用上面的全部4个指令,如果:
154-
①所需的分量已经在寄存器中了
155-
②运算结果不需要存放内存
156-
```
157-
53+
①所需的分量已经在寄存器中了
54+
②运算结果不需要存放内存
15855

15956

16057

dist/SDT/ILGen.js

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)