|  | // Copyright (c) 2025, 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 'package:benchmark_harness/benchmark_harness.dart'; | 
|  | import 'dart:js_interop'; | 
|  |  | 
|  | // Static js-interop benchmarks. | 
|  | // | 
|  | // Type checks: | 
|  | // | 
|  | //     JSInterop.as.Foo     - x as Foo | 
|  | //     JSInterop.as.T.Foo   - x as T, where T is Foo | 
|  | // | 
|  | // Type tests: | 
|  | // | 
|  | //     JSInterop.isA.Foo    - x.isA<Foo>() | 
|  | // | 
|  | // Calls. | 
|  | // | 
|  | //     JSInterop.call.moveFoo - | 
|  | //       Repeatedly copy the same Foo (some kind of JSObject) to JavaScript and | 
|  | //       back. | 
|  | // | 
|  | //     JSInterop.call.{inline,hoisted,implicit}3ArgsSSS | 
|  | //       Passes three String arguments, using different positioning of `.toJS` | 
|  | //       conversions. | 
|  | // | 
|  | //     JSInterop.call.{inline,hoisted,implicit}3ArgsIII | 
|  | //       Similar to JSInterop.call.*ArgsSSS but passing integer Numbers instead. | 
|  | // | 
|  | //     JSInterop.call.inline7Args | 
|  | //       Compare with JSInterop.call.inline3ArgsSSS to assess the cost of more | 
|  | //       arguments. | 
|  |  | 
|  | /// Aliases for `Object.assign` with different types. | 
|  | /// | 
|  | /// `Object.assign` with one argument returns the argument. It is a fast | 
|  | /// JavaScript method that returns a JSObject. | 
|  | @JS('Object') | 
|  | extension type ObjectMethods(JSObject _) implements JSObject { | 
|  | @JS('assign') | 
|  | external static JSObject moveJSObject(JSObject o); | 
|  |  | 
|  | @JS('assign') | 
|  | external static JSArray moveJSArray(JSArray o); | 
|  |  | 
|  | @JS('assign') | 
|  | external static JSUint8Array moveJSUint8Array(JSUint8Array o); | 
|  | } | 
|  |  | 
|  | // Alias for `.toString()` with different types. | 
|  | // | 
|  | // `x.toString()`, when called on a JSString receiver, calls | 
|  | // `String.prototype.toString`, which is fast, since it returns the | 
|  | // receiver. Additional arguments are ignored. | 
|  | extension ToStringMethod on JSAny { | 
|  | @JS('toString') | 
|  | external JSString toString1(JSAny? a); | 
|  |  | 
|  | @JS('toString') | 
|  | external JSString toString3SSS(JSString a, JSString b, JSString c); | 
|  |  | 
|  | @JS('toString') | 
|  | external JSString toString3NNN(JSNumber a, JSNumber b, JSNumber c); | 
|  |  | 
|  | // This version uses Dart primitive types which implies an implicit | 
|  | // conversion. | 
|  | @JS('toString') | 
|  | external String toString3ConvertedSSS(String a, String b, String c); | 
|  |  | 
|  | // This version uses Dart primitive types which implies an implicit | 
|  | // conversion. | 
|  | @JS('toString') | 
|  | external String toString3ConvertedIII(int a, int b, int c); | 
|  |  | 
|  | @JS('toString') | 
|  | external JSString toString7( | 
|  | JSAny? a1, | 
|  | JSAny? a2, | 
|  | JSAny? a3, | 
|  | JSAny? a4, | 
|  | JSAny? a5, | 
|  | JSAny? a6, | 
|  | JSAny? a7, | 
|  | ); | 
|  | } | 
|  |  | 
|  | extension type Date._(JSObject _) implements JSObject { | 
|  | external Date.forTimestamp(int _); | 
|  | external static int now(); | 
|  | } | 
|  |  | 
|  | const int N = 1000; | 
|  |  | 
|  | class Base extends BenchmarkBase { | 
|  | Base(super.name); | 
|  | } | 
|  |  | 
|  | List<Object?> allJSObjects() { | 
|  | return List<Object?>.generate(N, (i) => createObject(), growable: false); | 
|  | } | 
|  |  | 
|  | List<Object?> allJSAnys() { | 
|  | return allJSObjects() | 
|  | ..[1] = 123.toJS | 
|  | ..[2] = 'x'.toJS; | 
|  | } | 
|  |  | 
|  | List<Object?> allJSAnyQs() { | 
|  | return allJSObjects() | 
|  | ..[0] = null | 
|  | ..[1] = 123.toJS | 
|  | ..[2] = 'x'.toJS; | 
|  | } | 
|  |  | 
|  | final List<Object?> allJSNumbers = List<Object?>.generate( | 
|  | N, | 
|  | (i) => i.toJS, | 
|  | growable: false, | 
|  | ); | 
|  |  | 
|  | final List<Object?> allJSStrings = List<Object?>.generate( | 
|  | N, | 
|  | (i) => '$i'.toJS, | 
|  | growable: false, | 
|  | ); | 
|  |  | 
|  | final List<Object?> allJSUint8Array = List<Object?>.generate( | 
|  | N, | 
|  | (_) => JSUint8Array.withLength(2), | 
|  | growable: false, | 
|  | ); | 
|  |  | 
|  | abstract class CastsBase extends Base { | 
|  | final List<Object?> data; | 
|  | CastsBase(String name, {required this.data}) : super('JSInterop.as.$name'); | 
|  | } | 
|  |  | 
|  | class Casts1 extends CastsBase { | 
|  | Casts1() : super('JSObject', data: allJSObjects()); | 
|  |  | 
|  | void run() { | 
|  | for (final o in data) { | 
|  | sink = o as JSObject; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Casts2 extends CastsBase { | 
|  | Casts2() : super('JSAny', data: allJSAnys()); | 
|  |  | 
|  | void run() { | 
|  | for (final o in data) { | 
|  | sink = o as JSAny; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Casts3 extends CastsBase { | 
|  | Casts3() : super('JSAnyQ', data: allJSAnyQs()); | 
|  |  | 
|  | void run() { | 
|  | for (final o in data) { | 
|  | sink = o as JSAny?; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Casts4 extends CastsBase { | 
|  | Casts4() : super('JSNumber', data: allJSNumbers); | 
|  |  | 
|  | void run() { | 
|  | for (final o in data) { | 
|  | sink = o as JSNumber; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Casts5 extends CastsBase { | 
|  | Casts5() : super('JSString', data: allJSStrings); | 
|  |  | 
|  | void run() { | 
|  | for (final o in data) { | 
|  | sink = o as JSString; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Casts6 extends CastsBase { | 
|  | Casts6() : super('JSUint8Array', data: allJSUint8Array); | 
|  |  | 
|  | void run() { | 
|  | for (final o in data) { | 
|  | sink = o as JSUint8Array; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class CastsT<T> extends CastsBase { | 
|  | CastsT(String name, {required super.data}) : super('T.$name'); | 
|  |  | 
|  | void run() { | 
|  | for (final o in data) { | 
|  | sink = o as T; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls1SSS extends Base { | 
|  | Calls1SSS() : super('JSInterop.calls.inline3ArgsSSS'); | 
|  |  | 
|  | void run() { | 
|  | String s = 'hello'; | 
|  | for (int i = 0; i < N; i++) { | 
|  | // Pass in arguments with '.toJS' conversion. | 
|  | // There is potential for a compiler to hoist the conversions. | 
|  | // Implicit conversion of result of `toString3` to JSString. | 
|  | // Explicit `.toDart` conversion of result JSString to Dart String. | 
|  | sink = s = s.toJS.toString3SSS('a'.toJS, 'b'.toJS, 'c'.toJS).toDart; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls1NNN extends Base { | 
|  | Calls1NNN() : super('JSInterop.calls.inline3ArgsIII'); | 
|  |  | 
|  | void run() { | 
|  | String s = 'hello'; | 
|  | for (int i = 0; i < N; i++) { | 
|  | // Pass in arguments with '.toJS' conversion. | 
|  | // There is potential for a compiler to hoist the conversions. | 
|  | // Implicit conversion of result of `toString3` to JSString. | 
|  | // Explicit `.toDart` conversion of result JSString to Dart String. | 
|  | sink = s = s.toJS.toString3NNN(1.toJS, 2.toJS, 3.toJS).toDart; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls2SSS extends Base { | 
|  | Calls2SSS() : super('JSInterop.calls.hoisted3ArgsSSS'); | 
|  |  | 
|  | static final _a = 'a'.toJS; | 
|  | static final _b = 'b'.toJS; | 
|  | static final _c = 'c'.toJS; | 
|  |  | 
|  | void run() { | 
|  | String s = 'hello'; | 
|  | for (int i = 0; i < N; i++) { | 
|  | // Manually hoisted argument conversions. | 
|  | // Implicit conversion of result of `toString3` to JSString. | 
|  | // Explicit `.toDart` conversion of result JSString to Dart String. | 
|  | sink = s = s.toJS.toString3SSS(_a, _b, _c).toDart; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls2NNN extends Base { | 
|  | Calls2NNN() : super('JSInterop.calls.hoisted3ArgsIII'); | 
|  |  | 
|  | static final _a = 1.toJS; | 
|  | static final _b = 2.toJS; | 
|  | static final _c = 3.toJS; | 
|  |  | 
|  | void run() { | 
|  | String s = 'hello'; | 
|  | for (int i = 0; i < N; i++) { | 
|  | // Manually hoisted argument conversions. | 
|  | // Implicit conversion of result of `toString3` to JSString. | 
|  | // Explicit `.toDart` conversion of result JSString to Dart String. | 
|  | sink = s = s.toJS.toString3NNN(_a, _b, _c).toDart; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls3SSS extends Base { | 
|  | Calls3SSS() : super('JSInterop.calls.implicit3ArgsSSS'); | 
|  |  | 
|  | void run() { | 
|  | String s = 'hello'; | 
|  | for (int i = 0; i < N; i++) { | 
|  | // Implicit conversion of arguments and result as they have a primitive | 
|  | // type. | 
|  | sink = s = s.toJS.toString3ConvertedSSS('a', 'b', 'c'); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls3NNN extends Base { | 
|  | Calls3NNN() : super('JSInterop.calls.implicit3ArgsIII'); | 
|  |  | 
|  | void run() { | 
|  | String s = 'hello'; | 
|  | for (int i = 0; i < N; i++) { | 
|  | // Implicit conversion of arguments and result as they have a primitive | 
|  | // type. | 
|  | sink = s = s.toJS.toString3ConvertedIII(1, 2, 3); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls4 extends Base { | 
|  | Calls4() : super('JSInterop.calls.inline7Args'); | 
|  |  | 
|  | void run() { | 
|  | String s = 'hello'; | 
|  | for (int i = 0; i < N; i++) { | 
|  | sink = s = s.toJS | 
|  | .toString7( | 
|  | '1'.toJS, | 
|  | '2'.toJS, | 
|  | '3'.toJS, | 
|  | '4'.toJS, | 
|  | '5'.toJS, | 
|  | '6'.toJS, | 
|  | '7'.toJS, | 
|  | ) | 
|  | .toDart; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls5 extends Base { | 
|  | Calls5() : super('JSInterop.calls.moveJSObject'); | 
|  |  | 
|  | static final _o = JSObject(); | 
|  | void run() { | 
|  | JSObject o = _o; | 
|  | for (int i = 0; i < N; i++) { | 
|  | // Implicit conversion of result to JSObject. | 
|  | sink = o = ObjectMethods.moveJSObject(o); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls6 extends Base { | 
|  | Calls6() : super('JSInterop.calls.moveJSArray'); | 
|  |  | 
|  | static final _o = JSArray.withLength(2); | 
|  | void run() { | 
|  | JSArray o = _o; | 
|  | for (int i = 0; i < N; i++) { | 
|  | // Implicit conversion of result to JSArray. | 
|  | sink = o = ObjectMethods.moveJSArray(o); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class Calls7 extends Base { | 
|  | Calls7() : super('JSInterop.calls.moveJSUint8Array'); | 
|  |  | 
|  | static final _o = JSUint8Array.withLength(2); | 
|  | void run() { | 
|  | JSUint8Array o = _o; | 
|  | for (int i = 0; i < N; i++) { | 
|  | // Implicit conversion of result to JSUint8Array. | 
|  | sink = o = ObjectMethods.moveJSUint8Array(o); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | List<JSAny?> _objects() => List<JSAny?>.generate( | 
|  | N, | 
|  | (i) => switch (i % 7) { | 
|  | 0 => JSObject(), | 
|  | 1 => i.toJS, | 
|  | 2 => JSArray(), | 
|  | 3 => '$i'.toJS, | 
|  | 4 => Date.forTimestamp(Date.now()), | 
|  | 5 => null, | 
|  | 6 => JSUint8Array.withLength(2), | 
|  | int() => throw StateError('unexpected: $i'), | 
|  | }, | 
|  | growable: false, | 
|  | ); | 
|  |  | 
|  | abstract class IsABase extends Base { | 
|  | IsABase(String name) : super('JSInterop.isA.$name'); | 
|  |  | 
|  | final List<JSAny?> objects = _objects(); | 
|  | } | 
|  |  | 
|  | class IsA1 extends IsABase { | 
|  | IsA1() : super('JSObject'); | 
|  | void run() { | 
|  | for (final o in objects) { | 
|  | sink = o.isA<JSObject>(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class IsA2 extends IsABase { | 
|  | IsA2() : super('JSAny'); | 
|  | void run() { | 
|  | for (final o in objects) { | 
|  | sink = o.isA<JSAny>(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class IsA3 extends IsABase { | 
|  | IsA3() : super('JSString'); | 
|  | void run() { | 
|  | for (final o in objects) { | 
|  | sink = o.isA<JSString>(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class IsA4 extends IsABase { | 
|  | IsA4() : super('JSArray'); | 
|  | void run() { | 
|  | for (final o in objects) { | 
|  | sink = o.isA<JSArray>(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class IsA5 extends IsABase { | 
|  | IsA5() : super('Date'); | 
|  | void run() { | 
|  | for (final o in objects) { | 
|  | sink = o.isA<Date>(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class IsA6 extends IsABase { | 
|  | IsA6() : super('DateQ'); | 
|  | void run() { | 
|  | for (final o in objects) { | 
|  | sink = o.isA<Date?>(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class IsA7 extends IsABase { | 
|  | IsA7() : super('JSUint8Array'); | 
|  | void run() { | 
|  | for (final o in objects) { | 
|  | sink = o.isA<JSUint8Array>(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns a new JSObject, but with a type that is hard for an optimizing | 
|  | /// compiler to know exactly. | 
|  | Object? createObject() { | 
|  | if (inscrutableTrue) return JSObject(); | 
|  | if (inscrutableTrue) return 123.toJS; | 
|  | if (inscrutableTrue) return 'x'.toJS; | 
|  | if (inscrutableTrue) return true.toJS; | 
|  | if (inscrutableTrue) return StringBuffer(); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | bool get inscrutableTrue => DateTime.now().millisecondsSinceEpoch > 42; | 
|  |  | 
|  | Object? sink; | 
|  |  | 
|  | void main() { | 
|  | print(Date.forTimestamp(Date.now())); | 
|  |  | 
|  | final benchmarks = [ | 
|  | IsA1(), | 
|  | IsA2(), | 
|  | IsA3(), | 
|  | IsA4(), | 
|  | IsA5(), | 
|  | IsA6(), | 
|  | IsA7(), | 
|  |  | 
|  | Calls1SSS(), | 
|  | Calls1NNN(), | 
|  | Calls2SSS(), | 
|  | Calls2NNN(), | 
|  | Calls3SSS(), | 
|  | Calls3NNN(), | 
|  | Calls4(), | 
|  | Calls5(), | 
|  | Calls6(), | 
|  | Calls7(), | 
|  |  | 
|  | Casts1(), | 
|  | Casts2(), | 
|  | Casts3(), | 
|  | Casts4(), | 
|  | Casts5(), | 
|  | Casts6(), | 
|  |  | 
|  | CastsT<JSObject>('JSObject', data: allJSObjects()), | 
|  | CastsT<JSAny>('JSAny', data: allJSAnys()), | 
|  | CastsT<JSAny?>('JSAnyQ', data: allJSAnyQs()), | 
|  | CastsT<JSNumber>('JSNumber', data: allJSNumbers), | 
|  | CastsT<JSString>('JSString', data: allJSStrings), | 
|  | CastsT<JSUint8Array>('JSUint8Array', data: allJSUint8Array), | 
|  | ]; | 
|  |  | 
|  | // Warmup all benchmarks to ensure JIT compilers see full polymorphism. | 
|  | for (var benchmark in benchmarks) { | 
|  | benchmark.setup(); | 
|  | benchmark.run(); | 
|  | benchmark.run(); | 
|  | if (sink == null) throw StateError('unexpected'); | 
|  | } | 
|  |  | 
|  | for (var benchmark in benchmarks) { | 
|  | benchmark.run(); | 
|  | } | 
|  |  | 
|  | for (var benchmark in benchmarks) { | 
|  | benchmark.report(); | 
|  | } | 
|  | } |