by Slava Egorov
Nobody have seen or reviewed this talk. The content of this talk is aspirational and does not necessarily reflect plans of the Dart or Flutter teams.
is a construction built out of smaller pieces
// Which one is faster?
// Classic indexed loop.
for (var i = 0; i < list.length; i++) {
final e = list[i];
}
// or this Dart 3.0 beauty?
for (var (i, e) in list.indexed) {
// ...
}
// Which one is faster? (On native)
// Classic indexed loop.
for (var i = 0; i < list.length; i++) {
final e = list[i];
}
// or this Dart 3.0 beauty?
for (var (i, e) in list.indexed) {
// ...
}
$ dart compile exe -o loops loops.dart
$ ./loops
$ flutter run --release -t lib/loops.dart
import 'dart:isolate';
Isolate.spawnUri('my-aot-snapshot.aot');
#include "dart_api.h"
Dart_Initialize(...); // Init runtime system
isolate = Dart_CreateIsolateGroup(
// ...
"main", // isolate name
snapshot_data,
snapshot_code,
// ...
);
$ bazel build :gen_kernel \
:platform.dill \
:gen_snapshot \
:run_aot
$ bazel-bin/gen_kernel --aot \
--platform bazel-bin/platform.dill \
-o /tmp/loops.dill \
dart/loops.dart
$ bazel-bin/gen_snapshot \
--snapshot-kind=app-aot-assembly \
--snapshot=/tmp/loops.S \
/tmp/loops.dill
$ gcc -shared -o /tmp/loops.dylib /tmp/loops.S
$ bazel-bin/run_aot /tmp/loops.dylib
$ bazel-bin/run_aot /tmp/loops.dylib
ClassicLoop(RunTime): 63.10348942208462 us.
ForInIndexedLoop(RunTime): 819.2325 us.
AOT compiler can generate DWARF debug info
$ bazel-bin/gen_snapshot \
--dwarf-stack-traces \
--resolve-dwarf-paths \
...
AOT compiler can print its IL
$ bazel-bin/gen_snapshot \
--print-flow-graph-optimized \
--print-flow-graph-filter=... \
--disassemble-optimized \
...
Not enough inlining ⇒ not enough specialization
B1:
v2 ← Parameter(0)
v5 ← LoadField(v2 . list)
v63 ← StaticCall( IndexedIterable. v65, v5)
v7 ← StaticCall( get:iterator v63)
v49 ← LoadField(v7 . _source)
v89 ← LoadField(v49 . _iterable)
// ...
B3:
v98 ← DispatchTableCall( Iterable.elementAt, v89, v94)
Use @pragma('vm:prefer-inline')
// In sdk/lib/internal/iterable.dart
class IndexedIterable<T> extends Iterable<(int, T)> {
// ...
@pragma('vm:prefer-inline')
factory IndexedIterable(Iterable<T> source, int start) {
// ...
}
// ...
@pragma('vm:prefer-inline')
Iterator<(int, T)> get iterator => // ...
}
$ bazel-bin/run_aot /tmp/loops.dylib
ClassicLoop(RunTime): 62.95430732508757 us.
ForInIndexedLoop(RunTime): 249.33367062990135 us
ForInIndexedLoop
improved by 3x
Compiler did not inline int.operator&
B32:
v20 ← StaticCall( & v242, v292, recognized_kind = Integer_bitAnd)
Compiler got confused about the type of e
for (var (i, int e) in list.indexed) {
// ...
}
Fix the compiler!
LoadIndexed(...)
from a List<int>
returns int
$ bazel-bin/run_aot /tmp/loops.dylib
ClassicLoop(RunTime): 62.71734375 us.
ForInIndexedLoop(RunTime): 109.20825648258652 us.
ForInIndexedLoop
improved by 2.5x
Unfused and redundant comparison
v44 ← RelationalOp(>=, v42, v263)
Branch if StrictCompare(===, v44, v10) goto (37, 18)
B37:
Branch if RelationalOp(>=, v256, v235) goto (19, 3)
B3:
Branch if StrictCompare(===, v44, v10) goto (32, 11)
Fix the compiler!
x === true
and x === false
into blocks which comparison dominates
$ bazel-bin/run_aot /tmp/loops.dylib
ClassicLoop(RunTime): 62.81853125 us.
ForInIndexedLoop(RunTime): 93.52465990369782 us.
ForInIndexedLoop
improved by 15%
Left over iteration variable, redundant comparison and a bounds check
final l = list.length;
for (var i = 0, j = -1;
++j >= 0 && i < l;
i++) {
if (i >= l) throw OutOfBoundsError();
final e = list[i];
result ^= i & e;
}
Need to fix issues with range analysis
Much bigger project than other fixes
struct HelloWorldView: View {
@State private var name: String = ""
var body: some View {
VStack(alignment: .leading) {
TextField("Your name?", text: $name)
Text("Hello, \(name)")
}
}
}
struct HelloWorldView: View {
@State private var name: String = ""
var body: some View {
VStack(alignment: .leading) {
TextField("Your name?", text: $name)
Text("\(askDart(name))")
}
}
}
func askDart(_ input : String) -> String {
// ???
}
func askDart(_ input : String) -> String {
let args : [Dart_Handle] = [
Dart_NewStringFromCString(input)
]
let result =
args.withUnsafeBufferPointer { args in
return Dart_Invoke(
Dart_RootLibrary(),
Dart_NewStringFromCString("askDart"),
1,
args
)
}
// ...
}
dart:ffi
?Accessing any C ABI code from Dart is simple
import 'dart:ffi';
typedef CString = Pointer<Utf8>;
@Native<CString Function(CString input)>()
external CString askSwift(CString input);
What if it was just as simple to export?
// hello.dart
@ffi.Export()
String askDart(String input) {
return 'Dart says: Hi, $input';
}
// HelloWorldView.swift
import Hello
// ...
Text("\(Hello.askDart(input: name))")
// dart-sdk/pkg/vm/lib/transformations/ffi/export.dart
void transformLibraries(...) {
// ...
mainLibrary.addProcedure(Procedure(
Name('#ffiExports'),
ProcedureKind.Method,
FunctionNode(
ReturnStatement(StaticInvocation(
allocateExports,
Arguments([
ListLiteral(elements,
typeArgument: transformer.pointerVoidType),
ConstantExpression(
(callocField.initializer as ConstantExpression).constant),
]),
)),
returnType: coreTypes.intNonNullableRawType),
isStatic: true,
fileUri: mainLibrary.fileUri,
));
}
// Injected in Dart
CString _exported#askDart(CString input) =>
askDart(input.toDartString()).toNativeUtf8();
@pragma('vm:ffi:exports-list', ...)
@pragma('vm:entry-point')
int #ffiExports() => ffi._allocateExports([
Pointer<...>.fromFunction(_exported#askDart)
]);
// In C
struct hello_Exports {
char* (*askDart)(const char*);
char* (*makeView)();
};
char* hello_askDart(const char* input) {
return dart::embedder::
Exports<hello_Exports>()->askDart(input);
}
// In Swift
DynamicView(factory: Hello.makeView)
// In Dart
import 'swiftui.dart' as swiftui;
@ffi.Export()
swiftui.View makeView() => swiftui.VStack([
swiftui.Text('Hello, World!'),
]).toSwift();