Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Compiler] Compile and execute emit statement #3768

Merged
merged 3 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions bbq/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,9 +634,15 @@ func (c *Compiler[_]) VisitForStatement(_ *ast.ForStatement) (_ struct{}) {
panic(errors.NewUnreachableError())
}

func (c *Compiler[_]) VisitEmitStatement(_ *ast.EmitStatement) (_ struct{}) {
// TODO
panic(errors.NewUnreachableError())
func (c *Compiler[_]) VisitEmitStatement(statement *ast.EmitStatement) (_ struct{}) {
c.compileExpression(statement.InvocationExpression)
eventType := c.ExtendedElaboration.EmitStatementEventType(statement)
typeIndex := c.getOrAddType(eventType)
c.codeGen.Emit(opcode.InstructionEmitEvent{
TypeIndex: typeIndex,
})

return
}

func (c *Compiler[_]) VisitSwitchStatement(statement *ast.SwitchStatement) (_ struct{}) {
Expand Down
41 changes: 41 additions & 0 deletions bbq/compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,3 +654,44 @@ func TestCompileSwitch(t *testing.T) {
program.Constants,
)
}

func TestCompileEmit(t *testing.T) {

t.Parallel()

checker, err := ParseAndCheck(t, `
event Inc(val: Int)

fun test(x: Int) {
emit Inc(val: x)
}
`)
require.NoError(t, err)

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

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

var testFunction *bbq.Function[opcode.Instruction]
for _, f := range compiler.ExportFunctions() {
if f.Name == "test" {
testFunction = f
}
}
require.NotNil(t, testFunction)

assert.Equal(t,
[]opcode.Instruction{
opcode.InstructionGetLocal{LocalIndex: 0},
opcode.InstructionTransfer{TypeIndex: 0},
opcode.InstructionGetGlobal{GlobalIndex: 1},
opcode.InstructionInvoke{},
opcode.InstructionEmitEvent{TypeIndex: 1},
opcode.InstructionReturn{},
},
testFunction.Code,
)

assert.Empty(t, program.Constants)
}
4 changes: 4 additions & 0 deletions bbq/compiler/extended_elaboration.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ func (e *ExtendedElaboration) CastingExpressionTypes(expression *ast.CastingExpr
return e.elaboration.CastingExpressionTypes(expression)
}

func (e *ExtendedElaboration) EmitStatementEventType(statement *ast.EmitStatement) *sema.CompositeType {
return e.elaboration.EmitStatementEventType(statement)
}

func (e *ExtendedElaboration) ResultVariableType(enclosingBlock ast.Element) (typ sema.Type, exist bool) {
if e.resultVariableTypes != nil {
types, ok := e.resultVariableTypes[enclosingBlock]
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.

13 changes: 13 additions & 0 deletions bbq/opcode/instructions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -572,3 +572,16 @@
push:
- name: "result"
type: "bool"

# Other

- name: "emitEvent"
description:
Pops an event off the stack and then emits it.
operands:
- name: "typeIndex"
type: "index"
valueEffects:
pop:
- name: "event"
type: "value"
6 changes: 6 additions & 0 deletions bbq/opcode/opcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,10 @@ const (
_
_
_
_
_
_

// Other
EmitEvent
)
4 changes: 4 additions & 0 deletions bbq/opcode/opcode_string.go

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

9 changes: 9 additions & 0 deletions bbq/vm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ import (
"github.com/onflow/cadence/test_utils/common_utils"
)

// OnEventEmittedFunc is a function that is triggered when an event is emitted by the program.
type OnEventEmittedFunc func(
event *CompositeValue,
eventType *interpreter.CompositeStaticType,
) error

type Config struct {
interpreter.Storage
common.MemoryGauge
Expand All @@ -41,6 +47,9 @@ type Config struct {
MutationDuringCapabilityControllerIteration bool
referencedResourceKindedValues ReferencedResourceKindedValues

// OnEventEmitted is triggered when an event is emitted by the program
OnEventEmitted OnEventEmittedFunc

// TODO: These are temporary. Remove once storing/reading is supported for VM values.
inter *interpreter.Interpreter
TypeLoader func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType
Expand Down
38 changes: 38 additions & 0 deletions bbq/vm/test/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3645,3 +3645,41 @@ func TestDefaultFunctionsWithConditions(t *testing.T) {
)
})
}

func TestCompileEmit(t *testing.T) {

t.Parallel()

var eventEmitted bool

vmConfig := vm.NewConfig(interpreter.NewInMemoryStorage(nil))
vmConfig.OnEventEmitted = func(event *vm.CompositeValue, eventType *interpreter.CompositeStaticType) error {
require.False(t, eventEmitted)
eventEmitted = true

assert.Equal(t,
common.ScriptLocation{0x1}.TypeID(nil, "Inc"),
eventType.ID(),
)

return nil
}

_, err := compileAndInvokeWithOptions(t,
`
event Inc(val: Int)

fun test(x: Int) {
emit Inc(val: x)
}
`,
"test",
CompilerAndVMOptions{
VMConfig: vmConfig,
},
vm.NewIntValue(1),
)
require.NoError(t, err)

require.True(t, eventEmitted)
}
18 changes: 18 additions & 0 deletions bbq/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,8 @@ func (vm *VM) run() {
opNot(vm)
case opcode.InstructionUnwrap:
opUnwrap(vm)
case opcode.InstructionEmitEvent:
onEmitEvent(vm, ins)
default:
panic(errors.NewUnexpectedError("cannot execute instruction of type %T", ins))
}
Expand All @@ -813,6 +815,22 @@ func (vm *VM) run() {
}
}

func onEmitEvent(vm *VM, ins opcode.InstructionEmitEvent) {
eventValue := vm.pop().(*CompositeValue)

onEventEmitted := vm.config.OnEventEmitted
if onEventEmitted == nil {
return
}

eventType := vm.loadType(ins.TypeIndex).(*interpreter.CompositeStaticType)

err := onEventEmitted(eventValue, eventType)
if err != nil {
panic(err)
}
}

func (vm *VM) initializeConstant(index uint16) (value Value) {
executable := vm.callFrame.executable

Expand Down
Loading