[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 <sstrickl@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Tess Strickland
2026-02-06 06:13:22 -08:00
committed by Commit Queue
parent a9d5b4d3ee
commit b6c060a6ab
8 changed files with 281 additions and 41 deletions
+8 -7
View File
@@ -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<Library> libraries,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
Target target,
bool enableAsserts,
) {
Component component,
List<Library> libraries,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
Target target,
bool enableAsserts,
{Set<Library> extraLoadedLibraries = const {}}) {
final byteSink = new BytesSink();
generateBytecode(component, byteSink,
libraries: libraries,
extraLoadedLibraries: extraLoadedLibraries,
coreTypes: coreTypes,
hierarchy: hierarchy,
target: target,
+30 -17
View File
@@ -65,14 +65,16 @@ void generateBytecode(
required CoreTypes coreTypes,
required ClassHierarchy hierarchy,
required Target target,
required Set<Library> 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<Uri, Source> astUriToSource;
final List<Library> libraries;
final Set<Library> 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<Library> libraries,
Set<Library> 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<ParsedVmDeeplyImmutablePragma>(
node.annotations).isNotEmpty;
isInDeeplyImmutableClass = pragmaParser
.parsedPragmas<ParsedVmDeeplyImmutablePragma>(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();
+2 -1
View File
@@ -266,7 +266,8 @@ Future<int> 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();
@@ -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<Component> compileTestCaseToKernelProgram(Uri sourceUri,
// Similar to CompilerResult from pkg/vm/bin/kernel_service.dart.
class CompilerResult {
final Component component;
final Set<Library> loadedLibraries;
final ClassHierarchy? classHierarchy;
final CoreTypes? coreTypes;
CompilerResult(
this.component,
this.loadedLibraries,
this.classHierarchy,
this.coreTypes,
);
}
Future<CompilerResult> compileTestCaseToKernelProgram(Uri sourceUri,
{required Target target}) async {
final platformKernel =
computePlatformBinariesLocation().resolve('vm_platform.dill');
final options = CompilerOptions()
..target = target
..omitPlatform = true
..additionalDills = <Uri>[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<List<int>> {
@@ -175,6 +200,7 @@ main() {
'field_initializers.dart',
'optional_params.dart',
'bootstrapping.dart',
'ffi.dart',
};
group('gen-bytecode-with-closure-context-lowering', () {
+26
View File
@@ -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<NativeFunction<Void Function()>>.fromAddress(
0xdeadbeef,
);
final function = pointer.asFunction<void Function()>();
function();
}
testIntInt() {
final pointer = Pointer<NativeFunction<Int32 Function(Int64)>>.fromAddress(
0xdeadbeef,
);
final function = pointer.asFunction<int Function(int)>();
return function(42);
}
void main() {
testVoidNoArg();
testIntInt();
}
+167
View File
@@ -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<dynamic> [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<dynamic> [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
}