diff --git a/PVM/gas_model.go b/PVM/gas_model.go index 931bae83..e27e450f 100644 --- a/PVM/gas_model.go +++ b/PVM/gas_model.go @@ -14,13 +14,14 @@ type ExecUnits struct { var InitialUnits = ExecUnits{A: 4, L: 4, S: 4, M: 1, D: 1} // x.^(0) type Reg uint8 +type RegSlices [][]Reg -type State struct { +type BlockState struct { // ι^(n): next opcode index - Iota int + Iota ProgramCounter // c.^(n): cycle counter - Cyc int + Cyc Gas // n.^(n): ROB index count Next int @@ -34,8 +35,8 @@ type State struct { // ROB vectors S []uint8 // s->: instruction state: 1. decoded, 2. pending, 3. executing, 4. finished C []int // c->: remaining exec cycles - P [][]int // p->: pending dependencies - R [][]Reg // r->: destination registers + P RegSlices // p->: pending dependencies + R RegSlices // r->: destination registers X []ExecUnits // x->: units required to start // x.^(n): available execution units in current cycle @@ -46,8 +47,8 @@ type State struct { } // A.51: Ξ₀(ι) -func InitState(startIota int) *State { - return &State{ +func InitBlockState(startIota ProgramCounter) *BlockState { + return &BlockState{ Iota: startIota, // ι^(0)=ι Cyc: 0, // c.^(0)=0 Next: 0, // n.^(0)=0 @@ -56,11 +57,37 @@ func InitState(startIota int) *State { S: make([]uint8, 0, MaxROB), C: make([]int, 0, MaxROB), - P: make([][]int, 0, MaxROB), - R: make([][]Reg, 0, MaxROB), + P: make(RegSlices, 0, MaxROB), + R: make(RegSlices, 0, MaxROB), X: make([]ExecUnits, 0, MaxROB), UnitsAvail: InitialUnits, // x.^(0) Step: 0, } } + +func (r *RegSlices) remove1D(j int) { + if r == nil || j < 0 || j >= len(*r) { + return + } + + s := *r + copy(s[j:], s[j+1:]) + s[len(s)-1] = nil + *r = s[:len(s)-1] +} + +func (r *RegSlices) remove2D(j, k int) { + if r == nil || j < 0 || j >= len(*r) { + return + } + + inner := (*r)[j] + if k < 0 || k >= len(inner) { + return + } + + copy(inner[k:], inner[k+1:]) + inner[len(inner)-1] = 0 + (*r)[j] = inner[:len(inner)-1] +} diff --git a/PVM/gas_model_stf.go b/PVM/gas_model_stf.go new file mode 100644 index 00000000..7b278913 --- /dev/null +++ b/PVM/gas_model_stf.go @@ -0,0 +1,277 @@ +package PVM + +// A.52 +func (b *BlockState) SimulatePipeline(program *Program, pc ProgramCounter) Gas { + for program.ValidateOpcode(pc); ; { + // Ξ'(n+1) + blockEnd := (b.Step != 0 && program.Bitmasks.IsStartOfInstruction(int(b.Iota))) + opcodeIdx := program.InstructionData[b.Iota] + opcodeResource := getOpcodeResource(opcode(opcodeIdx)) + if b.Step == 0 || (!blockEnd && opcodeResource.decodeSlotsNeeded <= b.DecodeSlots && len(b.S) < 32) { + b.decodeInstr(program, pc) + } + + // Ξ''(n+1) + ready, _ := b.checkROBReady() + if ready && b.ExecutionSlots > 0 { + b.retire() + } + + // ∅ + if blockEnd && len(b.S) == 0 { + return max(b.Cyc-3, 1) // A.53 + } + + // Ξ'''(n+1) + b.Advance() + } +} + +// A.54 +func (b *BlockState) decodeInstr(program *Program, pc ProgramCounter) { + opcodeIdx := program.InstructionData[b.Iota] + if opcodeIdx == 100 { // move_reg + b.decodeNoDispatch(program, pc) + } else { + b.decodeDispatchToROB(program, pc) + } + +} + +// A.55 move_reg +func (b *BlockState) decodeNoDispatch(program *Program, pc ProgramCounter) { + targetPC := b.Iota + b.Iota += ProgramCounter(skip(int(pc), program.Bitmasks)) + 1 + b.DecodeSlots -= 1 + operands := getOperands(program, targetPC) + + for j := 0; j < len(b.R); j++ { + for k := 0; k < len(b.R[j]); k++ { + if b.R[j][k] == Reg(operands.srcReg[0]) { // move_reg only one srcRg + b.R.remove2D(j, k) + return + } + } + } + // no dependency found + b.R[len(b.R)] = make([]Reg, 1) + b.R[len(b.R)][0] = Reg(operands.srcReg[0]) +} + +// A.56 decode +func (b *BlockState) decodeDispatchToROB(program *Program, pc ProgramCounter) { + targetPC := b.Iota + opcodeResource := getOpcodeResource(opcode(program.InstructionData[targetPC])) + // iota + b.Iota += ProgramCounter(skip(int(pc), program.Bitmasks)) + 1 + // d + b.DecodeSlots -= opcodeResource.decodeSlotsNeeded + // n + b.Next += 1 + // s-> + // the ROB length is limited to MaxROB, so no need to check using append + b.S = append(b.S, 1) // decoded + // c-> + if len(b.C) >= b.Next { + b.C[b.Next-1] = opcodeResource.cyclesNeeded + } + // x-> + if len(b.X) >= b.Next { + b.X[b.Next-1] = opcodeResource.execUnitsNeeded + } + // p-> + operands := getOperands(program, targetPC) + + b.P = append(b.P, operands.dstReg) + + dstRegMap := make(map[Reg]bool, len(operands.dstReg)) + for _, v := range operands.dstReg { + dstRegMap[v] = true + } + // r-> + for j := 0; j < len(b.P); j++ { + if j == b.Step { + b.R = append(b.R, operands.dstReg) + } else { + for k := 0; k < len(b.P[j]); k++ { + if _, ok := dstRegMap[b.R[j][k]]; ok { + b.R.remove2D(j, k) + } + } + } + } +} + +// A.57 retire instruction in ROB +func (b *BlockState) retire() { + _, robIndex := b.checkROBReady() + b.S[robIndex] = 3 // executing + + b.UnitsAvail.A -= b.X[robIndex].A + b.UnitsAvail.L -= b.X[robIndex].L + b.UnitsAvail.S -= b.X[robIndex].S + b.UnitsAvail.M -= b.X[robIndex].M + b.UnitsAvail.D -= b.X[robIndex].D + + b.ExecutionSlots -= 1 + +} + +// A.59 +func (b *BlockState) Advance() { + // most of the state depend on S-> + // only need to iterate k+1 to j, since s->(0), ... s->(k) == 4 (finish) + // update S comes last, so that will not affect the order of S + var k int + for j := len(b.S) - 1; j >= 0; j-- { + if b.S[j] == 4 { + k = j + break + } + } + + // update C-> , R->, X + for j := k + 1; j < len(b.C); j++ { + if b.S[j] != 3 { + continue + } + b.C[j] -= 1 // update C-> + if b.C[j] == 1 { // about to finish + // update R, remove from ROB + b.R.remove1D(j) + + // update X + b.UnitsAvail.A += b.X[j].A + b.UnitsAvail.D += b.X[j].D + b.UnitsAvail.L += b.X[j].L + b.UnitsAvail.M += b.X[j].M + b.UnitsAvail.S += b.X[j].S + } + } + + b.Cyc += 1 + b.DecodeSlots = 4 + b.ExecutionSlots = 5 + +} + +// A.58 +func (b *BlockState) checkROBReady() (bool, int) { + for j := 0; j < len(b.S); j++ { + if b.S[j] != 2 { // b.S[i] == 2 => pending + continue + } + // check execute units availability + if b.X[j].A > b.UnitsAvail.A { + continue + } + if b.X[j].L > b.UnitsAvail.L { + continue + } + if b.X[j].S > b.UnitsAvail.S { + continue + } + if b.X[j].M > b.UnitsAvail.M { + continue + } + if b.X[j].D > b.UnitsAvail.D { + continue + } + + // check dependencies + execCycleSet := make(map[int]int, len(b.C)) + for k := 0; k < len(b.C); k++ { + execCycleSet[k] = b.C[k] + } + + for k := 0; k < len(b.P[j]); k++ { + if _, ok := execCycleSet[int(b.P[j][k])]; ok { + return false, -1 + } + } + return true, j + } + return false, -1 +} + +type opcodeResource struct { + cyclesNeeded int + decodeSlotsNeeded int + execUnitsNeeded ExecUnits +} + +type operand struct { + srcReg []Reg + dstReg []Reg +} + +func getOperands(program *Program, pc ProgramCounter) operand { + opcodeIdx := program.InstructionData[pc] + // According to A.5 + switch { + // no reg needed + case (opcodeIdx >= 0 && opcodeIdx <= 2), // A.5.1 + opcodeIdx == 10, // A.5.2 + (opcodeIdx >= 30 && opcodeIdx <= 33), // A.5.4 + opcodeIdx == 40: // A.5.5 + return operand{nil, nil} + + // one dstReg + case opcodeIdx == 20, // A.5.3 + (opcodeIdx >= 51 && opcodeIdx <= 58), // A.5.6 + (opcodeIdx >= 70 && opcodeIdx <= 73), // A.5.7 + opcodeIdx == 80: // A.5.8 + rA := min(12, (program.InstructionData[pc+1])%16) + return operand{nil, []Reg{Reg(rA)}} + + // one srcReg + case opcodeIdx == 50, (opcodeIdx >= 59 && opcodeIdx <= 62), // A.5.6 + (opcodeIdx >= 81 && opcodeIdx <= 90): // A.5.8 + rA := min(12, (program.InstructionData[pc+1])%16) + return operand{[]Reg{Reg(rA)}, nil} + + // one srcReg, one dstReg, + case (opcodeIdx >= 100 && opcodeIdx <= 110), // A.5.9 + (opcodeIdx >= 124 && opcodeIdx <= 161), // A.5.10 + opcodeIdx == 180: // A.5.12 rA => rD (dstReg), rB => rA (srcReg) + rD := min(12, (program.InstructionData[pc+1])%16) + rA := min(12, (program.InstructionData[pc+1])>>4) + return operand{[]Reg{Reg(rA)}, []Reg{Reg(rD)}} + + // one srcReg, one dstRg (different srcReg, dstReg computation) + case (opcodeIdx >= 120 && opcodeIdx <= 123): // A.5.10 + rA := min(12, (program.InstructionData[pc+1])%16) + rB := min(12, (program.InstructionData[pc+1])>>4) + return operand{[]Reg{Reg(rA)}, []Reg{Reg(rB)}} + + // Two srcReg + case (opcodeIdx >= 170 && opcodeIdx <= 175): // A.5.11 + rA := min(12, (program.InstructionData[pc+1])%16) + rB := min(12, (program.InstructionData[pc+1])>>4) + return operand{[]Reg{Reg(rA), Reg(rB)}, nil} + + // Three regs: one dstReg, two srcReg + case opcodeIdx >= 190 && opcodeIdx <= 230: + rA := min(12, (program.InstructionData[pc+1])%16) + rB := min(12, (program.InstructionData[pc+1])>>4) + rD := min(12, program.InstructionData[pc+2]) + return operand{[]Reg{Reg(rA), Reg(rB)}, []Reg{Reg(rD)}} + + default: + // this will never be reached in current implementation, opcode is checked before + return operand{nil, nil} + } +} + +// A.57 +// TODO: implement according to actual gas model table +func getOpcodeResource(opcodeData opcode) opcodeResource { + switch opcodeData { + case 0: + return opcodeResource{2, 2, ExecUnits{A: 1, L: 0, S: 0, M: 0, D: 0}} + case 1: + return opcodeResource{2, 2, ExecUnits{A: 1, L: 0, S: 0, M: 0, D: 0}} + default: + return opcodeResource{1, 1, ExecUnits{A: 1, L: 0, S: 0, M: 0, D: 0}} + } +} diff --git a/PVM/instructions.go b/PVM/instructions.go index 412fb61e..87f19a7d 100644 --- a/PVM/instructions.go +++ b/PVM/instructions.go @@ -19,6 +19,7 @@ var zeta = map[opcode]string{ // Ins w/o Arg 0: "trap", 1: "fallthrough", + 2: "unlikey", // Ins w/ Arg of One Imm 10: "ecalli", // Ins w/ Arg of One Reg and One Extended Width Imm @@ -190,6 +191,7 @@ var execInstructions = [231]func([]byte, ProgramCounter, ProgramCounter, Registe // A.5.1 Instructiopns without Arguments 0: instTrap, 1: instFallthrough, + 2: instUnlikey, // A.5.2 Instructions with Arguments of One Immediate 10: instEcalli, // A.5.3 Instructions with Arguments of One Register & One Extended With Immediate @@ -356,6 +358,12 @@ func instFallthrough(instructionCode []byte, pc ProgramCounter, skipLength Progr return PVMExitTuple(CONTINUE, nil), pc, reg, mem } +// opcode 2 +func instUnlikey(instructionCode []byte, pc ProgramCounter, skipLength ProgramCounter, reg Registers, mem Memory, jumpTable JumpTable, bitmask Bitmask) (error, ProgramCounter, Registers, Memory) { + pvmLogger.Debugf("[%d]: pc: %d, %s", instrCount, pc, zeta[opcode(instructionCode[pc])]) + return PVMExitTuple(CONTINUE, nil), pc, reg, mem +} + // opcode 10 // //lint:ignore ST1008 error naming here is intentional @@ -385,12 +393,19 @@ func instEcalli(instructionCode []byte, pc ProgramCounter, skipLength ProgramCou // //lint:ignore ST1008 error naming here is intentional func instLoadImm64(instructionCode []byte, pc ProgramCounter, skipLength ProgramCounter, reg Registers, mem Memory, jumpTable JumpTable, bitmask Bitmask) (error, ProgramCounter, Registers, Memory) { - rA := min(12, (int(instructionCode[pc+1]) % 16)) - // zeta_{iota+2,...,+8} - instLength := instructionCode[pc+2 : pc+10] - nuX, err := utils.DeserializeFixedLength(instLength, types.U64(8)) + /* + rA := min(12, (int(instructionCode[pc+1]) % 16)) + // zeta_{iota+2,...,+8} + instLength := instructionCode[pc+2 : pc+10] + nuX, err := utils.DeserializeFixedLength(instLength, types.U64(8)) + if err != nil { + pvmLogger.Errorf("insLoadImm64 deserialization raise error: %v", err) + } + */ + rA, nuX, err := decodeOneRegisterAndOneExtendedWidthImmediate(instructionCode, pc, skipLength) if err != nil { - pvmLogger.Errorf("insLoadImm64 deserialization raise error: %v", err) + pvmLogger.Errorf("insLoadImm64 decodeOneRegisterAndOneExtendedWidthImmediate error: %v", err) + return err, pc, reg, mem } reg[rA] = uint64(nuX) pvmLogger.Debugf("[%d]: pc: %d, %s, %s = %s", instrCount, pc, zeta[opcode(instructionCode[pc])], RegName[rA], formatInt(uint64(nuX))) diff --git a/PVM/invocation.go b/PVM/invocation.go index fe8d5f49..2139264c 100644 --- a/PVM/invocation.go +++ b/PVM/invocation.go @@ -93,8 +93,9 @@ func SingleStepStateTransition(instructionData ProgramCode, bitmask Bitmask, jum // block based version of (A.1) ψ_1 func BlockBasedInvoke(program Program, pc ProgramCounter, gas Gas, reg Registers, mem Memory) (error, ProgramCounter, Gas, Registers, Memory) { - gasPrime := Gas(gas) - // decode instructions in a block + // gasPrime := Gas(gas) + + // in fact, interpreter do not need to decode, this func will check opcode, block validity pcPrime, _, err := DecodeInstructionBlock(program.InstructionData, pc, program.Bitmasks) if err != nil { pvmLogger.Errorf("DecodeInstructionBlock error : %v", err) @@ -164,6 +165,22 @@ func DecodeInstructionBlock(instructionData ProgramCode, pc ProgramCounter, bitm } } +func (p *Program) ValidateOpcode(pc ProgramCounter) error { + // check pc is not out of range and avoid infinit-loop + if pc > ProgramCounter(len(p.InstructionData)) { + // pvmLogger.Debugf("PVM panic: program counter out of range, pcPrime = %d > program-length = %d", pcPrime, len(instructionData)) + return PVMExitTuple(PANIC, nil) + } + + // check opcode is valid after computing with skip + if !p.InstructionData.isOpcodeValid(pc) { + // pvmLogger.Debugf("PVM panic: decode program failed: opcode invalid") + return PVMExitTuple(PANIC, nil) + } + + return nil +} + // execute each instruction in block[pc:pcPrime] , pcPrime is computed by DecodeInstructionBlock func ExecuteInstructions(instructionData ProgramCode, bitmask Bitmask, jumpTable JumpTable, pc ProgramCounter, pcPrime ProgramCounter, registers Registers, memory Memory, gas Gas) (ProgramCounter, Registers, Memory, Gas, error) { // no need to worry about gas, opcode valid here, it's checked in HostCall and DecodeInstructionBlock respectively @@ -190,7 +207,7 @@ func ExecuteInstructions(instructionData ProgramCode, bitmask Bitmask, jumpTable case PANIC, HALT: return 0, registers, memory, gas, exitReason case HOST_CALL: - return pc + skipLength + 1, registers, memory, gas, exitReason + return pc, registers, memory, gas, exitReason } if pc != newPC { diff --git a/PVM/program_code.go b/PVM/program_code.go index b81c7575..c87fc715 100644 --- a/PVM/program_code.go +++ b/PVM/program_code.go @@ -78,9 +78,9 @@ func isBasicBlockTerminationInstruction(opcode byte) bool { // type BasicBlock [][]byte // each sequence is a instruction type Program struct { - InstructionData []byte // c , includes opcodes & instruction variables - Bitmasks Bitmask // k - JumpTable JumpTable // j, z, |j| + InstructionData ProgramCode // c , includes opcodes & instruction variables + Bitmasks Bitmask // k + JumpTable JumpTable // j, z, |j| } func (p *Program) RunHostCallFunc(operationType OperationType) Omega {