Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
576ed9a
Use static-types for VM subtype checks
SupunS Nov 5, 2025
3307cb7
Fix sema-type to static-type conversion
SupunS Nov 6, 2025
4a3cbfa
Implement missing subtyping checks for static-types
SupunS Nov 6, 2025
ac62b9f
Some more minor optimizations
SupunS Nov 6, 2025
cbf626b
Remove static-to-sema conversions from FT transfer execution path
SupunS Nov 6, 2025
c9c944d
Always call into the type-converter to convert from sema to static types
SupunS Nov 6, 2025
37527c7
Fix test-cases
SupunS Nov 6, 2025
fe176de
Cache authorization conversion results
SupunS Nov 7, 2025
1f0e1f1
Merge branch 'supun/subtype-gen-9' into supun/subtype-gen-runtime
SupunS Nov 7, 2025
520f400
Add ConformingStaticTYpe
SupunS Nov 7, 2025
3d8dc70
Cache typeID
SupunS Nov 7, 2025
e0130fc
Customize the generated runtime subtype check to use sema types
SupunS Nov 10, 2025
619bfa6
Update generator
SupunS Nov 10, 2025
fb001cd
Merge branch 'supun/subtype-gen-statictypes' into supun/subtype-gen-r…
SupunS Nov 10, 2025
19d453d
Revert "Cache static-type IDs"
SupunS Nov 10, 2025
ffd951d
Remove temp files
SupunS Nov 10, 2025
13f8951
Fix tests
SupunS Nov 10, 2025
3e14b7e
Merge branch 'supun/subtype-gen-statictypes' into supun/subtype-gen-r…
SupunS Nov 13, 2025
c282550
Fix tests
SupunS Nov 13, 2025
34bd31f
Merge branch 'supun/subtype-gen-statictypes' into supun/subtype-gen-r…
SupunS Nov 13, 2025
a729ee1
Merge branch 'supun/subtype-gen-statictypes-tests' into supun/subtype…
SupunS Nov 13, 2025
75b045c
Fix tests
SupunS Nov 13, 2025
24ea3ec
Merge branch 'feature/subtype-gen' of https://github.com/onflow/caden…
SupunS Nov 14, 2025
ac573c0
Fix lint
SupunS Nov 14, 2025
35c6492
Merge branch 'feature/subtype-gen' of https://github.com/onflow/caden…
SupunS Nov 17, 2025
79b5985
Merge branch 'feature/subtype-gen' of https://github.com/onflow/caden…
SupunS Nov 17, 2025
c7d893b
Fix capability static-type in VM
SupunS Nov 17, 2025
f648587
Migrate more functions to use static-types
SupunS Nov 18, 2025
e3cc290
Refactor code
SupunS Nov 18, 2025
1b51b3f
Merge branch 'master' into bastian/update-subtype-gen-runtime
turbolent Dec 5, 2025
d0c5cc8
Merge pull request #4381 from onflow/bastian/update-subtype-gen-runtime
turbolent Dec 5, 2025
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
56 changes: 56 additions & 0 deletions bbq/commons/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ func TypeQualifiedName(typ sema.Type, functionName string) string {
return typeQualifier + "." + functionName
}

func StaticTypeQualifiedName(typ interpreter.StaticType, functionName string) string {
if typ == nil {
return functionName
}

typeQualifier := StaticTypeQualifier(typ)
return typeQualifier + "." + functionName
}

