|  | // Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import 'dart:js_interop'; | 
|  | import 'dart:math' as math; | 
|  |  | 
|  | import 'package:benchmark_harness/benchmark_harness.dart'; | 
|  |  | 
|  | // Benchmark for converting Dart functions to JS and calling them. | 
|  | // | 
|  | // Contains the following benchmarks: | 
|  | // - A series that determines how expensive calling `Function.toJS` is. These | 
|  | //   are prefixed with `Convert`. These contain two dimensions in their names: | 
|  | //   - Whether the function that is being wrapped is an `Instance` method, | 
|  | //     `Static` method, `Closure`, or a closure that is stored in a field | 
|  | //     (`ClosureField`). | 
|  | //   - The arity of the function: `Zero`, `Two`, and `Eight` arguments. | 
|  | // - A series that determines how expensive calling the result of a | 
|  | //   `Function.toJS`-wrapped function using interop is. These are prefixed with | 
|  | //   `CallJS`. These contain the same dimensions as the `Convert` series, except | 
|  | //   there is no `ClosureField` series as it would be the same as `Closure`. | 
|  | // - A series that determines how expensive calling a closure that's stored in a | 
|  | //   field in Dart is. These are prefixed with `CallDartClosure`. They only | 
|  | //   contain one dimension, which is the arity of the function. This is used to | 
|  | //   compare the performance of calling a wrapped function versus calling it | 
|  | //   directly. | 
|  |  | 
|  | // Caching the function to a global should help avoid V8 from optimizing the | 
|  | // call away. | 
|  | @JS() | 
|  | external set cache(JSFunction jsFunction); | 
|  |  | 
|  | extension on JSFunction { | 
|  | external int call( | 
|  | [JSAny? thisArg, | 
|  | int? arg1, | 
|  | int? arg2, | 
|  | int? arg3, | 
|  | int? arg4, | 
|  | int? arg5, | 
|  | int? arg6, | 
|  | int? arg7, | 
|  | int? arg8]); | 
|  | } | 
|  |  | 
|  | final random = math.Random(); | 
|  |  | 
|  | class ConvertInstanceZeroBenchmark extends BenchmarkBase { | 
|  | ConvertInstanceZeroBenchmark() : super('FunctionToJs.Convert.Instance.0'); | 
|  |  | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @pragma('dart2js:never-inline') | 
|  | int zero() => randomInt; | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = zero.toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertInstanceTwoBenchmark extends BenchmarkBase { | 
|  | ConvertInstanceTwoBenchmark() : super('FunctionToJs.Convert.Instance.2'); | 
|  |  | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @pragma('dart2js:never-inline') | 
|  | int two(int arg1, int arg2) => randomInt + arg1 + arg2; | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = two.toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertInstanceEightBenchmark extends BenchmarkBase { | 
|  | ConvertInstanceEightBenchmark() : super('FunctionToJs.Convert.Instance.8'); | 
|  |  | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @pragma('dart2js:never-inline') | 
|  | int eight(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, | 
|  | int arg7, int arg8) => | 
|  | randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8; | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = eight.toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertStaticZeroBenchmark extends BenchmarkBase { | 
|  | ConvertStaticZeroBenchmark() : super('FunctionToJs.Convert.Static.0'); | 
|  |  | 
|  | static final int randomInt = random.nextInt(10); | 
|  |  | 
|  | static int zero() => randomInt; | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = zero.toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertStaticTwoBenchmark extends BenchmarkBase { | 
|  | ConvertStaticTwoBenchmark() : super('FunctionToJs.Convert.Static.2'); | 
|  |  | 
|  | static final int randomInt = random.nextInt(10); | 
|  |  | 
|  | static int two(int arg1, int arg2) => randomInt + arg1 + arg2; | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = two.toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertStaticEightBenchmark extends BenchmarkBase { | 
|  | ConvertStaticEightBenchmark() : super('FunctionToJs.Convert.Static.8'); | 
|  |  | 
|  | static final int randomInt = random.nextInt(10); | 
|  |  | 
|  | static int eight(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, | 
|  | int arg7, int arg8) => | 
|  | randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8; | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = eight.toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertClosureZeroBenchmark extends BenchmarkBase { | 
|  | ConvertClosureZeroBenchmark() : super('FunctionToJs.Convert.Closure.0'); | 
|  |  | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = (() => randomInt).toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertClosureTwoBenchmark extends BenchmarkBase { | 
|  | ConvertClosureTwoBenchmark() : super('FunctionToJs.Convert.Closure.2'); | 
|  |  | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = ((int arg1, int arg2) => randomInt + arg1 + arg2).toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertClosureEightBenchmark extends BenchmarkBase { | 
|  | ConvertClosureEightBenchmark() : super('FunctionToJs.Convert.Closure.8'); | 
|  |  | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = ((int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, | 
|  | int arg7, int arg8) => | 
|  | randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8).toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertClosureFieldZeroBenchmark extends BenchmarkBase { | 
|  | ConvertClosureFieldZeroBenchmark() | 
|  | : super('FunctionToJs.Convert.ClosureField.0'); | 
|  |  | 
|  | late int Function() closure; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | closure = () => randomInt; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = closure.toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertClosureFieldTwoBenchmark extends BenchmarkBase { | 
|  | ConvertClosureFieldTwoBenchmark() | 
|  | : super('FunctionToJs.Convert.ClosureField.2'); | 
|  |  | 
|  | late int Function(int, int) closure; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | closure = (int arg1, int arg2) => randomInt + arg1 + arg2; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = closure.toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConvertClosureFieldEightBenchmark extends BenchmarkBase { | 
|  | ConvertClosureFieldEightBenchmark() | 
|  | : super('FunctionToJs.Convert.ClosureField.8'); | 
|  |  | 
|  | late int Function(int, int, int, int, int, int, int, int) closure; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | closure = (int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, | 
|  | int arg7, int arg8) => | 
|  | randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | cache = closure.toJS; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallJSInstanceZeroBenchmark extends BenchmarkBase { | 
|  | CallJSInstanceZeroBenchmark() : super('FunctionToJs.CallJS.Instance.0'); | 
|  |  | 
|  | late JSExportedDartFunction jsFunction; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @pragma('dart2js:never-inline') | 
|  | int zero() => randomInt; | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | jsFunction = zero.toJS; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = jsFunction.call(); | 
|  | if (val < 0 || val >= 10) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallJSInstanceTwoBenchmark extends BenchmarkBase { | 
|  | CallJSInstanceTwoBenchmark() : super('FunctionToJs.CallJS.Instance.2'); | 
|  |  | 
|  | late JSExportedDartFunction jsFunction; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @pragma('dart2js:never-inline') | 
|  | int two(int arg1, int arg2) => randomInt + arg1 + arg2; | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | jsFunction = two.toJS; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = jsFunction.call(null, 1, 1); | 
|  | if (val < 2 || val >= 12) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallJSInstanceEightBenchmark extends BenchmarkBase { | 
|  | CallJSInstanceEightBenchmark() : super('FunctionToJs.CallJS.Instance.8'); | 
|  |  | 
|  | late JSExportedDartFunction jsFunction; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @pragma('dart2js:never-inline') | 
|  | int eight(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, | 
|  | int arg7, int arg8) => | 
|  | randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8; | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | jsFunction = eight.toJS; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = jsFunction.call(null, 1, 1, 1, 1, 1, 1, 1, 1); | 
|  | if (val < 8 || val >= 18) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallJSStaticZeroBenchmark extends BenchmarkBase { | 
|  | CallJSStaticZeroBenchmark() : super('FunctionToJs.CallJS.Static.0'); | 
|  |  | 
|  | static final int randomInt = random.nextInt(10); | 
|  |  | 
|  | static int zero() => randomInt; | 
|  |  | 
|  | late JSExportedDartFunction jsFunction; | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | jsFunction = zero.toJS; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = jsFunction.call(); | 
|  | if (val < 0 || val >= 10) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallJSStaticTwoBenchmark extends BenchmarkBase { | 
|  | CallJSStaticTwoBenchmark() : super('FunctionToJs.CallJS.Static.2'); | 
|  |  | 
|  | static final int randomInt = random.nextInt(10); | 
|  |  | 
|  | static int two(int arg1, int arg2) => randomInt + arg1 + arg2; | 
|  |  | 
|  | late JSExportedDartFunction jsFunction; | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | jsFunction = two.toJS; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = jsFunction.call(null, 1, 1); | 
|  | if (val < 2 || val >= 12) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallJSStaticEightBenchmark extends BenchmarkBase { | 
|  | CallJSStaticEightBenchmark() : super('FunctionToJs.CallJS.Static.8'); | 
|  |  | 
|  | static final int randomInt = random.nextInt(10); | 
|  |  | 
|  | static int eight(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, | 
|  | int arg7, int arg8) => | 
|  | randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8; | 
|  |  | 
|  | late JSExportedDartFunction jsFunction; | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | jsFunction = eight.toJS; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = jsFunction.call(null, 1, 1, 1, 1, 1, 1, 1, 1); | 
|  | if (val < 8 || val >= 18) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallJSClosureZeroBenchmark extends BenchmarkBase { | 
|  | CallJSClosureZeroBenchmark() : super('FunctionToJs.CallJS.Closure.0'); | 
|  |  | 
|  | late JSExportedDartFunction jsFunction; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | jsFunction = (() => randomInt).toJS; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = jsFunction.call(); | 
|  | if (val < 0 || val >= 10) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallJSClosureTwoBenchmark extends BenchmarkBase { | 
|  | CallJSClosureTwoBenchmark() : super('FunctionToJs.CallJS.Closure.2'); | 
|  |  | 
|  | late JSExportedDartFunction jsFunction; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | jsFunction = ((int arg1, int arg2) => randomInt + arg1 + arg2).toJS; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = jsFunction.call(null, 1, 1); | 
|  | if (val < 2 || val >= 12) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallJSClosureEightBenchmark extends BenchmarkBase { | 
|  | CallJSClosureEightBenchmark() : super('FunctionToJs.CallJS.Closure.8'); | 
|  |  | 
|  | late JSExportedDartFunction jsFunction; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | jsFunction = ((int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, | 
|  | int arg7, int arg8) => | 
|  | randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8).toJS; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = jsFunction.call(null, 1, 1, 1, 1, 1, 1, 1, 1); | 
|  | if (val < 8 || val >= 18) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallDartClosureZeroBenchmark extends BenchmarkBase { | 
|  | CallDartClosureZeroBenchmark() : super('FunctionToJs.CallDart.Closure.0'); | 
|  |  | 
|  | late int Function() closure; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | closure = () => randomInt; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = closure(); | 
|  | if (val < 0 || val >= 10) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallDartClosureTwoBenchmark extends BenchmarkBase { | 
|  | CallDartClosureTwoBenchmark() : super('FunctionToJs.CallDart.Closure.2'); | 
|  |  | 
|  | late int Function(int, int) closure; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | closure = (int arg1, int arg2) => randomInt + arg1 + arg2; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = closure(1, 1); | 
|  | if (val < 2 || val >= 12) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CallDartClosureEightBenchmark extends BenchmarkBase { | 
|  | CallDartClosureEightBenchmark() : super('FunctionToJs.CallDart.Closure.8'); | 
|  |  | 
|  | late int Function(int, int, int, int, int, int, int, int) closure; | 
|  | final int randomInt = random.nextInt(10); | 
|  |  | 
|  | @override | 
|  | void setup() { | 
|  | closure = (int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, | 
|  | int arg7, int arg8) => | 
|  | randomInt + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | final val = closure(1, 1, 1, 1, 1, 1, 1, 1); | 
|  | if (val < 8 || val >= 18) throw 'Bad result: $val'; | 
|  | } | 
|  | } | 
|  |  | 
|  | void main() { | 
|  | final benchmarks = [ | 
|  | ConvertInstanceZeroBenchmark(), | 
|  | ConvertInstanceTwoBenchmark(), | 
|  | ConvertInstanceEightBenchmark(), | 
|  | ConvertStaticZeroBenchmark(), | 
|  | ConvertStaticTwoBenchmark(), | 
|  | ConvertStaticEightBenchmark(), | 
|  | ConvertClosureZeroBenchmark(), | 
|  | ConvertClosureTwoBenchmark(), | 
|  | ConvertClosureEightBenchmark(), | 
|  | ConvertClosureFieldZeroBenchmark(), | 
|  | ConvertClosureFieldTwoBenchmark(), | 
|  | ConvertClosureFieldEightBenchmark(), | 
|  | CallJSInstanceZeroBenchmark(), | 
|  | CallJSInstanceTwoBenchmark(), | 
|  | CallJSInstanceEightBenchmark(), | 
|  | CallJSStaticZeroBenchmark(), | 
|  | CallJSStaticTwoBenchmark(), | 
|  | CallJSStaticEightBenchmark(), | 
|  | CallJSClosureZeroBenchmark(), | 
|  | CallJSClosureTwoBenchmark(), | 
|  | CallJSClosureEightBenchmark(), | 
|  | CallDartClosureZeroBenchmark(), | 
|  | CallDartClosureTwoBenchmark(), | 
|  | CallDartClosureEightBenchmark(), | 
|  | ]; | 
|  | // Warmup all benchmarks so that the first benchmark doesn't get overfitted by | 
|  | // a JIT compiler. | 
|  | for (final benchmark in benchmarks) { | 
|  | benchmark.setup(); | 
|  | } | 
|  | for (var i = 0; i < 10; i++) { | 
|  | for (final benchmark in benchmarks) { | 
|  | benchmark.run(); | 
|  | } | 
|  | } | 
|  | for (final benchmark in benchmarks) { | 
|  | benchmark.report(); | 
|  | } | 
|  | } |