@@ -366,4 +366,39 @@ private:
366
366
367
367
你看到那些”保留“和”取回“了吗?每个“保留”对应于一个push,每个“取回”对应于一个pop。这意味着我们可以轻易将其转换为字节码。例如,第一行获取巫师的当前生命值:
368
368
369
- LITERAL 0 GET_HEALTH
369
+ LITERAL 0
370
+ GET_HEALTH
371
+
372
+ 这段字节码将巫师的生命值入栈。如果我们重复这样的操作,最终会得到一段能计算出原表达式的字节码。为了让你体会指令是怎样组合的,我已经帮你做好了。
373
+
374
+ 为了演示堆栈如何随时间变化,权且将巫师的初始状态设置为45点生命、7点敏捷和11点智力。跟在每个指令后面的是执行后的堆栈状态,以及这个指令作用的注释:
375
+
376
+ LITERAL 0 [0] # Wi zard i ndex
377
+ LITERAL 0 [0, 0] # Wi zard i ndex
378
+ GET_HEALTH [0, 45] # getHealth()
379
+ LITERAL 0 [0, 45, 0] # Wi zard i ndex
380
+ GET_AGILITY [0, 45, 7] # getAgi li ty()
381
+ LITERAL 0 [0, 45, 7, 0] # Wi zard i ndex
382
+ GET_WISDOM [0, 45, 7, 11] # getWi sdom()
383
+ ADD [0, 45, 18] # Add agi li ty and wi sdom
384
+ LITERAL 2 [0, 45, 18, 2] # Di vi sor
385
+ DIVIDE [0, 45, 9] # Average agi li ty and wi sdom
386
+ ADD [0, 54] # Add average to current health
387
+ SET_HEALTH [] # Set health to result
388
+
389
+ 如果你一步一步看完这个堆栈,你就会发现数据像魔法一样在它内部流动。我们在一开始入栈巫师的索引0,然后做了很多不同的操作,直到最后栈低设置巫师生命值时用到它。
390
+
391
+ > 也许我这里对“魔法”的定义有点宽。
392
+
393
+ ###一个虚拟机
394
+
395
+ 我可以继续前进,添加更多各种各样的指令,但这是个停下来的好地方。像它现在这样,我们有了一个不错的小虚拟机,好让我们能使用简单又可压缩的数据根式来定义相对可扩展的指令。虽然“字节码”和“虚拟机”听起来有点吓人,但你会发现它们往往简单到一个堆栈、一个循环或是一个switch语句。
396
+
397
+ 还记得我们最初目标是让字节码得到很好的沙箱化?现在你看过虚拟机的整个实现过程,很明显我们已经做到了。字节码没法深入引擎的各个部分做有恶意的事情,因为我们只定义了少量访问引擎局部的指令。
398
+
399
+ 我们通过控制堆栈尺寸来限制它的可用内存,我们要当心以免内存溢出。我们甚至可以限制它的执行时间。在指令循环中,我们可以记录已经执行了多久,在它超出某个时间限制时,将它取消掉。
400
+
401
+ > 限制执行时间在我们的例子中并非必要,因为我们没有任何循环指令。我们可以通过限制字节码的总尺寸来限制执行时间。这也意味着字节码并非图灵完备。
402
+
403
+ 只剩下一个问题了:真正去创建字节码。眼下我们将一段伪代码编译成了字节码。除非你真的很闲,否则这在实践中根本行不通。
404
+
0 commit comments