// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // Five pcode interpreter — executes pcode bytecode on a Thread. // Each opcode directly calls the corresponding Thread method, // so pcode execution is semantically identical to gengo-compiled code. package hbrt import ( "encoding/binary" "fmt" "math" ) // ExecPcode runs a pcode function on the given thread. func ExecPcode(t *Thread, fn *PcodeFunc, mod *PcodeModule) { code := fn.Code pc := 0 // program counter t.Frame(fn.Params, fn.Locals) defer t.EndProc() for pc < len(code) { op := code[pc] pc++ switch op { case PcOpNop: // do nothing // --- Stack --- case PcOpPushNil: t.PushNil() case PcOpPushTrue: t.PushBool(true) case PcOpPushFalse: t.PushBool(false) case PcOpPushInt: v := int64(binary.LittleEndian.Uint64(code[pc:])) pc += 8 t.PushLong(v) case PcOpPushDouble: bits := binary.LittleEndian.Uint64(code[pc:]) pc += 8 t.PushDouble(math.Float64frombits(bits), 0, 0) case PcOpPushString: slen := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 t.PushString(string(code[pc : pc+slen])) pc += slen case PcOpPushBool: t.PushBool(code[pc] != 0) pc++ case PcOpPushLocal: idx := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 t.PushLocal(idx) case PcOpPopLocal: idx := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 t.PopLocal(idx) case PcOpPop: t.Pop() case PcOpDup: t.Dup() // --- Arithmetic --- case PcOpPlus: t.Plus() case PcOpMinus: t.Minus() case PcOpMult: t.Mult() case PcOpDivide: t.Divide() case PcOpMod: t.Modulus() case PcOpPower: t.Power() case PcOpNegate: t.Negate() // --- Comparison --- case PcOpEqual: t.Equal() case PcOpNotEqual: t.NotEqual() case PcOpLess: t.Less() case PcOpGreater: t.Greater() case PcOpLessEq: t.LessEqual() case PcOpGreaterEq: t.GreaterEqual() case PcOpInString: t.InString() // --- Logical --- case PcOpAnd: t.And() case PcOpOr: t.Or() case PcOpNot: t.Not() // --- Flow control --- case PcOpJump: offset := int32(binary.LittleEndian.Uint32(code[pc:])) pc += 4 pc += int(offset) case PcOpJumpFalse: offset := int32(binary.LittleEndian.Uint32(code[pc:])) pc += 4 if !t.PopLogical() { pc += int(offset) } case PcOpJumpTrue: offset := int32(binary.LittleEndian.Uint32(code[pc:])) pc += 4 if t.PopLogical() { pc += int(offset) } case PcOpReturn: return case PcOpRetValue: t.RetValue() return // --- Frame --- case PcOpFrame: // Already called at function entry; skip if re-encountered pc += 4 // params + locals case PcOpEndProc: return // --- Function calls --- case PcOpPushSymbol: slen := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 name := string(code[pc : pc+slen]) pc += slen sym := t.VM().FindSymbol(name) t.PushSymbol(sym) case PcOpPushNilArg: t.PushNil() case PcOpFunction: nArgs := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 t.Function(nArgs) case PcOpDo: nArgs := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 t.Do(nArgs) // --- Self / OOP --- case PcOpPushSelf: t.PushSelf() case PcOpPushSelfField: slen := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 name := string(code[pc : pc+slen]) pc += slen t.PushSelfField(name) case PcOpSetSelfField: slen := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 name := string(code[pc : pc+slen]) pc += slen t.SetSelfField(name) case PcOpSend: slen := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 name := string(code[pc : pc+slen]) pc += slen nArgs := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 t.Send(name, nArgs) // --- Array --- case PcOpArrayGen: count := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 t.ArrayGen(count) case PcOpArrayPush: t.ArrayPush() case PcOpArrayPop: t.ArrayPop() // --- Block --- case PcOpPushBlock: codeLen := int(binary.LittleEndian.Uint32(code[pc:])) pc += 4 blockCode := make([]byte, codeLen) copy(blockCode, code[pc:pc+codeLen]) pc += codeLen nDetached := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 // Create a Go function that interprets the block's pcode blockFn := &PcodeFunc{Code: blockCode} modCopy := mod t.PushBlock(func(t2 *Thread) { ExecPcode(t2, blockFn, modCopy) }, nDetached) // --- Local ops --- case PcOpLocalAddInt: idx := int(binary.LittleEndian.Uint16(code[pc:])) pc += 2 val := int32(binary.LittleEndian.Uint32(code[pc:])) pc += 4 t.LocalAddInt(idx, int64(val)) case PcOpInc: t.Inc() case PcOpDec: t.Dec() case PcOpPopLogical: t.PopLogical() case PcOpLine: pc += 2 // skip line number (for debugging) case PcOpHalt: return default: panic(fmt.Sprintf("unknown pcode opcode: 0x%02X at pc=%d", op, pc-1)) } } }