【游戏编程扯淡精粹】Python虚拟机源码

【游戏编程扯淡精粹】Python虚拟机源码

  • BufferedInputStream还是个RAII,自动关闭流
  • BufferedInputStream打不开文件,要用异常
    • 我想不到比较合适的做法,assert-false把
    • 这里其实有用户交互的,但是想不通
  • fileio,每次操作检查ferror,出错就退出
  • python -m compileall
    • 调用模块compileall
    • 这里的参数看不懂,所以我用bat移动文件了
  • path的话,文件路径是const-string,不会修改了
  • fileio的fread
    • 返回读取的元素个数
    • 因此调用的时候还是要注意的,一个元素1byte,这样返回bytes数目
    • 他的错误做法导致,元素块很大,256B,文件120B,fread一次一个元素读不满,返回0
  • 然后是大小端,这里是小端
    • 序列化多字节值要注意大小端
  • 这个BinaryReader类,c#自带。。。
  • 关于eol异常,其实可以不处理的,你知道不会出就没关系
  • 不要动submodule,很麻烦
  • 不要改命名,直接拷贝
  • realloc正是用于vector的expand
    • 错误处理有点麻烦,失败返回null,并且源指针不会动,别忘了释放
    • 这样不对,原来是用new的,不能用realloc
  • 只expand,不回缩

windows.h里竟然有#define IN
【游戏编程扯淡精粹】Python虚拟机源码_第1张图片

HiString

构造

  • std::strcpy - cppreference.com
  • NOTE strcpy会拷贝null,因为他需要构造一个cstring
    • 但是我们自己的字符串类,会维护len,因为不null-terminate
    • 一般我们拿到strlen,要为news分配len+1的空间
    • 比如src-str长度为0,我们要分配1格内存来存储null
  • 下面的代码
  • 第一个ctor从cstring构造
    • NOTE 我们只分配了len
    • 这里其实用strlen不好,你不是要构造一个cstring,文档里说dest如果不够大时UB
  • 所以memcpy是最合适的
  • 第二个从一个buffer构造
    • NOTE 这里不能用strlen,因为这是个buffer,不是cstring
      • 一个buffer有很多null是合法的,我们要完整地拷贝它,如果你用了strcpy,遇到第一个null就停下来了
  • 没有null的设计更符合直觉,s的内容就是buffer
  • 但是用cstring的函数就不能用了,比如printf,你要自己实现
    • 打印null是很奇怪的事情
HiString::HiString(const char* x) {
    _length = strlen(x);
    _value = (char*)Universe::heap->allocate(_length);
    strcpy(_value, x);

    set_klass(StringKlass::get_instance());
}

HiString::HiString(const char * x, const int length) {
    _length = length;
    _value = (char*)Universe::heap->allocate(_length);

    // do not use strcpy here, since '\0' is allowed.
    for (int i = 0; i < length; i++) {
        _value[i] = x[i];
    }

    set_klass(StringKlass::get_instance());
}

BinaryFileParser

  • 也就是lua的undumper
  • 从parse开始,从pyc文件流解析出CodeProject结构
  • 首先是文件头的信息,可惜,pyc的文件头没有什么信息,我们都扔掉
  • 这种格式和json是有区别的,
    • 是解析一个具体的struct,
    • 我们在已知CodeProject的情况下,pyc的结构是比较紧凑的
  • 这里的格式细节我们就忽略了,只看CodeProject
  • _string_table是str-intern表
    • pyc的HiObject-union语法是一个char标记类型,然后是值(用readxxx解析)
    • string会做intern,t标记加入string-table,R标记重用字符串(R后面跟一个int-id,索引string-table)
  • 这里有一个开发上的循环依赖
    • None的实现过早登场了,但是和类型系统紧密关联
    • 如果现在深入None实现,是低效率的
    • 正确的做法是,先用null简单地替代,

CodeProject

class CodeObject : public HiObject {
public:
    int _argcount;  // 参数个数
    int _nlocals;  // 巨变变量个数
    int _stack_size;  // 栈大小的最大值
    int _flag;  // 字节码的一些属性的flags

    HiString* _bytecodes;  // 字节码本码

    ArrayList<HiObject*>*  _consts;  // 常量表
    ArrayList<HiObject*>*  _names;  // 变量名表
    ArrayList<HiObject*>*  _var_names;  // 参数列表
    
    // 闭包用
    ArrayList<HiObject*>*  _free_vars;
    ArrayList<HiObject*>*  _cell_vars;

    HiString* _co_name;  // 所属模块名
    HiString* _file_name;  // 文件名
    
    // line-no表
    // debug,traceback-callstack用
    int _lineno;
    HiString* _notable;
};

