Skip to content

Commit

Permalink
feat: Decode instruction (#6)
Browse files Browse the repository at this point in the history
* feat: Add instruction type and decoding method

* Optimization + refactoring
  • Loading branch information
ElijahVlasov committed Aug 2, 2023
1 parent bc41422 commit 92eb795
Show file tree
Hide file tree
Showing 5 changed files with 426 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
bin/
.DS_Store
vendor/
12 changes: 4 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ module github.com/NethermindEth/cairo-vm-go

go 1.20

require github.com/fxamacker/cbor/v2 v2.4.0
require github.com/NethermindEth/juno v0.4.1

require (
github.com/NethermindEth/juno v0.4.1 // indirect
github.com/bits-and-blooms/bitset v1.7.0 // indirect
github.com/consensys/gnark-crypto v0.10.1-0.20230414110055-e500f2f0ff3a // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/sys v0.5.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
github.com/stretchr/testify v1.8.4
github.com/x448/float16 v0.8.4 // indirect
)
require github.com/stretchr/testify v1.8.4
12 changes: 2 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,20 @@ github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHl
github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/consensys/gnark-crypto v0.10.1-0.20230414110055-e500f2f0ff3a h1:hUUl++56+8w2aG2NXdQGfU8cBT9ZZ8UP4R3s47dSFdQ=
github.com/consensys/gnark-crypto v0.10.1-0.20230414110055-e500f2f0ff3a/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
260 changes: 260 additions & 0 deletions pkg/vm/instruction.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,265 @@
package vm

import (
"fmt"
"math/big"

f "github.com/NethermindEth/juno/core/felt"
)

type Register uint8

const (
Ap Register = iota
Fp
)

type Op1Addr uint8

const (
Imm Op1Addr = iota
ApPlusOff2
FpPlustOff2
Op0
)

type ResLogic uint8

const (
AddOperands ResLogic = iota
MulOperands
Unconstrained
Op1
)

type PcUpdate uint8

const (
Jump PcUpdate = iota
JumpRel
Jnz
NextInstr
)

type ApUpdate uint8

const (
AddImm ApUpdate = iota
Add1
SameAp
Add2
)

type FpUpdate uint8

const (
ApPlus2 FpUpdate = iota
Dst
SameFp
)

type Opcode uint8

const (
Call Opcode = iota
Ret
AssertEq
Nop
)

type Instruction struct {
Off0 int16
Off1 int16
Off2 int16

Imm *f.Felt

DstRegister Register
Op0Register Register

Op1Addr Op1Addr

Res ResLogic
PcUpdate PcUpdate
ApUpdate ApUpdate
FpUpdate FpUpdate
Opcode Opcode
}

func (instr Instruction) Size() uint8 {
if instr.Imm != nil {
return 2
}
return 1
}

const (
dstRegBit = 0
op0RegBit = 1
op1ImmBit = 2
op1FpBit = 3
op1ApBit = 4
resAddBit = 5
resMulBit = 6
pcJumpAbsBit = 7
pcJumpRelBit = 8
pcJnzBit = 9
apAddBit = 10
apAdd1Bit = 11
opcodeCallBit = 12
opcodeRetBit = 13
opcodeAssertEqBit = 14
//reservedBit = 15
offsetBits = 16
numberOfFlags = 15
)

func decodeInstructionValues(encoding *big.Int) (flags uint16, off0Enc uint16, off1Enc uint16, off2Enc uint16, err error) {
if encoding.Cmp(new(big.Int).Lsh(big.NewInt(1), uint(3*offsetBits+numberOfFlags))) >= 0 {
return 0, 0, 0, 0, fmt.Errorf("unsupported instruction")
}

// After this we can safely assume encoding < 2^63
var uintEncoding = encoding.Uint64()

// first, second and third 16 bits of the instruction encoding respectively
off0Enc = uint16(uintEncoding & (1<<offsetBits - 1))
off1Enc = uint16((uintEncoding >> offsetBits) & (1<<offsetBits - 1))
off2Enc = uint16((uintEncoding >> (2 * offsetBits)) & (1<<offsetBits - 1))
// bits 48..63
flags = uint16(uintEncoding >> (3 * offsetBits))
err = nil

return
}

// Given []uint16 of 0s or 1s returns the set bit if there's only one such
// and return len(bits) in case there's no set bits.
// If there are more than 1 set bits return an error.
func oneHot(bits ...uint16) (uint16, error) {
var checkSum uint16 = 0
setBit := len(bits)

// checking
for i, bit := range bits {
checkSum += bit

if bit == 1 {
setBit = i
}
}

if checkSum > 1 {
return 0, fmt.Errorf("decoding wrong sequence of bits: %v", bits)
}

return uint16(setBit), nil
}

func DecodeInstruction(instruction *f.Felt, imm *f.Felt) (*Instruction, error) {
var instr *Instruction = new(Instruction)

// break down the instruction into 4 16-bit segments
flags, off0Enc, off1Enc, off2Enc, err := decodeInstructionValues(instruction.BigInt(big.NewInt(0)))

if err != nil {
return nil, fmt.Errorf("error decoding an instruction: %w", err)
}

instr.DstRegister = Register((flags >> dstRegBit) & 1)
instr.Op0Register = Register((flags >> op0RegBit) & 1)

op1Addr, err := oneHot((flags>>op1ImmBit)&1, (flags>>op1ApBit)&1, (flags>>op1FpBit)&1)
if err != nil {
return nil, fmt.Errorf("error decoding op1_addr of instruction: %w", err)
}

instr.Op1Addr = Op1Addr(op1Addr)

// if the address to draw op1 from is set to be the imm
// check the imm argument
if instr.Op1Addr == Imm {
if imm == nil {
return nil, fmt.Errorf("op1_addr is Op1Addr.IMM, but no immediate given")
} else {
var immFelt f.Felt
instr.Imm = immFelt.Set(imm)
}
} else {
instr.Imm = nil
}

pcUpdate, err := oneHot((flags>>pcJumpAbsBit)&1, (flags>>pcJumpRelBit)&1, (flags>>pcJnzBit)&1)
if err != nil {
return nil, fmt.Errorf("error decoding pc_update of instruction: %w", err)
}

instr.PcUpdate = PcUpdate(pcUpdate)

var defaultResLogic ResLogic

// (0, 0) bits at pc_update corespond to different
// scenarios depending on the instruction.
// For JNZ the result is not constrained
if instr.PcUpdate == Jnz {
defaultResLogic = Unconstrained
} else {
defaultResLogic = Op1
}

res, err := oneHot((flags>>resAddBit)&1, (flags>>resMulBit)&1)
if err != nil {
return nil, fmt.Errorf("error decoding res_logic of instruction: %w", err)
}

if res == 2 {
instr.Res = defaultResLogic
} else {
instr.Res = ResLogic(res)
}

// Moreover, the result must be unconstrained in case of JNZ
if instr.PcUpdate == Jnz && instr.Res != Unconstrained {
return nil, fmt.Errorf("jnz opcode must have Unconstrained res logic")
}

apUpdate, err := oneHot((flags>>apAddBit)&1, (flags>>apAdd1Bit)&1)
if err != nil {
return nil, fmt.Errorf("error decoding ap_update of instruction: %w", err)
}

instr.ApUpdate = ApUpdate(apUpdate)

opcode, err := oneHot((flags>>opcodeCallBit)&1, (flags>>opcodeRetBit)&1, (flags>>opcodeAssertEqBit)&1)
if err != nil {
return nil, fmt.Errorf("error decoding opcode of instruction: %w", err)
}

instr.Opcode = Opcode(opcode)

if instr.Opcode == Call {
// (0, 0) bits for ap_update also stand for different
// behaviour in different opcodes.
// Call treats (0, 0) as ADD2 logic
if instr.ApUpdate != SameAp {
return nil, fmt.Errorf("CALL must have ap_update = ADD2")
}
instr.ApUpdate = Add2
}

switch instr.Opcode {
case Call:
instr.FpUpdate = ApPlus2
case Ret:
instr.FpUpdate = Dst
default:
instr.FpUpdate = SameFp
}

// Turning unsigned offsets into signed ones
instr.Off0 = int16(int(off0Enc) - (1 << (offsetBits - 1)))
instr.Off1 = int16(int(off1Enc) - (1 << (offsetBits - 1)))
instr.Off2 = int16(int(off2Enc) - (1 << (offsetBits - 1)))

return instr, nil
}
Loading

0 comments on commit 92eb795

Please sign in to comment.