mraleph
Joined 16 years ago
Joined 16 years ago
JIT mode is only for development, and its performance has not been a priority for a while now.
Dart 2.12 (released March 2021) introduced null-safety.
That started a 2 year transitionary period during which you could mix nullsafe (language versions 2.12 or above) and non-nullsafe (language versions below 2.12) code in one program.
Dart 3.0 (released May 2023) removed support for language versions prior to 2.12 - meaning that you can no longer opt out of null-safety.
This way you get a stack trace which contains all Lua and native frames. You can use it when profiling and you can use it to print hybrid stack traces when your binary crashes.
I was considering open-sourcing it, but it requires a bunch of patches in LJ internals so I gave up on that idea.
(There is also some amount of over-engineering involved, e.g. to compute unwinding information for interpreter code I run an abstract interpretation on its implementation and annotate interpreter code range with information on whether it is safe or unsafe to try unwinding at a specific pc inside the interpreter. I could have just done this by hand - but did not want to maintain it between LJ versions)
I thought Compose Desktop is running on JVM an renders through Swing/AWT? Did this change?
Who said you can't? :) This actually works in Dart:
Dog? dog;
bool isDog = dog is Dog;
if (isDog) {
dog.bark();
}
i.e. boolean variables which serve as witnesses for type judgements are integrated into promotion machinery.I do agree with you that
1. In general there is no limit to developers' expectations with respect to promotion, but there is a limit to what compiler can reasonably achieve. At some point rules become too complex for developers to understand and rely upon. 2. Creating local variables when you need to refine the type is conceptually simpler and more explicit, both for language designers, language developers and language users.
I don't hate excessive typing, especially where it helps to keep things simple and readable - so I would not be against a language that does not do any control flow based variable type promotion. Alas many developers don't share this view of the world and are vehemently opposed to typing anything they believe compiler can already infer.
It's all a matter of personal taste in the end.
Dog? maybeDog;
if (maybeDog != null) {
maybeDog.bark();
}
If compiler says "sorry, maybeDog might be null", developer (rightfully so) usually responds "but I have just checked and I know it is not, why do you bother me?". So languages chose to accommodate this.> What if I want to set the value back to nil if it is not-nil?
You can. The type of the variable does not actually change. You could say that the information about more precise type is simply propagated to the uses which are guarded by the control flow. The following will compile just fine:
Dog? maybeDog;
if (maybeDog != null) {
maybeDog.bark();
maybeDog = null;
}
> Why should I have to wrap things back in an optional if I want to pass it along as such?You don't, with a few exceptions. There is a subtype relationship between T and T?: T <: T?, so the following is just fine:
void foo(Dog? maybeDog);
Dog? maybeDog;
if (maybeDog != null) {
maybeDog.bark();
foo(maybeDog); // Dog can be used where Dog? is expected
}
You might need to account for it in places where type inference infers type which is too precise, e.g. Dog? maybeDog;
if (maybeDog != null) {
// This will be List<Dog> rather than List<Dog?>.
final listOfDogs = [maybeDog];
}
Though I don't think it is that bad of a problem in practice.The syntax is unfortunately more verbose, but if all goes well we will address that issue this year.
if (value case final value?) {
// value is a fresh final variable
} sealed class MySomething<T> {
}
final class One extends MySomething<Never> {
}
final class Two extends MySomething<Never> {
final Child child;
Two(this.child);
}
final class Three<T> extends MySomething<T> {
final T value;
Three(this.value);
}
final class Four extends MySomething<Never> {
final int Function() callback;
Four(this.callback);
}
And then you can exhaustively switch over values of MySomething: int foo(MySomething<String> foo) {
return switch (foo) {
One() => 1,
Two(child: Child(:final value)) => value,
Three(:final value) => int.parse(value),
Four(:final callback) => callback(),
};
}
The declaration of a sealed family is considerably more verbose than Swift - and I really would like[1] us to optimized things for the case when people often declare such families. Macros and a primary constructors will likely provide reprieve from this verbosity.But importantly it composes well with Dart's OOPy nature to achieve things which are not possible in Swift: you can mix and match pattern matching and classical virtual dispatch as you see fit. This, for example, means you can attach different virtual behavior to every member of the sealed class family.
That's not how Flutter works on either of the platforms it supports.
When you run natively (e.g. mobile, desktop, etc) Skia or Impeller execute natively and all your Dart code is compiled ahead-of-time to native code as well. No JS or Wasm is involved anywhere in the stack. No JIT compilation in release binaries, only during development.
When you run on the Web - then naturally Skia is compiled to Wasm and Dart code is compiled to JS or Wasm, JS VM will end up running both of those. No Dart VM is involved anywhere here.
With primary constructors this will become something along the lines of:
sealed class Message();
class IncrementBy(final int value) extends Message;
class DecrementBy(final int value) extends Message;
class Set(final int value) extends Message;
Which is considerably less repetitive, though `extends Message` is still there. I am fairly optimistic that the next step would be to eliminate that[1], though I think we would need to gather a bit more feedback from users as people are getting more and more reps in with Dart 3.0 features. I personally would prefer something along the lines of: sealed class Message() {
case IncrementBy(final int value);
case DecrementBy(final int value);
case Set(final int value);
}
Current syntax is not all that bad if you are going to do OO and add various helper methods on `Message` and its subclasses, but if you just want to define your data and no behavior / helpers - then it is exceedingly verbose.Was never built on top of V8 in any shape of form. Dart VM does not even share any code with V8.
Dart 1 was designed by people who originally started V8 (Lars Bak and Kasper Lund), that's the only connection.
Current measurement presents comparison of two wildly different implementation strategies: LuaJIT Remake uses IC and hidden classes while LuaJIT proper does not. It's a well known fact that ICs+hidden classes can lead to major performance improvements. That insight originated in Smalltalk world and was brought to dynamic language mainstream by V8† - but unfortunately it muddles the comparison here.
† V8 was open sourced on September 2, 2008... Coincidentally (though probably not :)) JSC landed their first IC prototype on September 1, 2008.
I should update that side note because a lot of things has changed. It should probably say something like:
> Theoretically, Dart VM JIT should have best peak performance, while Dart VM AOT has best startup time. In reality the comparison is quite complicated. AOT has seen a lot of work in the last few years, which made it faster than JIT in some aspects e.g. both direct and highly polymorphic method calls are usually faster in AOT. JIT's peak performance has been languishing in a state of neglect, but it still runs circles around AOT in some cases due to aggressive inlining.
Something like this.