From 9dcb88b638b09e3e1fe288e333f2eed0e33bc75a Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 18 Oct 2023 16:55:39 -0700 Subject: [PATCH 1/2] Add computation metering to array function --- runtime/common/computationkind.go | 2 +- runtime/common/computationkind_string.go | 7 +- runtime/interpreter/value.go | 9 ++ runtime/interpreter/value_string.go | 3 + runtime/tests/interpreter/metering_test.go | 115 +++++++++++++++++++++ 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/runtime/common/computationkind.go b/runtime/common/computationkind.go index 90b96e858e..7574127cb5 100644 --- a/runtime/common/computationkind.go +++ b/runtime/common/computationkind.go @@ -57,7 +57,7 @@ const ( ComputationKindCreateArrayValue ComputationKindTransferArrayValue ComputationKindDestroyArrayValue - _ + ComputationKindIterateArrayValue _ _ _ diff --git a/runtime/common/computationkind_string.go b/runtime/common/computationkind_string.go index 9ed621d72f..dd2a4b27f5 100644 --- a/runtime/common/computationkind_string.go +++ b/runtime/common/computationkind_string.go @@ -18,6 +18,7 @@ func _() { _ = x[ComputationKindCreateArrayValue-1025] _ = x[ComputationKindTransferArrayValue-1026] _ = x[ComputationKindDestroyArrayValue-1027] + _ = x[ComputationKindIterateArrayValue-1028] _ = x[ComputationKindCreateDictionaryValue-1040] _ = x[ComputationKindTransferDictionaryValue-1041] _ = x[ComputationKindDestroyDictionaryValue-1042] @@ -33,7 +34,7 @@ const ( _ComputationKind_name_0 = "Unknown" _ComputationKind_name_1 = "StatementLoopFunctionInvocation" _ComputationKind_name_2 = "CreateCompositeValueTransferCompositeValueDestroyCompositeValue" - _ComputationKind_name_3 = "CreateArrayValueTransferArrayValueDestroyArrayValue" + _ComputationKind_name_3 = "CreateArrayValueTransferArrayValueDestroyArrayValueIterateArrayValue" _ComputationKind_name_4 = "CreateDictionaryValueTransferDictionaryValueDestroyDictionaryValue" _ComputationKind_name_5 = "EncodeValue" _ComputationKind_name_6 = "STDLIBPanicSTDLIBAssertSTDLIBUnsafeRandom" @@ -43,7 +44,7 @@ const ( var ( _ComputationKind_index_1 = [...]uint8{0, 9, 13, 31} _ComputationKind_index_2 = [...]uint8{0, 20, 42, 63} - _ComputationKind_index_3 = [...]uint8{0, 16, 34, 51} + _ComputationKind_index_3 = [...]uint8{0, 16, 34, 51, 68} _ComputationKind_index_4 = [...]uint8{0, 21, 44, 66} _ComputationKind_index_6 = [...]uint8{0, 11, 23, 41} _ComputationKind_index_7 = [...]uint8{0, 21, 40} @@ -59,7 +60,7 @@ func (i ComputationKind) String() string { case 1010 <= i && i <= 1012: i -= 1010 return _ComputationKind_name_2[_ComputationKind_index_2[i]:_ComputationKind_index_2[i+1]] - case 1025 <= i && i <= 1027: + case 1025 <= i && i <= 1028: i -= 1025 return _ComputationKind_name_3[_ComputationKind_index_3[i]:_ComputationKind_index_3[i+1]] case 1040 <= i && i <= 1042: diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 4e34139977..2fde8680b9 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -2986,6 +2986,9 @@ func (v *ArrayValue) Reverse( count := v.Count() index := count - 1 + // Meter computation for iterating the array. + interpreter.ReportComputation(common.ComputationKindIterateArrayValue, uint(v.Count())) + return NewArrayValueWithIterator( interpreter, v.Type, @@ -3017,6 +3020,9 @@ func (v *ArrayValue) Filter( procedure FunctionValue, ) Value { + // Meter computation for iterating the array. + interpreter.ReportComputation(common.ComputationKindIterateArrayValue, uint(v.Count())) + elementTypeSlice := []sema.Type{v.semaType.ElementType(false)} iterationInvocation := func(arrayElement Value) Invocation { invocation := NewInvocation( @@ -3091,6 +3097,9 @@ func (v *ArrayValue) Map( transformFunctionType *sema.FunctionType, ) Value { + // Meter computation for iterating the array. + interpreter.ReportComputation(common.ComputationKindIterateArrayValue, uint(v.Count())) + elementTypeSlice := []sema.Type{v.semaType.ElementType(false)} iterationInvocation := func(arrayElement Value) Invocation { return NewInvocation( diff --git a/runtime/interpreter/value_string.go b/runtime/interpreter/value_string.go index de8f342297..1f29dd062a 100644 --- a/runtime/interpreter/value_string.go +++ b/runtime/interpreter/value_string.go @@ -126,6 +126,9 @@ func stringFunctionJoin(invocation Invocation) Value { panic(errors.NewUnreachableError()) } + // Meter computation for iterating the array. + inter.ReportComputation(common.ComputationKindIterateArrayValue, uint(stringArray.Count())) + // NewStringMemoryUsage already accounts for empty string. common.UseMemory(inter, common.NewStringMemoryUsage(0)) var builder strings.Builder diff --git a/runtime/tests/interpreter/metering_test.go b/runtime/tests/interpreter/metering_test.go index 8929ebc74b..d269f2b197 100644 --- a/runtime/tests/interpreter/metering_test.go +++ b/runtime/tests/interpreter/metering_test.go @@ -397,3 +397,118 @@ func TestInterpretFunctionInvocationHandler(t *testing.T) { occurrences, ) } + +func TestInterpretArrayFunctionsComputationMetering(t *testing.T) { + + t.Parallel() + + t.Run("reverse", func(t *testing.T) { + t.Parallel() + + computationMeteredValues := make(map[common.ComputationKind]uint) + inter, err := parseCheckAndInterpretWithOptions(t, ` + fun main() { + let x = [1, 2, 3] + let y = x.reverse() + }`, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) { + computationMeteredValues[compKind] += intensity + }, + }, + }, + ) + require.NoError(t, err) + + _, err = inter.Invoke("main") + require.NoError(t, err) + + assert.Equal(t, uint(3), computationMeteredValues[common.ComputationKindIterateArrayValue]) + }) + + t.Run("map", func(t *testing.T) { + t.Parallel() + + computationMeteredValues := make(map[common.ComputationKind]uint) + inter, err := parseCheckAndInterpretWithOptions(t, ` + fun main() { + let x = [1, 2, 3, 4] + let trueForEven = fun (_ x: Int): Bool { + return x % 2 == 0 + } + let y = x.map(trueForEven) + }`, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) { + computationMeteredValues[compKind] += intensity + }, + }, + }, + ) + require.NoError(t, err) + + _, err = inter.Invoke("main") + require.NoError(t, err) + + assert.Equal(t, uint(4), computationMeteredValues[common.ComputationKindIterateArrayValue]) + }) + + t.Run("filter", func(t *testing.T) { + t.Parallel() + + computationMeteredValues := make(map[common.ComputationKind]uint) + inter, err := parseCheckAndInterpretWithOptions(t, ` + fun main() { + let x = [1, 2, 3, 4, 5] + let onlyEven = fun (_ x: Int): Bool { + return x % 2 == 0 + } + let y = x.filter(onlyEven) + }`, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) { + computationMeteredValues[compKind] += intensity + }, + }, + }, + ) + require.NoError(t, err) + + _, err = inter.Invoke("main") + require.NoError(t, err) + + assert.Equal(t, uint(5), computationMeteredValues[common.ComputationKindIterateArrayValue]) + }) +} + +func TestInterpretStdlibComputationMetering(t *testing.T) { + + t.Parallel() + + t.Run("string join", func(t *testing.T) { + t.Parallel() + + computationMeteredValues := make(map[common.ComputationKind]uint) + inter, err := parseCheckAndInterpretWithOptions(t, ` + fun main() { + let s = String.join(["one", "two", "three", "four"], separator: ", ") + }`, + ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnMeterComputation: func(compKind common.ComputationKind, intensity uint) { + computationMeteredValues[compKind] += intensity + }, + }, + }, + ) + require.NoError(t, err) + + _, err = inter.Invoke("main") + require.NoError(t, err) + + assert.Equal(t, uint(4), computationMeteredValues[common.ComputationKindIterateArrayValue]) + }) +} From a07342f532f62ba962806d1141963f79daada4f5 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 19 Oct 2023 09:04:57 -0700 Subject: [PATCH 2/2] Meter on each iteration --- runtime/interpreter/value.go | 18 +++++++++--------- runtime/interpreter/value_string.go | 7 ++++--- runtime/tests/interpreter/metering_test.go | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 2fde8680b9..45f7593aac 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -2986,9 +2986,6 @@ func (v *ArrayValue) Reverse( count := v.Count() index := count - 1 - // Meter computation for iterating the array. - interpreter.ReportComputation(common.ComputationKindIterateArrayValue, uint(v.Count())) - return NewArrayValueWithIterator( interpreter, v.Type, @@ -2999,6 +2996,9 @@ func (v *ArrayValue) Reverse( return nil } + // Meter computation for iterating the array. + interpreter.ReportComputation(common.ComputationKindIterateArrayValue, 1) + value := v.Get(interpreter, locationRange, index) index-- @@ -3020,9 +3020,6 @@ func (v *ArrayValue) Filter( procedure FunctionValue, ) Value { - // Meter computation for iterating the array. - interpreter.ReportComputation(common.ComputationKindIterateArrayValue, uint(v.Count())) - elementTypeSlice := []sema.Type{v.semaType.ElementType(false)} iterationInvocation := func(arrayElement Value) Invocation { invocation := NewInvocation( @@ -3052,6 +3049,9 @@ func (v *ArrayValue) Filter( var value Value for { + // Meter computation for iterating the array. + interpreter.ReportComputation(common.ComputationKindIterateArrayValue, 1) + atreeValue, err := iterator.Next() if err != nil { panic(errors.NewExternalError(err)) @@ -3097,9 +3097,6 @@ func (v *ArrayValue) Map( transformFunctionType *sema.FunctionType, ) Value { - // Meter computation for iterating the array. - interpreter.ReportComputation(common.ComputationKindIterateArrayValue, uint(v.Count())) - elementTypeSlice := []sema.Type{v.semaType.ElementType(false)} iterationInvocation := func(arrayElement Value) Invocation { return NewInvocation( @@ -3148,6 +3145,9 @@ func (v *ArrayValue) Map( uint64(v.Count()), func() Value { + // Meter computation for iterating the array. + interpreter.ReportComputation(common.ComputationKindIterateArrayValue, 1) + atreeValue, err := iterator.Next() if err != nil { panic(errors.NewExternalError(err)) diff --git a/runtime/interpreter/value_string.go b/runtime/interpreter/value_string.go index 1f29dd062a..4922cc5ab2 100644 --- a/runtime/interpreter/value_string.go +++ b/runtime/interpreter/value_string.go @@ -126,15 +126,16 @@ func stringFunctionJoin(invocation Invocation) Value { panic(errors.NewUnreachableError()) } - // Meter computation for iterating the array. - inter.ReportComputation(common.ComputationKindIterateArrayValue, uint(stringArray.Count())) - // NewStringMemoryUsage already accounts for empty string. common.UseMemory(inter, common.NewStringMemoryUsage(0)) var builder strings.Builder first := true stringArray.Iterate(inter, func(element Value) (resume bool) { + + // Meter computation for iterating the array. + inter.ReportComputation(common.ComputationKindIterateArrayValue, 1) + // Add separator if !first { // Construct directly instead of using NewStringMemoryUsage to avoid diff --git a/runtime/tests/interpreter/metering_test.go b/runtime/tests/interpreter/metering_test.go index d269f2b197..a2fd7ab596 100644 --- a/runtime/tests/interpreter/metering_test.go +++ b/runtime/tests/interpreter/metering_test.go @@ -452,7 +452,7 @@ func TestInterpretArrayFunctionsComputationMetering(t *testing.T) { _, err = inter.Invoke("main") require.NoError(t, err) - assert.Equal(t, uint(4), computationMeteredValues[common.ComputationKindIterateArrayValue]) + assert.Equal(t, uint(5), computationMeteredValues[common.ComputationKindIterateArrayValue]) }) t.Run("filter", func(t *testing.T) { @@ -480,7 +480,7 @@ func TestInterpretArrayFunctionsComputationMetering(t *testing.T) { _, err = inter.Invoke("main") require.NoError(t, err) - assert.Equal(t, uint(5), computationMeteredValues[common.ComputationKindIterateArrayValue]) + assert.Equal(t, uint(6), computationMeteredValues[common.ComputationKindIterateArrayValue]) }) }