blob: c000ca5828e9e69a31abeee9f7d5a25cc297787f [file] [edit]
// Copyright (c) 2026, 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:typed_data';
import 'package:cfg/front_end/ast_to_ir.dart';
import 'package:cfg/front_end/computed_scopes.dart';
import 'package:cfg/front_end/recognized_methods.dart';
import 'package:cfg/ir/functions.dart';
import 'package:cfg/ir/global_context.dart';
import 'package:cfg/ir/ssa_computation.dart';
import 'package:cfg/ir/types.dart';
import 'package:cfg/passes/pass.dart';
import 'package:front_end/src/api_unstable/vm.dart'
show CompilerOptions, kernelForProgram;
import 'package:kernel/ast.dart' as ast;
import 'package:kernel/library_index.dart';
import 'package:kernel/type_environment.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
import '../target.dart';
import 'cfg/to_wasm.dart';
import 'runtime.dart';
const w.ValueType _jsString = w.RefType.extern(nullable: false);
/// Compiles a Dart file into a Wasm dev module.
///
/// It assumes that either
/// * [platformPath] is provided and is the path to the
/// `dart2wasm_dev_platform.dill` file or
/// * [librariesSpecPath] is provided and points to a valid `libraries.json`
/// file allowing the compiler to compile the `dart:*` libraries from source
Future<Uint8List> compile(
Uri mainUri, {
Uri? platformPath,
Uri? librariesSpecPath,
}) async {
final compilerOptions = CompilerOptions()
..target = WasmDevTarget()
..sdkRoot = Uri.file('.')
..librariesSpecificationUri = librariesSpecPath
..sdkSummary = platformPath
..compileSdk = platformPath == null
..environmentDefines = const {};
final compilerResult = (await kernelForProgram(mainUri, compilerOptions))!;
final component = compilerResult.component!;
final mainMethod = component.mainMethod!;
return GlobalContext.withContext(
GlobalContext(
typeEnvironment: TypeEnvironment(
compilerResult.coreTypes!,
compilerResult.classHierarchy!,
),
coreLibraries: LibraryIndex.all(component),
),
() {
final module = w.ModuleBuilder('dev', null);
final runtime = DevRuntime(module);
final compiler = ModuleCompiler(module, runtime);
compiler.compileLibrary(
mainMethod.enclosingLibrary,
mainMethod: mainMethod,
);
final serializer = w.Serializer();
module.build().serialize(serializer);
return serializer.data;
},
);
}
/// Emits a Wasm dev module's code into [module].
class ModuleCompiler {
final w.ModuleBuilder module;
final DevRuntime runtime;
final FunctionRegistry functionRegistry = FunctionRegistry();
final RecognizedMethods recognizedMethods = CommonRecognizedMethods(
requireMethods: false,
);
/// Maps a static's label to the global holding its ID.
final Map<String, w.Global> _staticIds = {};
ModuleCompiler(this.module, this.runtime);
w.InstructionsBuilder get _start => module.startFunction.body;
void compileLibrary(ast.Library library, {ast.Procedure? mainMethod}) {
for (final procedure in library.procedures) {
_compileProcedure(procedure, isMain: procedure == mainMethod);
}
for (final cls in library.classes) {
for (final procedure in cls.procedures) {
_compileProcedure(procedure, isMain: false);
}
}
}
void _compileProcedure(ast.Procedure procedure, {required bool isMain}) {
final cFunction = functionRegistry.getFunction(procedure);
final graph = AstToIr(
cFunction,
functionRegistry,
recognizedMethods,
onLocalFunction: (_) {},
enableAsserts: false,
typeParametersStyle: .separateFunctionAndClassTypeParameters,
scopes: ComputedScopes(procedure, enableAsserts: false),
).buildFlowGraph();
Pipeline([SSAComputation()]).run(graph);
final label = staticLabel(procedure);
final type = functionTypeFor(cFunction);
defineStatic(
label,
type,
(b) => DevCfgToWasm(this, graph, b).generate(),
exportName: isMain ? 'main' : null,
);
}
w.FunctionType functionTypeFor(CFunction cFunction) {
final functionNode = cFunction.functionNode!;
final inputs = <w.ValueType>[
for (final param in functionNode.positionalParameters)
DevCfgToWasm.translateCType(CType.fromStaticType(param.type)),
];
final returnType = cFunction.returnType;
final outputs =
(returnType.dartType is ast.VoidType || returnType is NeverType)
? const <w.ValueType>[]
: <w.ValueType>[DevCfgToWasm.translateCType(returnType)];
return module.types.defineFunction(inputs, outputs);
}
/// Defines static [label] with [type] and body [emitBody].
void defineStatic(
String label,
w.FunctionType type,
void Function(w.InstructionsBuilder b) emitBody, {
String? exportName,
}) {
final functionBuilder = module.functions.define(type, label);
emitBody(functionBuilder.body);
final function = functionBuilder.build();
if (exportName != null) {
module.exports.export(exportName, function);
}
// Declare [function] so `ref.func` can reference it.
module.elements.declarativeSegmentBuilder.declare(function);
_start.global_get(_staticId(label));
_start.ref_func(function);
_start.call(runtime.declareStatic);
}
/// Emits a call to static [label] with [type].
void callStatic(w.InstructionsBuilder b, String label, w.FunctionType type) {
b.global_get(_staticId(label));
b.call(runtime.lookupStatic);
b.ref_cast(w.RefType.def(type, nullable: false));
b.call_ref(type);
}
/// Returns the global holding static [label]'s ID.
w.Global _staticId(String label) => _staticIds.putIfAbsent(label, () {
// Emit a global variable to hold [label]'s ID.
final globalBuilder = module.globals.define(
w.GlobalType(w.NumType.i32, mutable: true),
label,
);
// We use 0 as a placeholder but override it below.
globalBuilder.initializer
..i32_const(0)
..end();
pushString(_start, label);
_start.call(runtime.staticId);
_start.global_set(globalBuilder.global);
return globalBuilder.build();
});
}
/// Returns the static label for [member].
String staticLabel(ast.Member member) {
final libraryUri = member.enclosingLibrary.importUri;
final className = member.enclosingClass?.name;
final memberName = member.name.text;
if (className != null) {
return '$libraryUri::$className.$memberName';
}
return '$libraryUri::$memberName';
}
/// Pushes [value] as a JS string.
// TODO: Canonicalize strings.
w.ValueType pushString(w.InstructionsBuilder b, String value) {
b.global_get(
b.moduleBuilder.globals.import(
'',
value,
w.GlobalType(_jsString, mutable: false),
),
);
return _jsString;
}