Skip to content

Commit

Permalink
Merge pull request #3488 from onflow/bastian/update-atree-inlining-ca…
Browse files Browse the repository at this point in the history
…dence-v1.0-16
  • Loading branch information
turbolent authored Jul 24, 2024
2 parents 84c7d3d + 0c13569 commit 892df34
Show file tree
Hide file tree
Showing 10 changed files with 1,270 additions and 22 deletions.
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

0 comments on commit 892df34

Please sign in to comment.