From b6c060a6ab54e7e9fa950d54346c509efe62861e Mon Sep 17 00:00:00 2001 From: Tess Strickland Date: Fri, 6 Feb 2026 06:13:22 -0800 Subject: [PATCH] [vm,dyn_modules] Pass additional libraries to the BytecodeGenerator. In addition to passing in the libraries that should be visited to the bytecode generator, also pass the libraries which are needed by the component and either are not in the component or should not be visited (e.g., libraries from the platform when the platform is not included). This way, the bytecode generator can create appropriate LibraryIndexes to detect the use of `dart:` libraries which aren't already in VmTarget.extraIndexedLibraries, and thus in coreTypes.index (e.g., 'dart:ffi'). TEST=pkg/dart2bytecode Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try,vm-dyn-mac-debug-arm64-try Change-Id: Ic08f2022753950bf639c446c0b2594e1e269872a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/476760 Commit-Queue: Tess Strickland Reviewed-by: Alexander Markov --- pkg/dart2bytecode/bin/kernel_service.dart | 15 +- pkg/dart2bytecode/lib/bytecode_generator.dart | 47 +++-- pkg/dart2bytecode/lib/dart2bytecode.dart | 3 +- .../test/bytecode_generator_test.dart | 46 +++-- pkg/dart2bytecode/testcases/ffi.dart | 26 +++ pkg/dart2bytecode/testcases/ffi.dart.expect | 167 ++++++++++++++++++ pkg/vm/bin/kernel_service.dart | 11 +- pkg/vm/lib/incremental_compiler.dart | 7 +- 8 files changed, 281 insertions(+), 41 deletions(-) create mode 100644 pkg/dart2bytecode/testcases/ffi.dart create mode 100644 pkg/dart2bytecode/testcases/ffi.dart.expect 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.