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

Update atree register inlining 1.0 branch #3488

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion npm-packages/cadence-parser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onflow/cadence-parser",
"version": "1.0.0-preview.38",
"version": "1.0.0-preview.39",
"description": "The Cadence parser",
"homepage": "https://github.com/onflow/cadence",
"repository": {
Expand Down
235 changes: 235 additions & 0 deletions runtime/empty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
* Cadence - The resource-oriented smart contract programming language
*
* Copyright Flow Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package runtime

import (
"time"

"github.com/onflow/atree"
"go.opentelemetry.io/otel/attribute"

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

// EmptyRuntimeInterface is an empty implementation of runtime.Interface.
// It can be embedded in other types implementing runtime.Interface to avoid having to implement all methods.
type EmptyRuntimeInterface struct{}

var _ Interface = EmptyRuntimeInterface{}

func (EmptyRuntimeInterface) MeterMemory(_ common.MemoryUsage) error {
// NO-OP
return nil
}

func (EmptyRuntimeInterface) ResolveLocation(_ []Identifier, _ Location) ([]ResolvedLocation, error) {
panic("unexpected call to ResolveLocation")
}

func (EmptyRuntimeInterface) GetOrLoadProgram(_ Location, _ func() (*interpreter.Program, error)) (*interpreter.Program, error) {
panic("unexpected call to GetOrLoadProgram")
}

func (EmptyRuntimeInterface) GetAccountContractCode(_ common.AddressLocation) (code []byte, err error) {
panic("unexpected call to GetAccountContractCode")
}

func (EmptyRuntimeInterface) MeterComputation(_ common.ComputationKind, _ uint) error {
// NO-OP
return nil
}

func (EmptyRuntimeInterface) ComputationUsed() (uint64, error) {
panic("unexpected call to ComputationUsed")
}

func (EmptyRuntimeInterface) MemoryUsed() (uint64, error) {
panic("unexpected call to MemoryUsed")
}

func (EmptyRuntimeInterface) InteractionUsed() (uint64, error) {
panic("unexpected call to InteractionUsed")
}

func (EmptyRuntimeInterface) GetCode(_ Location) ([]byte, error) {
panic("unexpected call to GetCode")
}

func (EmptyRuntimeInterface) SetInterpreterSharedState(_ *interpreter.SharedState) {
panic("unexpected call to SetInterpreterSharedState")
}

func (EmptyRuntimeInterface) GetInterpreterSharedState() *interpreter.SharedState {
panic("unexpected call to GetInterpreterSharedState")
}

func (EmptyRuntimeInterface) GetValue(_, _ []byte) (value []byte, err error) {
panic("unexpected call to GetValue")
}

func (EmptyRuntimeInterface) SetValue(_, _, _ []byte) (err error) {
panic("unexpected call to SetValue")
}

func (EmptyRuntimeInterface) ValueExists(_, _ []byte) (exists bool, err error) {
panic("unexpected call to ValueExists")
}

func (EmptyRuntimeInterface) AllocateSlabIndex(_ []byte) (atree.SlabIndex, error) {
panic("unexpected call to AllocateSlabIndex")
}

func (EmptyRuntimeInterface) CreateAccount(_ Address) (address Address, err error) {
panic("unexpected call to CreateAccount")
}

func (EmptyRuntimeInterface) AddAccountKey(
_ Address,
_ *PublicKey,
_ HashAlgorithm,
_ int,
) (*AccountKey, error) {
panic("unexpected call to AddAccountKey")
}

func (EmptyRuntimeInterface) GetAccountKey(_ Address, _ uint32) (*AccountKey, error) {
panic("unexpected call to GetAccountKey")
}

func (EmptyRuntimeInterface) AccountKeysCount(_ Address) (uint32, error) {
panic("unexpected call to AccountKeysCount")
}

func (EmptyRuntimeInterface) RevokeAccountKey(_ Address, _ uint32) (*AccountKey, error) {
panic("unexpected call to RevokeAccountKey")
}

func (EmptyRuntimeInterface) UpdateAccountContractCode(_ common.AddressLocation, _ []byte) (err error) {
panic("unexpected call to UpdateAccountContractCode")
}

func (EmptyRuntimeInterface) RemoveAccountContractCode(_ common.AddressLocation) (err error) {
panic("unexpected call to RemoveAccountContractCode")
}

func (EmptyRuntimeInterface) GetSigningAccounts() ([]Address, error) {
panic("unexpected call to GetSigningAccounts")
}

func (EmptyRuntimeInterface) ProgramLog(_ string) error {
panic("unexpected call to ProgramLog")
}

func (EmptyRuntimeInterface) EmitEvent(_ cadence.Event) error {
panic("unexpected call to EmitEvent")
}

func (EmptyRuntimeInterface) GenerateUUID() (uint64, error) {
panic("unexpected call to GenerateUUID")
}

func (EmptyRuntimeInterface) DecodeArgument(_ []byte, _ cadence.Type) (cadence.Value, error) {
panic("unexpected call to DecodeArgument")
}

func (EmptyRuntimeInterface) GetCurrentBlockHeight() (uint64, error) {
panic("unexpected call to GetCurrentBlockHeight")
}

func (EmptyRuntimeInterface) GetBlockAtHeight(_ uint64) (block Block, exists bool, err error) {
panic("unexpected call to GetBlockAtHeight")
}

func (EmptyRuntimeInterface) ReadRandom(_ []byte) error {
panic("unexpected call to ReadRandom")
}

func (EmptyRuntimeInterface) VerifySignature(
_ []byte,
_ string,
_ []byte,
_ []byte,
_ SignatureAlgorithm,
_ HashAlgorithm,
) (bool, error) {
panic("unexpected call to VerifySignature")
}

func (EmptyRuntimeInterface) Hash(_ []byte, _ string, _ HashAlgorithm) ([]byte, error) {
panic("unexpected call to Hash")
}

func (EmptyRuntimeInterface) GetAccountBalance(_ common.Address) (value uint64, err error) {
panic("unexpected call to GetAccountBalance")
}

func (EmptyRuntimeInterface) GetAccountAvailableBalance(_ common.Address) (value uint64, err error) {
panic("unexpected call to GetAccountAvailableBalance")
}

func (EmptyRuntimeInterface) GetStorageUsed(_ Address) (value uint64, err error) {
panic("unexpected call to GetStorageUsed")
}

func (EmptyRuntimeInterface) GetStorageCapacity(_ Address) (value uint64, err error) {
panic("unexpected call to GetStorageCapacity")
}

func (EmptyRuntimeInterface) ImplementationDebugLog(_ string) error {
panic("unexpected call to ImplementationDebugLog")
}

func (EmptyRuntimeInterface) ValidatePublicKey(_ *PublicKey) error {
panic("unexpected call to ValidatePublicKey")
}

func (EmptyRuntimeInterface) GetAccountContractNames(_ Address) ([]string, error) {
panic("unexpected call to GetAccountContractNames")
}

func (EmptyRuntimeInterface) RecordTrace(_ string, _ Location, _ time.Duration, _ []attribute.KeyValue) {
panic("unexpected call to RecordTrace")
}

func (EmptyRuntimeInterface) BLSVerifyPOP(_ *PublicKey, _ []byte) (bool, error) {
panic("unexpected call to BLSVerifyPOP")
}

func (EmptyRuntimeInterface) BLSAggregateSignatures(_ [][]byte) ([]byte, error) {
panic("unexpected call to BLSAggregateSignatures")
}

func (EmptyRuntimeInterface) BLSAggregatePublicKeys(_ []*PublicKey) (*PublicKey, error) {
panic("unexpected call to BLSAggregatePublicKeys")
}

func (EmptyRuntimeInterface) ResourceOwnerChanged(
_ *interpreter.Interpreter,
_ *interpreter.CompositeValue,
_ common.Address,
_ common.Address,
) {
panic("unexpected call to ResourceOwnerChanged")
}

func (EmptyRuntimeInterface) GenerateAccountID(_ common.Address) (uint64, error) {
panic("unexpected call to GenerateAccountID")
}
6 changes: 6 additions & 0 deletions runtime/interpreter/interpreter_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,12 @@ func (interpreter *Interpreter) VisitStringExpression(expression *ast.StringExpr
return NewUnmeteredCharacterValue(expression.Value)
}

// Optimization: If the string is empty, return the empty string singleton
// to avoid allocating a new string value.
if len(expression.Value) == 0 {
return EmptyString
}

// NOTE: already metered in lexer/parser
return NewUnmeteredStringValue(expression.Value)
}
Expand Down
12 changes: 4 additions & 8 deletions runtime/interpreter/statictype.go
Original file line number Diff line number Diff line change
Expand Up @@ -1113,19 +1113,14 @@ func ConvertSemaAccessToStaticAuthorization(
) Authorization {
switch access := access.(type) {
case sema.PrimitiveAccess:
if access.Equal(sema.UnauthorizedAccess) {
switch access {
case sema.UnauthorizedAccess:
return UnauthorizedAccess
}
if access.Equal(sema.InaccessibleAccess) {
case sema.InaccessibleAccess:
return InaccessibleAccess
}

case sema.EntitlementSetAccess:
var entitlements []common.TypeID
access.Entitlements.Foreach(func(key *sema.EntitlementType, _ struct{}) {
typeId := key.ID()
entitlements = append(entitlements, typeId)
})
return NewEntitlementSetAuthorization(
memoryGauge,
func() (entitlements []common.TypeID) {
Expand All @@ -1143,6 +1138,7 @@ func ConvertSemaAccessToStaticAuthorization(
typeId := access.Type.ID()
return NewEntitlementMapAuthorization(memoryGauge, typeId)
}

panic(errors.NewUnreachableError())
}

Expand Down
58 changes: 53 additions & 5 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,14 @@ var _ IterableValue = &StringValue{}
var VarSizedArrayOfStringType = NewVariableSizedStaticType(nil, PrimitiveStaticTypeString)

func (v *StringValue) prepareGraphemes() {
// If the string is empty, methods of StringValue should never call prepareGraphemes,
// as it is not only unnecessary, but also means that the value is the empty string singleton EmptyString,
// which should not be mutated because it may be used from different goroutines,
// so should not get mutated by preparing the graphemes iterator.
if len(v.Str) == 0 {
panic(errors.NewUnreachableError())
}

if v.graphemes == nil {
v.graphemes = uniseg.NewGraphemes(v.Str)
} else {
Expand Down Expand Up @@ -1278,7 +1286,14 @@ func (v *StringValue) slice(fromIndex int, toIndex int, locationRange LocationRa
})
}

if fromIndex == toIndex {
// If the string is empty or the result is empty,
// return the empty string singleton EmptyString,
// as an optimization to avoid allocating a new value.
//
// It also ensures that if the sliced value is the empty string singleton EmptyString,
// which should not be mutated because it may be used from different goroutines,
// it does not get mutated by preparing the graphemes iterator.
if len(v.Str) == 0 || fromIndex == toIndex {
return EmptyString
}

Expand Down Expand Up @@ -1521,6 +1536,14 @@ func (*StringValue) SetMember(_ *Interpreter, _ LocationRange, _ string, _ Value

// Length returns the number of characters (grapheme clusters)
func (v *StringValue) Length() int {
// If the string is empty, the length is 0, and there are no graphemes.
//
// Do NOT store the length, as the value is the empty string singleton EmptyString,
// which should not be mutated because it may be used from different goroutines.
if len(v.Str) == 0 {
return 0
}

if v.length < 0 {
var length int
v.prepareGraphemes()
Expand Down Expand Up @@ -1853,6 +1876,16 @@ func (v *StringValue) ForEach(
}

func (v *StringValue) IsGraphemeBoundaryStart(startOffset int) bool {

// Empty strings have no grapheme clusters, and therefore no boundaries.
//
// Exiting early also ensures that if the checked value is the empty string singleton EmptyString,
// which should not be mutated because it may be used from different goroutines,
// it does not get mutated by preparing the graphemes iterator.
if len(v.Str) == 0 {
return false
}

v.prepareGraphemes()

var characterIndex int
Expand Down Expand Up @@ -1883,17 +1916,23 @@ func (v *StringValue) seekGraphemeBoundaryStartPrepared(startOffset int, charact
}

func (v *StringValue) IsGraphemeBoundaryEnd(end int) bool {

// Empty strings have no grapheme clusters, and therefore no boundaries.
//
// Exiting early also ensures that if the checked value is the empty string singleton EmptyString,
// which should not be mutated because it may be used from different goroutines,
// it does not get mutated by preparing the graphemes iterator.
if len(v.Str) == 0 {
return false
}

v.prepareGraphemes()
v.graphemes.Next()

return v.isGraphemeBoundaryEndPrepared(end)
}

func (v *StringValue) isGraphemeBoundaryEndPrepared(end int) bool {
// Empty strings have no grapheme clusters, and therefore no boundaries
if len(v.Str) == 0 {
return false
}

for {
boundaryStart, boundaryEnd := v.graphemes.Positions()
Expand Down Expand Up @@ -1928,6 +1967,15 @@ func (v *StringValue) indexOf(inter *Interpreter, other *StringValue) (character
return 0, 0
}

// If the string is empty, exit early.
//
// That ensures that if the checked value is the empty string singleton EmptyString,
// which should not be mutated because it may be used from different goroutines,
// it does not get mutated by preparing the graphemes iterator.
if len(v.Str) == 0 {
return -1, -1
}

// Meter computation as if the string was iterated.
// This is a conservative over-estimation.
inter.ReportComputation(common.ComputationKindLoop, uint(len(v.Str)*len(other.Str)))
Expand Down
Loading
Loading