1 Method Declarations
这回不从comipler开始,从runtime开始。
GC也需要follow
接下来难点在于如何填充这些表
2 Compiling method declarations
难点:
一个类可以声明任意数量的方法。运行时需要查找并绑定所有这些方法。如果将这些方法都打包到一条 OP_CLASS 指令中,工作量会非常大。
解决方案
编译器会发出一条 OP_CLASS 指令,创建一个新的空 ObjClass 对象。然后,编译器会发出指令,将该类存储到一个带有其名称的变量中。
这里有点像Closure。 emit OP_CLOSURE后,把upvalue 的内容放到这个指令之后。
Method也是。和OP_METHOD是分开的指令。
3 定义Method
现在,对于每个方法声明,我们都会发出一条新的 OP_METHOD 指令,为该类添加一个方法。当所有 OP_METHOD 指令都执行完毕后,我们就得到了一个完整的类。在用户看来,类的声明是一个单一的atomic操作,而虚拟机则将其作为一系列mutation来实现。
3.1 具体实现
identifier 放到 constant里。把constant的index当做OP_METHOD的operand
function函数会 compile 函数parameters 还有body
然后emit ObjClosure
3.2 去哪找class?然后bind method?
Class的定义可能在stack上,可能在global。
因此
编译时记录下class的名字
当编译method以前,先将这个class 调出来放到栈上,namedVariable可以来做这件事情
----
defineVariable也会做这件事(global),但是下一章这两个call之间有东西,所以不这么干
----
所以编译完method后要pop出来那个class
3.3 给一个例子
回头看下为什么OP_CLOSURE之后会pop出来
-----
这OP_CLOSURE 会把function 放到stack。
OP_METHOD会把Closure pop出来?
右侧是执行完左侧后 stack的样子。
-----
3.4 运行时,OP_METHOD
之后会把closure pop出来。
4 Method References
两种情况都要支持
4.1 第一步
需要用“.”来得到method
但要记住,这里需要bind instance的state
举例,这里应该打出来 jane
jlox中,用的是heap allcoated Environment class。
但是clox,有临时变量在stack,还有在global的table中的,还有在closure的upvalue。
所以需要一个新的class
5 Bound Method(来自 CPython)
receiver 其实只能是ObjInstance。但是这里为了兼容更多函数,所以用了这个
配套代码
这样可以确保method句柄在内存中保留receiver,以便以后调用句柄时仍能找到对象。我们还会跟踪method closure。
隐藏实现,所以类似打印function
这是最后一个runtime Obj tyep 牛逼
6 Accessing Methods
改下OP_GET_PROPERTY
---
原来的
----
具体,先找method,如果不在就报错。
如果有,就bind method
然后把instance pop 出去,把method放到栈上。
7 bind 的例子
把instance 和 ObjClosure 结合
8 Calling Method
---
---
目前可以run这样的函数了
但是目前没有用instance 的状态
8 使用This
把这个this 当作局部变量,这样可以复用很多处理。closure也可以用
false的意思是 是不是canAssign 。这里永远是false。
variable函数不在乎this 有自己的token 或者不是identifier
要考虑instance 什么时候要进入到memory
至少要在capture closure 以前,clox把所有local variable 在stack上。
每个参数会给自己在stack上有个占位。empty string
那个位置是为method留着的。那里会存this ,也就是instance
这个例子
应该打印 Nested instance
8.1 为此添加
runtime的时候, receiver不在slot zero。要这样修一下,那里原来是什么?
如果是call 的话, 那里原来是 fn function。现在换成了instance
8.2 this 的错误使用
全局放个currentClass
这个类用于 放完nearest enclosing function 又来看继承
我们创建一个链表,用来串起来所有nest class
编译class时,会隐式的把当前的放到linked stack上
这个classCompiler或者C的stack上。
结束时,pop他,c 会自动回收他
这里就可以检查是否在class 里面了。
9 Instance Initializers
对initializer的要求
大概这个逻辑
9.1 invoke initializer
例子
init() 方法的新 CallFrame 共享该堆栈窗口,因此这些参数会隐式地转发给初始化器。
init()后面时"egg" 还有“coffee”
如果没有定义init函数,但是传进了参数,就报错
因为每次创建instance 都要找init ,所有就提前创建这个string,就可以用intern string了
这里有个潜在的bug就是copyString 可能调用GC,这时访问他就挂了。
所以要给个初值。
9.2 initializer return values
确保initializer 会返回新的instance,而不是nil或者其他东西。
只要初始化方法返回,用户调用类创建实例的过程就会结束,并在堆栈中留下初始化方法的任何值。这意味着,除非用户在初始化方法的末尾加上 return this;,否则不会有实例产生。这并不是很有用。
为了解决这个,单独为initializer设置一个类型。单独处理。
9.3 Incorrect returns ini initializers
防止他提前返回
10 Optimized Invocations
method invocation= accessing the method + calling the result
现状支持
但是效果不好
但是,总是将这些操作作为单独的操作执行会带来很大的代价。每次 Lox 程序访问和调用一个方法时,运行时堆都会分配一个新的 ObjBoundMethod,初始化其字段,然后再将其拉出。之后,GC 必须花费时间释放所有这些短暂的绑定方法。ObjBoundMethod 不用每次都new 吧
实际上,Compiler可以看到访问method 然后立即call it
所以可以有个单独的指令 来完成这两个操作
---
如果看下bytecode的运行情况,发现有很多OP_XXX都是成组重复出现。
可以把这些instruction组合起来,叫superinstruction
影响表现表现最大的overhead之一,就是decoding and dispatching each instruction
还不能加太多instruction,否则可能decoding变慢。
----
10.1 代码这样改
OP_INVOKE
有两个operand
-
The index of the property name in the constant table.
-
The number of arguments passed to the method.
假设invoke成功,最后更新frame
invoke()函数,先得到instance。要检查下。
但是好像并没有bind?
这里都不用bind。因为receiver and method都在正确的位置,之后举个例子
快了好多,10s能够call的次数
10.2 如果是field 有点问题
这里和invoke处理的稍微不同。这里的. 是access field。这个filed 被赋值为一个函数。这个field本身不是method。
---
有时有些错误,但是算的很快也可以接受。这是Monte Carlo algorithms.
---
这样修改
在找method之前,先找field。
如果找到了,就把他放到receiver的位置。因为他不是medthod,所以不要instance。
invoke的总结
11 language design
I believe this is one of the fundamental balancing acts of language design: similarity to other languages lowers learning cost, while divergence raises the compelling advantages.
novelty budget, idiosyncrasy credit,
作者牛逼