Skip to content

Commit

Permalink
compile if-let statements, add JumpIfNil instruction
Browse files Browse the repository at this point in the history
  • Loading branch information
turbolent committed Feb 4, 2025
1 parent 03ea43b commit f33f77b
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 5 deletions.
4 changes: 4 additions & 0 deletions bbq/compiler/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ func (g *InstructionCodeGen) PatchJump(offset int, newTarget uint16) {
ins.Target = newTarget
(*g.target)[offset] = ins

case opcode.InstructionJumpIfNil:
ins.Target = newTarget
(*g.target)[offset] = ins

default:
panic(errors.NewUnreachableError())
}
Expand Down
30 changes: 28 additions & 2 deletions bbq/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ func (c *Compiler[_]) emitUndefinedJumpIfFalse() int {
return offset
}

func (c *Compiler[_]) emitUndefinedJumpIfNil() int {
offset := c.codeGen.Offset()
c.codeGen.Emit(opcode.InstructionJumpIfNil{Target: math.MaxUint16})
return offset
}

func (c *Compiler[_]) patchJump(opcodeOffset int) {
count := c.codeGen.Offset()
if count == 0 {
Expand Down Expand Up @@ -570,14 +576,33 @@ func (c *Compiler[_]) VisitContinueStatement(_ *ast.ContinueStatement) (_ struct

func (c *Compiler[_]) VisitIfStatement(statement *ast.IfStatement) (_ struct{}) {
// TODO: scope
var elseJump int
switch test := statement.Test.(type) {
case ast.Expression:
c.compileExpression(test)
elseJump = c.emitUndefinedJumpIfFalse()

case *ast.VariableDeclaration:
// TODO: second value
c.compileExpression(test.Value)

tempIndex := c.currentFunction.generateLocalIndex()
c.codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: tempIndex})

c.codeGen.Emit(opcode.InstructionGetLocal{LocalIndex: tempIndex})
elseJump = c.emitUndefinedJumpIfNil()

c.codeGen.Emit(opcode.InstructionGetLocal{LocalIndex: tempIndex})
c.codeGen.Emit(opcode.InstructionUnwrap{})
varDeclTypes := c.ExtendedElaboration.VariableDeclarationTypes(test)
c.emitTransfer(varDeclTypes.TargetType)
local := c.currentFunction.declareLocal(test.Identifier.Identifier)
c.codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: local.index})

default:
// TODO:
panic(errors.NewUnreachableError())
}
elseJump := c.emitUndefinedJumpIfFalse()

c.compileBlock(statement.Then)
elseBlock := statement.Else
if elseBlock != nil {
Expand All @@ -588,6 +613,7 @@ func (c *Compiler[_]) VisitIfStatement(statement *ast.IfStatement) (_ struct{})
} else {
c.patchJump(elseJump)
}

return
}

Expand Down
62 changes: 62 additions & 0 deletions bbq/compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package compiler
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/onflow/cadence/bbq"
Expand Down Expand Up @@ -457,3 +458,64 @@ func TestCompileDictionary(t *testing.T) {
program.Constants,
)
}

func TestCompileIfLet(t *testing.T) {

t.Parallel()

checker, err := ParseAndCheck(t, `
fun test(x: Int?): Int {
if let y = x {
return y
} else {
return 2
}
}
`)
require.NoError(t, err)

compiler := NewInstructionCompiler(checker)
program := compiler.Compile()

require.Len(t, program.Functions, 1)

assert.Equal(t,
[]opcode.Instruction{
// let y = x
opcode.InstructionGetLocal{LocalIndex: 0x0},
opcode.InstructionSetLocal{LocalIndex: 0x1},

// if
opcode.InstructionGetLocal{LocalIndex: 0x1},
opcode.InstructionJumpIfNil{Target: 11},

// let y = x
opcode.InstructionGetLocal{LocalIndex: 0x1},
opcode.InstructionUnwrap{},
opcode.InstructionTransfer{TypeIndex: 0x0},
opcode.InstructionSetLocal{LocalIndex: 0x2},

// then { return y }
opcode.InstructionGetLocal{LocalIndex: 0x2},
opcode.InstructionReturnValue{},
opcode.InstructionJump{Target: 13},

// else { return 2 }
opcode.InstructionGetConstant{ConstantIndex: 0x0},
opcode.InstructionReturnValue{},

opcode.InstructionReturn{},
},
compiler.ExportFunctions()[0].Code,
)

assert.Equal(t,
[]*bbq.Constant{
{
Data: []byte{0x2},
Kind: constantkind.Int,
},
},
program.Constants,
)
}
9 changes: 7 additions & 2 deletions bbq/compiler/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,17 @@ func newFunction[E any](name string, parameterCount uint16, isCompositeFunction
}
}

