diff --git a/pkg/dart2bytecode/bin/kernel_service.dart b/pkg/dart2bytecode/bin/kernel_service.dart index f5bf828ee53..e80bfddf54c 100644 --- a/pkg/dart2bytecode/bin/kernel_service.dart +++ b/pkg/dart2bytecode/bin/kernel_service.dart @@ -15,16 +15,17 @@ import 'package:kernel/target/targets.dart' show Target; import '../../vm/bin/kernel_service.dart' as kernel_service; Uint8List _generateBytecode( - Component component, - List libraries, - CoreTypes coreTypes, - ClassHierarchy hierarchy, - Target target, - bool enableAsserts, -) { + Component component, + List libraries, + CoreTypes coreTypes, + ClassHierarchy hierarchy, + Target target, + bool enableAsserts, + {Set extraLoadedLibraries = const {}}) { final byteSink = new BytesSink(); generateBytecode(component, byteSink, libraries: libraries, + extraLoadedLibraries: extraLoadedLibraries, coreTypes: coreTypes, hierarchy: hierarchy, target: target, diff --git a/pkg/dart2bytecode/lib/bytecode_generator.dart b/pkg/dart2bytecode/lib/bytecode_generator.dart index 1c234354922..ffbe33c67a4 100644 --- a/pkg/dart2bytecode/lib/bytecode_generator.dart +++ b/pkg/dart2bytecode/lib/bytecode_generator.dart @@ -65,14 +65,16 @@ void generateBytecode( required CoreTypes coreTypes, required ClassHierarchy hierarchy, required Target target, + required Set extraLoadedLibraries, }) { Timeline.timeSync("generateBytecode", () { verifyBytecodeInstructionDeclarations(); final typeEnvironment = TypeEnvironment(coreTypes, hierarchy); final pragmaParser = ConstantPragmaAnnotationParser(coreTypes, target); - final bytecodeGenerator = BytecodeGenerator(component, coreTypes, hierarchy, - typeEnvironment, options, pragmaParser); + final bytecodeGenerator = BytecodeGenerator( + component, coreTypes, hierarchy, typeEnvironment, options, pragmaParser, + libraries: libraries, extraLoadedLibraries: extraLoadedLibraries); for (Library library in libraries) { bytecodeGenerator.visitLibrary(library); } @@ -105,6 +107,8 @@ class BytecodeGenerator extends RecursiveVisitor { final PragmaAnnotationParser pragmaParser; final RecognizedMethods recognizedMethods; final Map astUriToSource; + final List libraries; + final Set extraLoadedLibraries; final LibraryIndex ffiLibraryIndex; late StringTable stringTable; late ObjectTable objectTable; @@ -149,7 +153,9 @@ class BytecodeGenerator extends RecursiveVisitor { ClassHierarchy hierarchy, TypeEnvironment typeEnvironment, BytecodeOptions options, - PragmaAnnotationParser pragmaParser) + PragmaAnnotationParser pragmaParser, + {required List libraries, + Set extraLoadedLibraries = const {}}) : this._internal( component, coreTypes, @@ -157,6 +163,8 @@ class BytecodeGenerator extends RecursiveVisitor { typeEnvironment, options, pragmaParser, + libraries: libraries, + extraLoadedLibraries: extraLoadedLibraries, StatefulStaticTypeContext.flat(typeEnvironment)); BytecodeGenerator._internal( @@ -166,10 +174,15 @@ class BytecodeGenerator extends RecursiveVisitor { this.typeEnvironment, this.options, this.pragmaParser, - this.staticTypeContext) + this.staticTypeContext, + {required this.libraries, + required this.extraLoadedLibraries}) : recognizedMethods = new RecognizedMethods(staticTypeContext), astUriToSource = component.uriToSource, - ffiLibraryIndex = LibraryIndex(component, const ['dart:ffi']) { + ffiLibraryIndex = coreTypes.index.containsLibrary('dart:ffi') + ? coreTypes.index + : LibraryIndex.fromLibraries( + {...libraries, ...extraLoadedLibraries}, const ['dart:ffi']) { bytecodeComponent = new Component(coreTypes); stringTable = bytecodeComponent.stringTable; objectTable = bytecodeComponent.objectTable; @@ -198,9 +211,9 @@ class BytecodeGenerator extends RecursiveVisitor { @override void visitClass(Class node) { - isInDeeplyImmutableClass = - pragmaParser.parsedPragmas( - node.annotations).isNotEmpty; + isInDeeplyImmutableClass = pragmaParser + .parsedPragmas(node.annotations) + .isNotEmpty; startMembers(); visitList(node.constructors, this); visitList(node.procedures, this); @@ -1056,9 +1069,8 @@ class BytecodeGenerator extends RecursiveVisitor { ? ffiLibraryIndex.getTopLevelProcedure('dart:ffi', '_ffiCall') : null; - late Procedure ensureDeeplyImmutable = - libraryIndex.getTopLevelProcedure('dart:_internal', - '_ensureDeeplyImmutable'); + late Procedure ensureDeeplyImmutable = libraryIndex.getTopLevelProcedure( + 'dart:_internal', '_ensureDeeplyImmutable'); // Selector for implicit dynamic calls 'foo(...)' where // variable 'foo' has type 'dynamic'. @@ -1152,8 +1164,7 @@ class BytecodeGenerator extends RecursiveVisitor { final int cpIndex = cp.addInstanceField(field); if (isInDeeplyImmutableClass) { // TODO(dartbug.com/61078): Use static type to avoid runtime check. - _genDirectCall( - ensureDeeplyImmutable, objectTable.getArgDescHandle(1), 1); + _genDirectCall(ensureDeeplyImmutable, objectTable.getArgDescHandle(1), 1); } asm.emitStoreFieldTOS(cpIndex); @@ -2515,13 +2526,15 @@ class BytecodeGenerator extends RecursiveVisitor { currentLoopDepth = savedLoopDepth; asyncTryBlock = savedAsyncTryBlock; - bool capturesOnlyFinalNotLateVars = - locals.capturesOnlyFinalNotLateVars; + bool capturesOnlyFinalNotLateVars = locals.capturesOnlyFinalNotLateVars; locals.leaveScope(); - closure.code = new ClosureCode(asm.bytecode, asm.exceptionsTable, - finalizeSourcePositions(), finalizeLocalVariables(), + closure.code = new ClosureCode( + asm.bytecode, + asm.exceptionsTable, + finalizeSourcePositions(), + finalizeLocalVariables(), capturesOnlyFinalNotLateVars); _popAssemblerState(); diff --git a/pkg/dart2bytecode/lib/dart2bytecode.dart b/pkg/dart2bytecode/lib/dart2bytecode.dart index d3786762d96..27a1e91d5f5 100644 --- a/pkg/dart2bytecode/lib/dart2bytecode.dart +++ b/pkg/dart2bytecode/lib/dart2bytecode.dart @@ -266,7 +266,8 @@ Future runCompilerWithOptions({ hierarchy: results.classHierarchy!, coreTypes: results.coreTypes!, options: bytecodeOptions, - target: compilerOptions.target!); + target: compilerOptions.target!, + extraLoadedLibraries: results.loadedLibraries); await sink.close(); if (bytecodeOptions.showBytecodeSizeStatistics) { BytecodeSizeStatistics.dump(); diff --git a/pkg/dart2bytecode/test/bytecode_generator_test.dart b/pkg/dart2bytecode/test/bytecode_generator_test.dart index bf33540a62c..c81ffaaf723 100644 --- a/pkg/dart2bytecode/test/bytecode_generator_test.dart +++ b/pkg/dart2bytecode/test/bytecode_generator_test.dart @@ -23,6 +23,7 @@ import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/kernel.dart'; import 'package:kernel/target/targets.dart'; import 'package:test/test.dart'; +import 'package:vm/kernel_front_end.dart' show createLoadedLibrariesSet; import 'package:vm/modular/target/vm.dart'; /// Environment define to update expectation files on failures. @@ -33,17 +34,18 @@ final String dartSdkPkgDir = Platform.script.resolve('../..').toFilePath(); runTestCase(Uri source, {bool isClosureContextLoweringEnabled = false}) async { final target = VmTarget(TargetFlags( isClosureContextLoweringEnabled: isClosureContextLoweringEnabled)); - Component component = - await compileTestCaseToKernelProgram(source, target: target); + final result = await compileTestCaseToKernelProgram(source, target: target); - final mainLibrary = component.mainMethod!.enclosingLibrary; - final coreTypes = CoreTypes(component); - final hierarchy = ClassHierarchy(component, coreTypes); + final mainLibrary = result.component.mainMethod!.enclosingLibrary; final sink = ByteSink(); - generateBytecode(component, sink, + final coreTypes = result.coreTypes ?? CoreTypes(result.component); + final hierarchy = + result.classHierarchy ?? ClassHierarchy(result.component, coreTypes); + generateBytecode(result.component, sink, options: BytecodeOptions(), libraries: [mainLibrary], + extraLoadedLibraries: result.loadedLibraries, coreTypes: coreTypes, hierarchy: hierarchy, target: target); @@ -58,25 +60,48 @@ runTestCase(Uri source, {bool isClosureContextLoweringEnabled = false}) async { compareResultWithExpectationsFile(source, actual); } -Future compileTestCaseToKernelProgram(Uri sourceUri, +// Similar to CompilerResult from pkg/vm/bin/kernel_service.dart. +class CompilerResult { + final Component component; + final Set loadedLibraries; + final ClassHierarchy? classHierarchy; + final CoreTypes? coreTypes; + + CompilerResult( + this.component, + this.loadedLibraries, + this.classHierarchy, + this.coreTypes, + ); +} + +Future compileTestCaseToKernelProgram(Uri sourceUri, {required Target target}) async { final platformKernel = computePlatformBinariesLocation().resolve('vm_platform.dill'); final options = CompilerOptions() ..target = target + ..omitPlatform = true ..additionalDills = [platformKernel] ..environmentDefines = {} ..onDiagnostic = (CfeDiagnosticMessage message) { fail("Compilation error: ${message.plainTextFormatted.join('\n')}"); }; - final Component component = - (await kernelForProgram(sourceUri, options))!.component!; + final result = (await kernelForProgram(sourceUri, options))!; + final Component component = result.component!; // Make sure the library name is the same and does not depend on the order // of test cases. component.mainMethod!.enclosingLibrary.name = '#lib'; - return component; + return CompilerResult( + component, + // Use the same calculation as SingleShotCompilerWrapper. + createLoadedLibrariesSet(result.loadedComponents, result.sdkComponent, + includePlatform: false), + result.classHierarchy, + result.coreTypes, + ); } class ByteSink implements Sink> { @@ -175,6 +200,7 @@ main() { 'field_initializers.dart', 'optional_params.dart', 'bootstrapping.dart', + 'ffi.dart', }; group('gen-bytecode-with-closure-context-lowering', () { diff --git a/pkg/dart2bytecode/testcases/ffi.dart b/pkg/dart2bytecode/testcases/ffi.dart new file mode 100644 index 00000000000..64065297888 --- /dev/null +++ b/pkg/dart2bytecode/testcases/ffi.dart @@ -0,0 +1,26 @@ +// 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:ffi'; + +testVoidNoArg() { + final pointer = Pointer>.fromAddress( + 0xdeadbeef, + ); + final function = pointer.asFunction(); + function(); +} + +testIntInt() { + final pointer = Pointer>.fromAddress( + 0xdeadbeef, + ); + final function = pointer.asFunction(); + return function(42); +} + +void main() { + testVoidNoArg(); + testIntInt(); +} diff --git a/pkg/dart2bytecode/testcases/ffi.dart.expect b/pkg/dart2bytecode/testcases/ffi.dart.expect new file mode 100644 index 00000000000..d4b75f76254 --- /dev/null +++ b/pkg/dart2bytecode/testcases/ffi.dart.expect @@ -0,0 +1,167 @@ +Bytecode +Dynamic Module Entry Point: DART_SDK/pkg/dart2bytecode/testcases/ffi.dart::main +Library 'DART_SDK/pkg/dart2bytecode/testcases/ffi.dart' + name '#lib' + script 'DART_SDK/pkg/dart2bytecode/testcases/ffi.dart' + uses dart:ffi + +Class '', script = 'DART_SDK/pkg/dart2bytecode/testcases/ffi.dart' + + +Function 'testVoidNoArg', static, reflectable, debuggable + parameters [] (required: 0) + return-type dynamic + +Bytecode { + Entry 5 + CheckStack 0 + PushConstant CP#0 + PushConstant CP#1 + DirectCall CP#2, 2 + PopLocal r2 + AllocateContext 0, 1 + PopLocal r0 + Push r0 + Push r2 + StoreContextVar 0, 0 + PushConstant CP#4 + Push r0 + PushNull + AllocateClosure + PopLocal r4 + Push r4 + Push r0 + LoadContextParent + PopLocal r0 + PopLocal r3 + Push r3 + StoreLocal r4 + Push r4 + UncheckedClosureCall CP#9, 1 + Drop1 + PushNull + ReturnTOS +} +ConstantPool { + [0] = ObjectRef < dart:ffi::NativeFunction < FunctionType () -> dart:ffi::Void > > + [1] = ObjectRef const 3735928559 + [2] = DirectCall 'dart:ffi::Pointer::fromAddress (constructor)', ArgDesc num-args 2, num-type-args 0, names [] + [3] = Reserved + [4] = ClosureFunction 0 + [5] = InstanceField dart:core::_Closure::_context (field) + [6] = Reserved + [7] = FfiCall + [8] = EndClosureFunctionScope + [9] = ObjectRef ArgDesc num-args 1, num-type-args 0, names [] +} +Closure DART_SDK/pkg/dart2bytecode/testcases/ffi.dart::testVoidNoArg::'#ffiClosure0' annotations const List [const dart:core::pragma {dart:core::pragma::name (field): 'vm:ffi:call-closure', dart:core::pragma::options (field): const dart:ffi::_FfiCall < FunctionType () -> dart:ffi::Void > {dart:ffi::_FfiCall::isLeaf (field): const false}}] + () -> void +ClosureCode { + Entry 2 + Push FP[-5] + LoadFieldTOS CP#5 + PopLocal r0 + CheckStack 0 + Push r0 + LoadContextVar 0, 0 + FfiCall CP#7 + ReturnTOS +} + + +Function 'testIntInt', static, reflectable, debuggable + parameters [] (required: 0) + return-type dynamic + +Bytecode { + Entry 5 + CheckStack 0 + PushConstant CP#0 + PushConstant CP#1 + DirectCall CP#2, 2 + PopLocal r2 + AllocateContext 0, 1 + PopLocal r0 + Push r0 + Push r2 + StoreContextVar 0, 0 + PushConstant CP#4 + Push r0 + PushNull + AllocateClosure + PopLocal r4 + Push r4 + Push r0 + LoadContextParent + PopLocal r0 + PopLocal r3 + Push r3 + StoreLocal r4 + PushInt 42 + Push r4 + UncheckedClosureCall CP#12, 2 + ReturnTOS +} +ConstantPool { + [0] = ObjectRef < dart:ffi::NativeFunction < FunctionType (dart:ffi::Int64) -> dart:ffi::Int32 > > + [1] = ObjectRef const 3735928559 + [2] = DirectCall 'dart:ffi::Pointer::fromAddress (constructor)', ArgDesc num-args 2, num-type-args 0, names [] + [3] = Reserved + [4] = ClosureFunction 0 + [5] = InstanceField dart:core::_Closure::_context (field) + [6] = Reserved + [7] = Type dart:core::int + [8] = ObjectRef 'arg1' + [9] = SubtypeTestCache + [10] = FfiCall + [11] = EndClosureFunctionScope + [12] = ObjectRef ArgDesc num-args 2, num-type-args 0, names [] +} +Closure DART_SDK/pkg/dart2bytecode/testcases/ffi.dart::testIntInt::'#ffiClosure1' annotations const List [const dart:core::pragma {dart:core::pragma::name (field): 'vm:ffi:call-closure', dart:core::pragma::options (field): const dart:ffi::_FfiCall < FunctionType (dart:ffi::Int64) -> dart:ffi::Int32 > {dart:ffi::_FfiCall::isLeaf (field): const false}}] + (dart:core::int arg1) -> dart:core::int +ClosureCode { + Entry 2 + Push FP[-6] + LoadFieldTOS CP#5 + PopLocal r0 + CheckStack 0 + JumpIfUnchecked L1 + Push FP[-5] + PushConstant CP#7 + PushNull + PushNull + PushConstant CP#8 + AssertAssignable 1, CP#9 + Drop1 +L1: + PushNull + Drop1 + Push FP[-5] + Push r0 + LoadContextVar 0, 0 + FfiCall CP#10 + ReturnTOS +} + + +Function 'main', static, reflectable, debuggable + parameters [] (required: 0) + return-type void + +Bytecode { + Entry 0 + CheckStack 0 + DirectCall CP#0, 0 + Drop1 + DirectCall CP#2, 0 + Drop1 + PushNull + ReturnTOS +} +ConstantPool { + [0] = DirectCall 'DART_SDK/pkg/dart2bytecode/testcases/ffi.dart::testVoidNoArg', ArgDesc num-args 0, num-type-args 0, names [] + [1] = Reserved + [2] = DirectCall 'DART_SDK/pkg/dart2bytecode/testcases/ffi.dart::testIntInt', ArgDesc num-args 0, num-type-args 0, names [] + [3] = Reserved +} + diff --git a/pkg/vm/bin/kernel_service.dart b/pkg/vm/bin/kernel_service.dart index e4e86e3d29f..71c30582c46 100644 --- a/pkg/vm/bin/kernel_service.dart +++ b/pkg/vm/bin/kernel_service.dart @@ -85,8 +85,9 @@ Uint8List Function( CoreTypes coreTypes, ClassHierarchy hierarchy, Target target, - bool enableAsserts, -)? + bool enableAsserts, { + Set extraLoadedLibraries, +})? bytecodeGenerator; CompilerOptions setupCompilerOptions( @@ -375,10 +376,9 @@ class IncrementalCompilerWrapper extends Compiler { errorsPlain.clear(); errorsColorized.clear(); final compilerResult = await generator.compile(entryPoints: [script]); - final component = compilerResult.component; return new CompilerResult( - component, - const {}, + compilerResult.component, + compilerResult.neededDillLibraries ?? const {}, compilerResult.classHierarchy, compilerResult.coreTypes, ); @@ -1082,6 +1082,7 @@ Future _processLoadRequest(request) async { compilerResult.classHierarchy!, compiler.options.target!, compiler.enableAsserts, + extraLoadedLibraries: loadedLibraries, ); } else { bytes = serializeComponent( diff --git a/pkg/vm/lib/incremental_compiler.dart b/pkg/vm/lib/incremental_compiler.dart index 92c557a8627..189c94e694c 100644 --- a/pkg/vm/lib/incremental_compiler.dart +++ b/pkg/vm/lib/incremental_compiler.dart @@ -103,6 +103,7 @@ class IncrementalCompiler { Map uriToSource = new Map(); ClassHierarchy classHierarchy = _pendingDeltas.last.classHierarchy; CoreTypes coreTypes = _pendingDeltas.last.coreTypes; + Set neededDillLibraries = {}; for (IncrementalCompilerResult deltaResult in _pendingDeltas) { Component delta = deltaResult.component; if (delta.mainMethod != null) { @@ -112,7 +113,10 @@ class IncrementalCompiler { for (Library library in delta.libraries) { bool isPlatform = library.importUri.isScheme("dart") && !library.isSynthetic; - if (!includePlatform && isPlatform) continue; + if (!includePlatform && isPlatform) { + neededDillLibraries.add(library); + continue; + } combined[library.importUri] = library; } } @@ -125,6 +129,7 @@ class IncrementalCompiler { )..setMainMethodAndMode(mainMethod?.reference, true), classHierarchy: classHierarchy, coreTypes: coreTypes, + neededDillLibraries: neededDillLibraries, ); if (_pendingDeltas.length == 1) { // With only one delta to "merge" we can copy over the metadata.