7171#### 棧動態增長
7272也可以如零.一版一樣,一邊計算一邊把臨時結果壓入棧,實作可能簡單一些。但最終仍要把棧恢復到舊貌,也就是說仍然要記錄棧到底增長了多少,或者採用 ` fp ` 暫存器來記錄當下的棧底為多少。
7373
74+ 臨時增長棧也會導致用 ` sp ` 來索引區域變數的位址會不太穩定,以 ` fp ` 來索引實作會比較容易。(棧臨時增長時,` sp ` 動,但` fp ` 不動)
75+
7476使用 ` fp ` 的棧會形如:
7577
7678![ 精五棧圖解] ( ../image/精五棧圖解fp.png )
@@ -83,7 +85,127 @@ gcc 可以用 `-fomit-frame-pointer` 來調控是否採用 `fp` 的棧形式,`
8385
8486不過要計算臨時變數也挺麻煩,先以記錄 ` fp ` 的方式來實作施術吧。
8587
86- ## 術的真言形式
88+ ## 施術的真言形式
8789
8890來看看上述的甲、乙兩術翻成真言會是什麼樣子:
8991
92+ ``` assembly
93+ 甲:
94+ # 四個區域變數+返回位址+舊 fp
95+ # 總共 6 * 8 位元組 (64 位元系統)
96+ addi sp, sp, -48
97+ # 儲存返回地址
98+ sd ra, 40(sp) # ra = return address = 返回地址
99+ # 儲存舊棧底(fp)
100+ sd s0, 32(sp) # s0 就是 fp
101+ # 更新 s0(fp) 為現在的棧底
102+ addi s0, sp, 48
103+
104+ // 初始化區域變數
105+ li t0, 1 # t0 = 1
106+ sd t0, -24(s0) # 元.天 = 1
107+ sd t0, -32(s0) # 元.地 = 1
108+ sd t0, -40(s0) # 元.玄 = 1
109+ sd t0, -48(s0) # 元.黃 = 1
110+
111+ call 乙 # jal ra, 術乙
112+
113+ # 收尾
114+ ld ra, -8(s0) # 恢復返回位址給暫存器 ra
115+ mv sp, s0 # 歸還棧空間
116+ ld s0, -16(s0) # 恢復 fp
117+ ret # jr ra
118+
119+
120+ 乙:
121+ # 四個區域變數+返回位址+舊 fp
122+ # 總共 6 * 8 位元組 (64 位元系統)
123+ addi sp, sp, -48
124+ # 儲存返回地址
125+ sd ra, 40(sp) # ra = return address = 返回地址
126+ # 儲存舊棧底(fp)
127+ sd s0, 32(sp) # s0 就是 fp
128+ # 更新 s0(fp) 為現在的棧底
129+ addi s0, sp, 48
130+
131+ // 初始化區域變數
132+ li t0, 1 # t0 = 1
133+ sd t0, -24(s0) # 元.宇 = 1
134+ sd t0, -32(s0) # 元.宙 = 1
135+ sd t0, -40(s0) # 元.洪 = 1
136+ sd t0, -48(s0) # 元.荒 = 1
137+
138+ # 同零.一版用堆疊機來計算宇+宙+洪+荒
139+
140+ # 宇入棧
141+ addi sp, sp, -8
142+ ld t0, -24(s0)
143+ sd t0, 0(sp)
144+
145+ # 宙入棧
146+ addi sp, sp, -8
147+ ld t0, -32(s0)
148+ sd t0, 0(sp)
149+
150+ # 加
151+ ld t1, 0(sp)
152+ addi sp, sp, 8
153+ ld t0, 0(sp)
154+ add t0, t0, t1
155+ sd t0, 0(sp)
156+
157+ # 洪入棧
158+ addi sp, sp, -8
159+ ld t0, -40(s0)
160+ sd t0, 0(sp)
161+
162+ # 加
163+ ld t1, 0(sp)
164+ addi sp, sp, 8
165+ ld t0, 0(sp)
166+ add t0, t0, t1
167+ sd t0, 0(sp)
168+
169+ # 荒入棧
170+ addi sp, sp, -8
171+ ld t0, -48(s0)
172+ sd t0, 0(sp)
173+
174+ # 加
175+ ld t1, 0(sp)
176+ addi sp, sp, 8
177+ ld t0, 0(sp)
178+ add t0, t0, t1
179+ sd t0, 0(sp)
180+
181+ # 答案在棧頂,將其載到 t1 暫存器
182+ ld t1, 0(sp)
183+ # 彈出計算結果後 sp 又回到術開始時的原本位置,棧大小恢復如初
184+ addi sp, sp, 8
185+
186+ # 準備參數,施展外術「曰」
187+ mv a0, t1
188+ call 曰
189+
190+ # 收尾
191+ ld ra, -8(s0) # 恢復返回位址給暫存器 ra
192+ mv sp, s0 # 歸還棧空間
193+ ld s0, -16(s0) # 恢復 fp
194+ ret # jr ra
195+
196+ ```
197+ 全在暫存器就算完了。
198+
199+ 再來看看乙那段將宇、宙、洪、荒相加的算式,若編譯器好好優化,會怎麽計算:
200+
201+ ``` assembly
202+ // 初始化區域變數
203+ lw t1, -8(s0) # 「宇」載至t1
204+ lw t2, -16(s0) # 「宙」載至t1
205+ lw t3, -24(s0) # 「洪」載至t1
206+ lw t4, -32(s0) # 「荒」載至t1
207+ add t5, t1, t2 # t5 = 宇 + 宙
208+ add t5, t5, t3 # t5 = t5 + 洪
209+ add t5, t5, t4 # t5 = t5 + 荒
210+ ```
211+ 全在暫存器就算完了。
0 commit comments