func (f *function[E]) generateLocalIndex() uint16 {
index := f.localCount
f.localCount++
return index
}

func (f *function[E]) declareLocal(name string) *local {
if f.localCount == math.MaxUint16 {
panic(errors.NewDefaultUserError("invalid local declaration"))
}
index := f.localCount
f.localCount++
index := f.generateLocalIndex()
local := &local{index: index}
f.locals.Set(name, local)
return local
Expand Down
32 changes: 32 additions & 0 deletions bbq/opcode/instructions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions bbq/opcode/instructions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,22 @@
type: "index"
controlEffects:
- jump: "target"
valueEffects:
pop:
- name: "value"
type: "value"

- name: "jumpIfNil"
description: Jumps to the given instruction, if the top value on the stack is `nil`.
operands:
- name: "target"
type: "index"
controlEffects:
- jump: "target"
valueEffects:
pop:
- name: "value"
type: "value"

- name: "return"
description: Returns from the current function, without a value.
Expand Down
2 changes: 1 addition & 1 deletion bbq/opcode/opcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const (
ReturnValue
Jump
JumpIfFalse
_
JumpIfNil
_
_
_
Expand Down
48 changes: 48 additions & 0 deletions bbq/vm/test/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3184,3 +3184,51 @@ func TestFunctionPostConditions(t *testing.T) {
assert.Equal(t, []string{"A", "D", "F", "E", "C", "B"}, logs)
})
}

func TestIfLet(t *testing.T) {

t.Parallel()

t.Run("some", func(t *testing.T) {

t.Parallel()

result, err := compileAndInvoke(t, `
fun main(x: Int?): Int {
if let y = x {
return y
} else {
return 2
}
}
`,
"main",
vm.NewSomeValueNonCopying(
vm.NewIntValue(1),
),
)
require.NoError(t, err)
assert.Equal(t, vm.NewIntValue(1), result)
})

t.Run("nil", func(t *testing.T) {

t.Parallel()

result, err := compileAndInvoke(t, `
fun main(x: Int?): Int {
if let y = x {
return y
} else {
return 2
}
}
`,
"main",
vm.NilValue{},
)

require.NoError(t, err)
assert.Equal(t, vm.NewIntValue(2), result)
})
}
9 changes: 9 additions & 0 deletions bbq/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,13 @@ func opJumpIfFalse(vm *VM, ins opcode.InstructionJumpIfFalse) {
}
}

func opJumpIfNil(vm *VM, ins opcode.InstructionJumpIfNil) {
_, ok := vm.pop().(NilValue)
if ok {
vm.ip = ins.Target
}
}

func opBinaryIntAdd(vm *VM) {
left, right := vm.peekPop()
leftNumber := left.(IntValue)
Expand Down Expand Up @@ -698,6 +705,8 @@ func (vm *VM) run() {
opJump(vm, ins)
case opcode.InstructionJumpIfFalse:
opJumpIfFalse(vm, ins)
case opcode.InstructionJumpIfNil:
opJumpIfNil(vm, ins)
case opcode.InstructionIntAdd:
opBinaryIntAdd(vm)
case opcode.InstructionIntSubtract:
Expand Down

0 comments on commit f33f77b

Please sign in to comment.