Map

  • MapEntry很容易,就是个pair,略
  • 这个map竟然是线性查找
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AJUmrMRT-1640080289055)(en-resource://database/8674:1)]
  • 这几个都差不多,就是封装的最简单的数据结构

ArrayList

  • 模板类的函数可以分离实现到cpp文件
  • 实例化模板的T
    • Class template - cppreference.com
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bGiXgLDm-1640080294190)(en-resource://database/8656:1)]
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DaozivgH-1640080294191)(en-resource://database/8655:1)]
    • 书里的意思是,arraylist是hpp和cpp分离的,然后声明ArrayList时会链接错误,强制实例化模板
  • 重载new []
    • 不是很了解
    • 这里有点困惑,直接调用了全局new,又重载new干什么
  • 并不是vector,和HiObject有关
  • vector部分很容易
  • void oops_do(OopClosure* closure);和闭包有关,之后再说
  • c++ - How to get the default value of any type - Stack Overflow
    • cpp没有default(T),用大括号
  • 这个set,可以设置超过大小的index,他会扩容的
  • 这个其实没道理的,这就是个vector,vector类需要模板实例化吗
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KgJkwoYp-1640080294191)(en-resource://database/8657:1)]

用例分析

  • 实例化
klasses = new ArrayList<Klass*>();

完整源码

#ifndef ARRAY_LIST_HPP
#define ARRAY_LIST_HPP

#include 

class OopClosure;

template <typename T>
class ArrayList {
private:
    // array的大小
    int _length;
    T*  _array;
    // 当前有效元素个数
    int _size;
    
    // 内部扩容
    // 
    void expand();

public:
    ArrayList(int n = 8);
    
    // append
    void add(T t);
    void insert(int index, T t);
    T    get(int index);
    void set(int index, T t);
    int  size();
    int  length();
    int  index(T t);
    T*   value()  { return _array; }
    T    pop();
    void delete_index(int index);

    void* operator new(size_t size);

    void oops_do(OopClosure* closure);
};

class HiObject;
typedef ArrayList<HiObject*>* ObjList;

#endif

#include "util/arrayList.hpp"
#include "runtime/interpreter.hpp"
#include "runtime/universe.hpp"
#include "memory/heap.hpp"
#include "memory/oopClosure.hpp"
#include 
#include 

template <typename T>
ArrayList<T>::ArrayList(int n) {
    _length = n;
    _size   = 0;
    void* temp = Universe::heap->allocate(sizeof(T) * n);
    _array  = new(temp)T[n];
}

template <typename T>
void ArrayList<T>::add(T t) {
    if (_size >= _length)
        expand();

    _array[_size++] = t;
}

template <typename T>
void ArrayList<T>::insert(int index, T t) {
    add(NULL);

    for (int i = _size - 1; i > index; i--) {
        _array[i] = _array[i - 1];
    }

    _array[index] = t;
}

template <typename T>
void ArrayList<T>::expand() {
    void* temp = Universe::heap->allocate(sizeof(T) * (_length << 1));
    T* new_array = new(temp)T[_length << 1];
    for (int i = 0; i < _length; i++) {
        new_array[i] = _array[i];
    }
    // we do not rely on this, but gc.
    //delete[] _array;
    _array = new_array;

    _length <<= 1;
    printf("expand an array to %d, size is %d\n", _length, _size);
}

template <typename T>
int ArrayList<T>::size() {
    return _size;
}

template <typename T>
int ArrayList<T>::length() {
    return _length;
}

template <typename T>
T ArrayList<T>::get(int index) {
    return _array[index];
}

template <typename T>
void ArrayList<T>::set(int index, T t) {
    if (_size <= index)
        _size = index + 1;

    while (_size > _length)
        expand();

    _array[index] = t;
}

template <typename T>
T ArrayList<T>::pop() {
    return _array[--_size];
}

template <typename T>
void ArrayList<T>::delete_index(int index) {
    for (int i = index; i + 1 < _size; i++) {
        _array[i] = _array[i+1];
    }
    _size--;
}

template <typename T>
void* ArrayList<T>::operator new(size_t size) {
    return Universe::heap->allocate(size);
}

template <typename T>
void ArrayList<T>::oops_do(OopClosure* closure) {
    closure->do_raw_mem((char**)(&_array), 
            _length * sizeof(T));
}

template <>
void ArrayList<Klass*>::oops_do(OopClosure* closure) {
    closure->do_raw_mem((char**)(&_array), 
            _length * sizeof(Klass*));

    for (int i = 0; i < size(); i++) {
        closure->do_klass((Klass**)&_array[i]);
    }
    return;
}

template <>
void ArrayList<HiObject*>::oops_do(OopClosure* closure) {
    closure->do_raw_mem((char**)(&_array), 
            _length * sizeof(HiObject*));

    for (int i = 0; i < size(); i++) {
        closure->do_oop((HiObject**)&_array[i]);
    }
}

template <typename T>
int ArrayList<T>::index(T t) {
    return 0;
}

template <>
int ArrayList<HiObject*>::index(HiObject* t) {
    for (int i = 0; i < _size; i++) {
        if (_array[i]->equal(t) == Universe::HiTrue) {
            return i;
        }
    }

    return -1;
}

class HiObject;
template class ArrayList<HiObject*>;

class HiString;
template class ArrayList<HiString*>;

class Block;
template class ArrayList<Block*>;

class Klass;
template class ArrayList<Klass*>;


BufferedInputStream

  • 其实没必要的,之前C#写zlua有reader库很方便,提供readint等等函数,其实要的是这个
  • 这个其实可以用scanf做
  • 它自己又缓冲一次,然后每次读取一个char(这样越界就更新缓冲)
  • 然后封装readint等函数,一个一个char读取,解析
  • 这种写法并不好,这个类也过于原始
  • 解析多字节值要注意字节序
    int read_int() {
        int b1 = read() & 0xff;
        int b2 = read() & 0xff;
        int b3 = read() & 0xff;
        int b4 = read() & 0xff;

        return b4 << 24 | b3 << 16 | b2 << 8 | b1;
    }

    double read_double() {
        char t[8];
        for (int i = 0; i < 8; i++) {
            t[i] = read();
        }

        return *(double *)t;
    }

语法特性实现

控制流

指令

case ByteCode::COMPARE_OP:
w = POP();
v = POP();

switch(op_arg) {
case ByteCode::GREATER:
PUSH(v->greater(w));
break;

case ByteCode::LESS:
PUSH(v->less(w));
break;

case ByteCode::EQUAL:
PUSH(v->equal(w));
break;

case ByteCode::NOT_EQUAL:
PUSH(v->not_equal(w));
break;

case ByteCode::GREATER_EQUAL:
PUSH(v->ge(w));
break;

case ByteCode::LESS_EQUAL:
PUSH(v->le(w));
break;

default:
printf("Error: Unrecognized compare op %d\n", op_arg);

}
break;

case ByteCode::POP_JUMP_IF_FALSE:
v = POP();
if (((HiInteger*)v)->value() == 0)
pc = op_arg;
break;

case ByteCode::JUMP_FORWARD:
pc += op_arg;
break;
case ByteCode::JUMP_ABSOLUTE:
pc = op_arg;
break;

case ByteCode::SETUP_LOOP:
break;

case ByteCode::POP_BLOCK:
break;

元方法

virtual HiObject* greater (HiObject* x) {};
virtual HiObject* less (HiObject* x) {};
virtual HiObject* equal (HiObject* x) {};
virtual HiObject* not_equal(HiObject* x) {};
virtual HiObject* ge (HiObject* x) {};
virtual HiObject* le (HiObject* x) {};

continue & break

  • 新增数据结构Block
  • Interpreter添加ArrayList* _loop_stack;
  • 用于实现SETUP_LOOP和POP_BLOCK

klass-oop

  • class TypeKlass : public Klass
  • class HiTypeObject : public HiObject
  • Klass新增HiTypeObject* _type_object;

把虚方法转换成具体方法,把虚方法转移到klass指针
【游戏编程扯淡精粹】Python虚拟机源码_第2张图片
【游戏编程扯淡精粹】Python虚拟机源码_第3张图片

函数

函数定义和调用实现

  • 抽象出FrameObject
  • 新增KlassOop:FunctionObject
  • FrameObject增加sender(caller)
  • Interpreter
    • 新增指令:MAKE_FUNCTION,CALL_FUNCTION

LEGB规则实现

  • FrameObject和FunctionObject新增global表
  • LOADXXX指令增加LEGB规则,一个fallback的查找
  • Inpterpreter新增builtin表

函数参数实现

  • FrameObject增加参数列表fast-local,比较简单
  • Interpreter新增指令LOAD FAST

函数默认参数实现

  • FunctionObject新增defaults表
  • 指令MAKE-FUNCTION新增参数,默认参数列表
  • def f(a=1)时,1被作为参数传入make-func(f, 1),保存在f的defaults中
  • 调用时,先传一般参数,然后传默认参数补足
  • fast-local就是arglist,长度在make-func时确定

native函数实现

  • 新增NativeFunctionKlass
  • 新增call元方法
  • 用全局函数len作为例子,把len注册到builtin中

方法实现

  • 新增指令LOAD-ATTR
    • 加载o的attr
    • o被提前压栈,attr-name是字符串,作为指令LOAD-ATTR的参数传入
  • 方法调用在编译时额外发射push(o)和load-attr(method-name)指令
  • 原理很简单,改了很多代码
  • 以str.upper为例子,因为我们没有实现自定义类
  • load-attr指令很简单,略
  • 新增MehodKlass
    • 这里有一点区别,native我们只创建了klass,没有object,因为这不是虚拟机类型
  • Klass新增klass-dict,存放klass-attr,包含方法
  • 实现upper,注册到str类中

用户自定义类实现

  • class A(Base): class-body

  • 发射代码:

    • Base构造一个tuple,BaseTuple
    • class-body是一个函数体
    • 调用class-body(),无参数
    • 额外发射指令BUILD_CLASS,进行再一次处理,参数是上一步返回值和BaseTuple
    • 赋值给A

标准库实现

print-int实现

  • 实现了TypeObject机制后
  • 我们要调用typeklass的print
  • 打印出class的name字段即可

isinstance实现

  • 简单
  • 有super字段,在继承链上查找即可

int-ctor实现

  • call元方法添加typeklass的switch-case
  • allocate_instance是一个新的虚函数,是工厂
  • 从栈上接受参数列表,构造新对象然后压栈
  • 参数是cls,和ctor的参数列表

你可能感兴趣的:(游戏编程扯淡精粹,python,开发语言,后端)