dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.08k stars 1.56k forks source link

Enable tree shaking of platform-specific code in AOT mode. #31969

Open mraleph opened 6 years ago

mraleph commented 6 years ago

Based on the converstation with Flutter users it seems that Flutter code it is common to guard large chunks of code behind runtime checks that boil down to:

if (Platform.isAndroid) {
  // ...
}

(actual flutter code might be using a layer on top: see https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/foundation/platform.dart)

Here's a list of subtasks:

/cc @lukef @mit-mit

mraleph commented 5 years ago

/cc @sstrickl - also see other issues linked from the code.

mkustermann commented 2 years ago

Cross-linking some other issues

(related https://github.com/flutter/flutter/issues/99412)

See also go/flutter-platform-behavior

mkustermann commented 2 years ago

Assigning @sstrickl . Please work with @lrhn and @mit-mit on finding the right solution here.

mkustermann commented 1 year ago

The outcome of our offline discussion on Sept 29 was that we'll define a set of compiler-recognizable patterns for code that is dependent on platform/os/arch/... and then make our AOT compiler recognize those patterns to perform tree shaking on the kernel level.

@lrhn Have you thought about the set of patterns we should make our compiler support?

lrhn commented 1 year ago

My idea is fairly simple: https://gist.github.com/lrhn/2a3b06a8253fddaf61297cb604c6777f

Basically, recognize isLinux and operatingSystem == "linux", and combinations of those like isLinux || isWindows.

mkustermann commented 1 year ago

My idea is fairly simple: https://gist.github.com/lrhn/2a3b06a8253fddaf61297cb604c6777f

Basically, recognize isLinux and operatingSystem == "linux", and combinations of those like isLinux || isWindows.,

Thanks for writing it up, @lrhn !

What would be the code patterns we support on the usage site of those? (e.g. if (isLinux), caching isLinux in a local variable, using isLinux in other getters, more complicated control flow, ...)

Flutter is having it's own platform detection mechanism atm (it is exposing a defaultTargetPlatform getter that is based on _platform_io.dart which is currently even overridable at runtime by setting OS environment variable FLUTTER_TEST if assertions are enabled).

platform.TargetPlatform get defaultTargetPlatform {
  platform.TargetPlatform? result;
  if (Platform.isAndroid) {
    result = platform.TargetPlatform.android;
  } else if (Platform.isIOS) {
    ...
  }
  assert(() {
    if (Platform.environment.containsKey('FLUTTER_TEST')) {
      result = platform.TargetPlatform.android;
    }
    return true;
  }());
  if (platform.debugDefaultTargetPlatformOverride != null) {
    result = platform.debugDefaultTargetPlatformOverride;
  }
  if (result == null) throw ...;
  return result!;
}

Would we want to make our compiler recognize the above code to return a constant? Would we want to rewrite this implementation in flutter?

This flutter defaultTargetPlatform is used in practice to guard code, for example the flutter firebase plugin uses it here

lrhn commented 1 year ago

I'd prefer to not make things too complicated, so only direct occurrences of isLinux inside the condition expression itself. Remembering inferences from a boolean stored in a variable is possible (we've proven that for promotion), but it's also much more complicated and a little harder to read. The isX variables are short and directly useful.

I definitely don't want if (myPlatformTest()) { ... } to work, where myPlatformTest() => isLinux || isAndroid;. (If we ever make promoting methods work for type promotions, I'm willing to look at it again.)

As for more complicated control flow ... only if reasonable. (That would be recognizing escaping control flow.) It's safe to not start with it. The warnings apply whenever you use a platform specific method, unless we can deduce that you can only be on a supported platform (can't rule out that you are on an unsupported platform). Making our analysis better in the future will not introduce more errors, only fewer.

I'd probably make the compiler just check for the operatingSystem == "linux" (and "linux" == operatingSystem), and force-inline any getter starting with is from the package:platform/platform.dart library. Then we can add more isGroupOfPlatforms => isFoo || isBar || isBaz; getters if necessary without needing to add extra support.

We can choose to accept assert(isLinux); too, for warnings, even if we can't use it for optimizations when asserts are not enabled.

Flutter could rewrite their target platform code to use our getters. It won't make the result constant, but it might be known at compile-time anyway, if the compiler knows the value of, say, isAndroid. That's a valid optimization. Since they support overriding the actual runtime platform, it's different from what we do. The Dart compilers can safely optimize away non-platform code based on the values of the isX getters when it knows the value at runtime. The Flutter code above uses a non-const check for Platform.environment, so we can't do compile-time optimization based on the result of defaultTargetPlatform.

The goal here is two things:

If used correctly, all code that doesn't run on the current platform should be tree-shakable, because any use of it is definitely inside other code which can be tree-shakable.

mkustermann commented 1 year ago

Flutter could rewrite their target platform code to use our getters. It won't make the result constant, ...

We have to be able to tree shake code that is guarded by platform checks. If flutter provides a blessed API that they recommend to users, users will use it, so we have to be able to tree shake based on that API.

(Seemingly this was originally added in https://github.com/flutter/flutter/pull/33663)

mkustermann commented 1 year ago

Filed some sub-issues (see https://github.com/dart-lang/sdk/issues/31969#issue-290926253)