func QualifiedName(typeName, functionName string) string {
if typeName == "" {
return functionName
Expand All @@ -62,6 +71,8 @@ func QualifiedName(typeName, functionName string) string {
// TODO: Add other types
// TODO: Maybe make this a method on the type
func TypeQualifier(typ sema.Type) string {
// IMPORTANT: Ensure this is in sync with `StaticTypeQualifier` method below.

switch typ := typ.(type) {
case *sema.ConstantSizedType:
return TypeQualifierArrayConstantSized
Expand Down Expand Up @@ -95,6 +106,51 @@ func TypeQualifier(typ sema.Type) string {
}
}

func StaticTypeQualifier(typ interpreter.StaticType) string {
// IMPORTANT: Ensure this is in sync with `TypeQualifier` method above.
// TODO: try to unify

switch typ := typ.(type) {
case *interpreter.ConstantSizedStaticType:
return TypeQualifierArrayConstantSized
case *interpreter.VariableSizedStaticType:
return TypeQualifierArrayVariableSized
case *interpreter.DictionaryStaticType:
return TypeQualifierDictionary
case *interpreter.FunctionStaticType:
// This is only applicable for types that also has a constructor with the same name.
// e.g: `String` type has the `String()` constructor as well as the type on which
// functions can be called (`String.join()`).
// Thus, if a constructor function is used as a type-qualifier,
// then used the actual type associated with it (i.e: the return type).
if typ.TypeFunctionType != nil {
return TypeQualifier(typ.TypeFunctionType)
}
return TypeQualifierFunction
case *interpreter.OptionalStaticType:
return TypeQualifierOptional
case *interpreter.ReferenceStaticType:
return StaticTypeQualifier(typ.ReferencedType)
case *interpreter.IntersectionStaticType:
// TODO: Revisit. Probably this is not needed here?
return StaticTypeQualifier(typ.Types[0])
case *interpreter.CapabilityStaticType:
return interpreter.PrimitiveStaticTypeCapability.String()
case *interpreter.InclusiveRangeStaticType:
return TypeQualifierInclusiveRange

// In addition to the `TypeQualifier` method above,
// following are needed.
case *interpreter.CompositeStaticType:
return typ.QualifiedIdentifier
case *interpreter.InterfaceStaticType:
return typ.QualifiedIdentifier

default:
return typ.String()
}
}

func LocationQualifier(typ sema.Type) string {
switch typ := typ.(type) {
case *sema.ReferenceType:
Expand Down
42 changes: 25 additions & 17 deletions bbq/vm/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,14 +331,18 @@ func (c *Context) GetMethod(
) interpreter.FunctionValue {
staticType := value.StaticType(c)

semaType := c.SemaTypeFromStaticType(staticType)

var location common.Location
if locatedType, ok := semaType.(sema.LocatedType); ok {
location = locatedType.GetLocation()

switch staticType := staticType.(type) {
case *interpreter.CompositeStaticType:
location = staticType.Location
case *interpreter.InterfaceStaticType:
location = staticType.Location

// TODO: Anything else?
}

qualifiedFuncName := commons.TypeQualifiedName(semaType, name)
qualifiedFuncName := commons.StaticTypeQualifiedName(staticType, name)

method := c.GetFunction(location, qualifiedFuncName)
if method == nil {
Expand Down Expand Up @@ -422,22 +426,26 @@ func (c *Context) DefaultDestroyEvents(resourceValue *interpreter.CompositeValue
return eventValues
}

func (c *Context) SemaTypeFromStaticType(staticType interpreter.StaticType) sema.Type {
typeID := staticType.ID()
semaType, ok := c.semaTypeCache[typeID]
if ok {
return semaType
}
func (c *Context) SemaTypeFromStaticType(staticType interpreter.StaticType) (semaType sema.Type) {
_, isPrimitiveType := staticType.(interpreter.PrimitiveStaticType)

// TODO: avoid the sema-type conversion
semaType = interpreter.MustConvertStaticToSemaType(staticType, c)
if !isPrimitiveType {
typeID := staticType.ID()
cachedSemaType, ok := c.semaTypeCache[typeID]
if ok {
return cachedSemaType
}

if c.semaTypeCache == nil {
c.semaTypeCache = make(map[sema.TypeID]sema.Type)
defer func() {
if c.semaTypeCache == nil {
c.semaTypeCache = make(map[sema.TypeID]sema.Type)
}
c.semaTypeCache[typeID] = semaType
}()
}
c.semaTypeCache[typeID] = semaType

return semaType
// TODO: avoid the sema-type conversion
return interpreter.MustConvertStaticToSemaType(staticType, c)
}

func (c *Context) GetContractValue(contractLocation common.AddressLocation) *interpreter.CompositeValue {
Expand Down
6 changes: 1 addition & 5 deletions bbq/vm/linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,12 @@ func linkImportedGlobal(
contractValue := global.GetValue(context)

staticType := contractValue.StaticType(context)
semaType, err := interpreter.ConvertStaticToSemaType(context, staticType)
if err != nil {
panic(err)
}

return interpreter.NewEphemeralReferenceValue(
context,
interpreter.UnauthorizedAccess,
contractValue,
semaType,
staticType,
)
},
)
Expand Down
31 changes: 28 additions & 3 deletions bbq/vm/reference_tracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,35 @@
package vm

import (
"sync"

"github.com/onflow/atree"

"github.com/onflow/cadence/interpreter"
)

type ReferencedResourceKindedValues map[atree.ValueID]map[*interpreter.EphemeralReferenceValue]struct{}
type ReferencedResourceKindedValues map[atree.ValueID]ReferenceSet

type ReferenceSet = map[*interpreter.EphemeralReferenceValue]struct{}

var referenceSetPool = sync.Pool{
New: func() any {
return ReferenceSet{}
},
}

func newReferenceSet() ReferenceSet {
set := referenceSetPool.Get().(ReferenceSet)
return set
}

func releaseReferenceSet(set ReferenceSet) {
if set == nil {
return
}
clear(set)
referenceSetPool.Put(set)
}

func (c *Context) MaybeTrackReferencedResourceKindedValue(referenceValue *interpreter.EphemeralReferenceValue) {
referenceTrackedValue, ok := referenceValue.Value.(interpreter.ReferenceTrackedResourceKindedValue)
Expand All @@ -36,7 +59,7 @@ func (c *Context) MaybeTrackReferencedResourceKindedValue(referenceValue *interp

values := c.referencedResourceKindedValues[id]
if values == nil {
values = map[*interpreter.EphemeralReferenceValue]struct{}{}
values = newReferenceSet()
if c.referencedResourceKindedValues == nil {
c.referencedResourceKindedValues = make(ReferencedResourceKindedValues)
}
Expand All @@ -46,9 +69,11 @@ func (c *Context) MaybeTrackReferencedResourceKindedValue(referenceValue *interp
}

func (c *Context) ClearReferencedResourceKindedValues(valueID atree.ValueID) {
set := c.referencedResourceKindedValues[valueID]
releaseReferenceSet(set)
delete(c.referencedResourceKindedValues, valueID)
}

func (c *Context) ReferencedResourceKindedValues(valueID atree.ValueID) map[*interpreter.EphemeralReferenceValue]struct{} {
func (c *Context) ReferencedResourceKindedValues(valueID atree.ValueID) ReferenceSet {
return c.referencedResourceKindedValues[valueID]
}
4 changes: 2 additions & 2 deletions bbq/vm/test/ft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ func compiledFTTransfer(tb testing.TB) {
context interpreter.BorrowCapabilityControllerContext,
address interpreter.AddressValue,
capabilityID interpreter.UInt64Value,
wantedBorrowType *sema.ReferenceType,
capabilityBorrowType *sema.ReferenceType,
wantedBorrowType *interpreter.ReferenceStaticType,
capabilityBorrowType *interpreter.ReferenceStaticType,
) interpreter.ReferenceValue {
return stdlib.BorrowCapabilityController(
context,
Expand Down
4 changes: 2 additions & 2 deletions bbq/vm/test/interpreter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@ func interpreterFTTransfer(tb testing.TB) {
context interpreter.BorrowCapabilityControllerContext,
address interpreter.AddressValue,
capabilityID interpreter.UInt64Value,
wantedBorrowType *sema.ReferenceType,
capabilityBorrowType *sema.ReferenceType,
wantedBorrowType *interpreter.ReferenceStaticType,
capabilityBorrowType *interpreter.ReferenceStaticType,
) interpreter.ReferenceValue {
return stdlib.BorrowCapabilityController(
context,
Expand Down
6 changes: 5 additions & 1 deletion bbq/vm/test/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11726,7 +11726,11 @@ func TestBorrowContractLinksGlobals(t *testing.T) {
context,
contractAddress,
interpreter.NewUnmeteredStringValue(contractName),
sema.NewReferenceType(nil, sema.UnauthorizedAccess, sema.AnyStructType),
interpreter.NewReferenceStaticType(
nil,
interpreter.UnauthorizedAccess,
interpreter.PrimitiveStaticTypeAnyStruct,
),
accountHandler,
)

Expand Down
36 changes: 0 additions & 36 deletions bbq/vm/types.go

This file was deleted.

9 changes: 3 additions & 6 deletions bbq/vm/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,10 @@ func ConvertAndBox(
value Value,
valueType, targetType bbq.StaticType,
) Value {
valueSemaType := context.SemaTypeFromStaticType(valueType)
targetSemaType := context.SemaTypeFromStaticType(targetType)

return interpreter.ConvertAndBox(
return interpreter.ConvertAndBoxToStaticType(
context,
value,
valueSemaType,
targetSemaType,
valueType,
targetType,
)
}
4 changes: 2 additions & 2 deletions bbq/vm/value_implicit_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ func NewImplicitReferenceValue(context interpreter.ReferenceCreationContext, val
}
}

semaType := interpreter.MustSemaTypeOfValue(value, context)
staticType := value.StaticType(context)

// Create an explicit reference to represent the implicit reference behavior of 'self' value.
// Authorization doesn't matter, we just need a reference to add to tracking.
selfRef := interpreter.NewEphemeralReferenceValue(
context,
interpreter.UnauthorizedAccess,
value,
semaType,
staticType,
)

return ImplicitReferenceValue{
Expand Down
2 changes: 1 addition & 1 deletion bbq/vm/value_inclusiverange.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func init() {
if !ok {
panic(errors.NewUnreachableError())
}
elementType := interpreter.MustConvertStaticToSemaType(rangeType.ElementType, context)
elementType := context.SemaTypeFromStaticType(rangeType.ElementType)
return sema.InclusiveRangeContainsFunctionType(elementType)
},
interpreter.NativeInclusiveRangeContainsFunction,
Expand Down
Loading
Loading