Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 53becba

Browse files
完成运行时内容
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-
1所需的分量已经在寄存器中了
155-
2运算结果不需要存放内存
156-
```
157-
53+
1所需的分量已经在寄存器中了
54+
2运算结果不需要存放内存
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)

AltStyle によって変換されたページ (->オリジナル) /