上QQ阅读APP看书,第一时间看更新
3.3 指令解码
在第2章我们实现的二进制模块解码器还缺少一个最重要的部分:指令和表达式解码逻辑。具体来说,全局项的初始值表达式、元素和数据项的偏移量表达式、代码项的字节码还没有处理。下面是这4项以及表达式的编码格式。
global: global_type|init_expr elem : table_idx|offset_expr|vec<func_idx> data : mem_idx|offset_expr|vec<byte> code : byte_count|vec<locals>|expr expr : instr*|0x0b
这一节我们为解码器补上指令解码逻辑。以代码项为例,下面是改动之后的解码方法(省略了错误处理代码)。
func (reader *wasmReader) readCode(idx int) Code { return Code{ Locals: reader.readLocalsVec(), Expr: reader.readExpr(), // 新增代码 } }
由于全局项、元素项和数据项中的表达式更为简单(详见第11章),所以原来的代码不需要大改,只要把readExpr()方法重新实现就可以了。下面是重新定义后的Expr类型,以及调整后的readExpr()方法。
type Expr = []Instruction func (reader *WasmReader) readExpr() Expr { instrs, end := reader.readInstructions() if end != End_ { panic(fmt.Errorf("invalid expr end: %d", end)) } return instrs }
上面代码的重点是确保表达式以end指令结尾,真正的解码工作由readInstructions()方法完成。该方法读取并收集指令,直到遇到else或者end指令,代码如下所示。
func (reader *wasmReader) readInstructions() (instrs []Instruction, end byte) { for { instr := reader.readInstruction() if instr.Opcode == Else_ || instr.Opcode == End_ { end = instr.Opcode return } instrs = append(instrs, instr) } }
单条指令的解码逻辑很简单,先读取操作码,然后根据操作码读取立即数,代码如下所示。
func (reader *wasmReader) readInstruction() (instr Instruction) { instr.Opcode = reader.readByte() instr.Args = reader.readArgs(instr.Opcode) return }
立即数解码逻辑是一个大switch-case语句,部分代码如下所示(完整代码在binary/reader.go文件)。
func (reader *WasmReader) readArgs(opcode byte) interface{} { switch opcode { case Block: return reader.readBlockArgs() case Loop: return reader.readBlockArgs() case If: return reader.readIfArgs() case Br, BrIf: return reader.readVarU32() case BrTable: return reader.readBrTableArgs() case Call: return reader.readVarU32() case CallIndirect: return reader.readCallIndirectArgs() case LocalGet|Set|Tee: return reader.readVarU32() case GlobalGet|Set: return reader.readVarU32() case MemorySize|Grow: return reader.readZero() case I32Const: return reader.readVarS32() case I64Const: return reader.readVarS64() case F32Const: return reader.readF32() case F64Const: return reader.readF64() case TruncSat: return reader.readByte() case I32Load|...: return reader.readMemArg() default: return nil } }
立即数解码方法比较简单,这里我们以if指令的立即数解码方法为例,下面是readIfArgs()方法代码。
func (reader *wasmReader) readIfArgs() (args IfArgs) { var end byte args.BT = reader.readBlockType() args.Instrs1, end = reader.readInstructions() if end == Else_ { args.Instrs2, end = reader.readInstructions() if end != End_ { panic(fmt.Errorf("invalid block end: %d", end)) } } return }
注意看readIfArgs()和readInstructions()方法是如何相互递归调用的。块类型解码方法也比较简单,直接解码LEB128有符号数(并进行检查)即可,代码如下所示。
const ( BlockTypeI32 BlockType = -1 // ()->(i32) BlockTypeI64 BlockType = -2 // ()->(i64) BlockTypeF32 BlockType = -3 // ()->(f32) BlockTypeF64 BlockType = -4 // ()->(f64) BlockTypeEmpty BlockType = -64 // ()->() ) func (reader *wasmReader) readBlockType() int32 { bt := reader.readVarS32() if bt < 0 { switch bt { case BlockTypeI32, BlockTypeI64, BlockTypeF32, BlockTypeF64, BlockTypeEmpty: default: panic(fmt.Errorf("malformed block type: %d", bt)) } } return bt }
为了便于使用,我们给Module结构体添加一个方法,把块类型转换成相应的函数类型,代码如下所示。
func (module Module) GetBlockType(bt BlockType) FuncType { switch bt { case BlockTypeI32: return FuncType{ResultTypes: []ValType{ValTypeI32}} case BlockTypeI64: return FuncType{ResultTypes: []ValType{ValTypeI64}} case BlockTypeF32: return FuncType{ResultTypes: []ValType{ValTypeF32}} case BlockTypeF64: return FuncType{ResultTypes: []ValType{ValTypeF64}} case BlockTypeEmpty: return FuncType{} default: return module.TypeSec[bt] } }