[vm,ffi] Avoid linking test DLLs to dart.exe
The `ffi_test_functions` shared library needs to access functions from `dart_api.h` and `dart_native_api.h`, which are only available in the Dart executable. UNIX shared libraries can have undefined symbols, which are resolved at runtime and can be found in the loading executable. Windows DLLs cannot have undefined symbols, but they can be dynamically linked to an executable (in this case `dart.exe`). This requires the DLL to be able to find the executable at runtime. A better solution is to include implementations for the Dart APIs in the DLL itself, that use `GetModuleHandle(NULL)` to get a handle to the executable and `GetProcAddress` to get the address of the function. This is what `dart_api_win.c` does. Fixes https://github.com/dart-lang/sdk/issues/40579 Fixes https://github.com/dart-lang/sdk/issues/59677 TEST=ci Cq-Include-Trybots: luci.dart.try:vm-win-release-ia32-try,vm-win-debug-x64c-try,vm-win-debug-x64-try,vm-win-debug-arm64-try,vm-msvc-windows-try,vm-aot-win-debug-x64c-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-product-x64-try,vm-aot-win-release-x64-try,vm-aot-win-release-arm64-try,pkg-win-release-try,pkg-win-release-arm64-try,dart-sdk-win-try,dart-sdk-win-arm64-try Change-Id: I7f971a8ce21e03d18ed2967e74998f925c9236b2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400582 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com> Reviewed-by: Alexander Thomas <athom@google.com>
This commit is contained in:
committed by
Commit Queue
parent
1830689c80
commit
5c1796f4e0
@@ -529,6 +529,35 @@ def _CheckDevCompilerSync(input_api, output_api):
|
||||
return []
|
||||
|
||||
|
||||
def _CheckDartApiWinCSync(input_api, output_api):
|
||||
"""Ensure that dart_api_win.c is up-to-date."""
|
||||
GENERATOR = "runtime/tools/generate_dart_api_win_c.dart"
|
||||
DART_API_H = "runtime/include/dart_api.h"
|
||||
DART_NATIVe_API_H = "runtime/include/dart_native_api.h"
|
||||
|
||||
files = [git_file.LocalPath() for git_file in input_api.AffectedTextFiles()]
|
||||
|
||||
if (GENERATOR in files or DART_API_H in files or
|
||||
DART_NATIVe_API_H in files):
|
||||
# Run the generator with `--check-up-to-date` to see if the output is
|
||||
# up-to-date.
|
||||
args = [
|
||||
"tools/sdks/dart-sdk/bin/dart",
|
||||
GENERATOR,
|
||||
"--check-up-to-date",
|
||||
]
|
||||
try:
|
||||
subprocess.run(args, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
return [
|
||||
output_api.PresubmitError(
|
||||
f"Make sure to re-run {GENERATOR} when it or its inputs "
|
||||
"change.")
|
||||
]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def _CommonChecks(input_api, output_api):
|
||||
results = []
|
||||
results.extend(_CheckValidHostsInDEPS(input_api, output_api))
|
||||
@@ -544,6 +573,7 @@ def _CommonChecks(input_api, output_api):
|
||||
results.extend(_CheckAnalyzerFiles(input_api, output_api))
|
||||
results.extend(_CheckNoNewObservatoryServiceTests(input_api, output_api))
|
||||
results.extend(_CheckDevCompilerSync(input_api, output_api))
|
||||
results.extend(_CheckDartApiWinCSync(input_api, output_api))
|
||||
return results
|
||||
|
||||
|
||||
|
||||
@@ -1123,6 +1123,9 @@ executable("run_vm_tests") {
|
||||
shared_library("entrypoints_verification_test") {
|
||||
deps = [ ":dart" ]
|
||||
sources = [ "entrypoints_verification_test.cc" ]
|
||||
if (is_win) {
|
||||
sources += [ "dart_api_win.c" ]
|
||||
}
|
||||
include_dirs = [ ".." ]
|
||||
}
|
||||
|
||||
@@ -1149,19 +1152,17 @@ shared_library("ffi_test_functions") {
|
||||
"ffi_test/ffi_test_functions_generated_2.cc",
|
||||
"ffi_test/ffi_test_functions_vmspecific.cc",
|
||||
]
|
||||
|
||||
if (is_win) {
|
||||
sources += [ "dart_api_win.c" ]
|
||||
}
|
||||
|
||||
if (is_win && current_cpu == "x64") {
|
||||
sources += [ "ffi_test/clobber_x64_win.S" ]
|
||||
} else if (!is_win) {
|
||||
sources += [ "ffi_test/clobber_$current_cpu.S" ]
|
||||
}
|
||||
include_dirs = [ ".." ]
|
||||
|
||||
if (is_win) {
|
||||
# TODO(dartbug.com/40579): This wrongly links in dart.exe on precompiled.
|
||||
libs = [ "dart.lib" ]
|
||||
abs_root_out_dir = rebase_path(root_out_dir)
|
||||
ldflags = [ "/LIBPATH:$abs_root_out_dir" ]
|
||||
}
|
||||
}
|
||||
|
||||
# DartLibFuzzer only "exists" for restricted configurations.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,9 +33,6 @@
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
|
||||
// TODO(dartbug.com/40579): This requires static linking to either link
|
||||
// dart.exe or dartaotruntime.exe on Windows.
|
||||
// The sample currently fails on Windows in AOT mode.
|
||||
#include "include/dart_api.h"
|
||||
#include "include/dart_native_api.h"
|
||||
|
||||
|
||||
@@ -279,9 +279,6 @@ dart/kernel_determinism_test: SkipSlow
|
||||
dart/regress_48196_test: SkipSlow
|
||||
dart/regress_52703_test: SkipSlow
|
||||
|
||||
[ $compiler == dartkp && $system == windows ]
|
||||
dart/isolates/dart_api_create_lightweight_isolate_test: SkipByDesign # https://dartbug.com/40579 Dart C API symbols not available.
|
||||
|
||||
[ $compiler == dartkp && $simulator ]
|
||||
dart/awaiter_stacks/stream_methods_test/1: Pass, Slow
|
||||
dart/isolates/fast_object_copy2_test*: Skip # Uses ffi which is not available on simulated architectures
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) 2025, 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:io';
|
||||
import 'dart:ffi';
|
||||
|
||||
final repoRoot = File.fromUri(Platform.script).parent.parent.parent.path;
|
||||
final runtimeRoot = '$repoRoot/runtime';
|
||||
final buildtoolsRoot = '$repoRoot/buildtools';
|
||||
final clangBinDir =
|
||||
'$buildtoolsRoot/$currentPlatformBuildtoolsSubdir/clang/bin';
|
||||
final clangFormatBin = '$clangBinDir/clang-format';
|
||||
|
||||
final currentPlatformBuildtoolsSubdir = switch (Abi.current()) {
|
||||
Abi.macosX64 => 'mac-x64',
|
||||
Abi.macosArm64 => 'mac-arm64',
|
||||
Abi.linuxX64 => 'linux-x64',
|
||||
Abi.linuxArm64 => 'linux-arm64',
|
||||
Abi.windowsX64 => 'win-x64',
|
||||
Abi.windowsArm64 => 'win-arm64',
|
||||
_ => throw UnimplementedError(),
|
||||
};
|
||||
|
||||
void main(List<String> args) {
|
||||
final checkUpToDate = args.contains('--check-up-to-date');
|
||||
|
||||
final dartApiHFile = File('$runtimeRoot/include/dart_api.h');
|
||||
final dartNativeApiHFile = File('$runtimeRoot/include/dart_native_api.h');
|
||||
final dartApiWinCFile = File('$runtimeRoot/bin/dart_api_win.c');
|
||||
final dartApiWinCTmpFile = File('$runtimeRoot/bin/dart_api_win_tmp.c');
|
||||
final dartApiHContents = dartApiHFile.readAsStringSync();
|
||||
final dartNativeApiHContents = dartNativeApiHFile.readAsStringSync();
|
||||
|
||||
final procedureRegexp = RegExp(
|
||||
r'(DART_\w+\s+)+(?<returnType>[\w\s\*]+)\s+(?<name>\w+)\((?<arguments>[^)]*)\);',
|
||||
multiLine: true,
|
||||
);
|
||||
|
||||
final matches = [
|
||||
...procedureRegexp.allMatches(dartApiHContents),
|
||||
...procedureRegexp.allMatches(dartNativeApiHContents),
|
||||
];
|
||||
|
||||
final procedures = <Procedure>[];
|
||||
|
||||
for (final match in matches) {
|
||||
final returnType = match.namedGroup('returnType')!;
|
||||
final name = match.namedGroup('name')!;
|
||||
final argumentsString = match.namedGroup('arguments') ?? '';
|
||||
final argumentList =
|
||||
argumentsString.split(',').where((arg) => arg != 'void').map((arg) {
|
||||
final parts = arg.trim().split(' ');
|
||||
return (
|
||||
type: parts.sublist(0, parts.length - 1).join(' '),
|
||||
name: parts[parts.length - 1],
|
||||
);
|
||||
}).toList();
|
||||
procedures.add((
|
||||
name: name,
|
||||
returnType: returnType,
|
||||
arguments: argumentList,
|
||||
));
|
||||
}
|
||||
|
||||
final buffer = StringBuffer();
|
||||
|
||||
buffer.writeln('''
|
||||
// Copyright (c) 2025, 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.
|
||||
|
||||
// DO NOT EDIT. This file is generated by runtime/tools/generate_dart_api_win_c.dart.
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <include/dart_api.h>
|
||||
#include <include/dart_native_api.h>
|
||||
|
||||
''');
|
||||
|
||||
// Generate typedefs for all procedures.
|
||||
for (final procedure in procedures) {
|
||||
buffer.write('typedef ');
|
||||
buffer.write(procedure.returnType);
|
||||
buffer.write(' (*');
|
||||
buffer.write(procedure.typedefName);
|
||||
buffer.write(')(');
|
||||
for (final (i, argument) in procedure.arguments.indexed) {
|
||||
buffer.write(argument.type);
|
||||
if (i < procedure.arguments.length - 1) {
|
||||
buffer.write(', ');
|
||||
}
|
||||
}
|
||||
buffer.writeln(');');
|
||||
}
|
||||
|
||||
buffer.writeln();
|
||||
|
||||
// Generate function pointers for all procedures.
|
||||
for (final procedure in procedures) {
|
||||
buffer.write('static ');
|
||||
buffer.write(procedure.typedefName);
|
||||
buffer.write(' ');
|
||||
buffer.write(procedure.functionPointerName);
|
||||
buffer.writeln(' = NULL;');
|
||||
}
|
||||
|
||||
buffer.writeln();
|
||||
|
||||
// Generate the DllMain function that initializes all function pointers.
|
||||
buffer.writeln('''
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
|
||||
if (fdwReason == DLL_PROCESS_ATTACH) {
|
||||
HMODULE process = GetModuleHandle(NULL);
|
||||
''');
|
||||
|
||||
for (final procedure in procedures) {
|
||||
buffer.write(' ');
|
||||
buffer.write(procedure.functionPointerName);
|
||||
buffer.write(' = (');
|
||||
buffer.write(procedure.typedefName);
|
||||
buffer.write(') GetProcAddress(process, "');
|
||||
buffer.write(procedure.name);
|
||||
buffer.writeln('");');
|
||||
}
|
||||
|
||||
buffer.writeln('''
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}''');
|
||||
|
||||
buffer.writeln();
|
||||
|
||||
// Generate redirecting implementations for all procedures.
|
||||
for (final procedure in procedures) {
|
||||
final (:name, :returnType, :arguments) = procedure;
|
||||
buffer.write(returnType);
|
||||
buffer.write(' ');
|
||||
buffer.write(name);
|
||||
buffer.write('(');
|
||||
for (final (i, (:name, :type)) in arguments.indexed) {
|
||||
buffer.write(type);
|
||||
buffer.write(' ');
|
||||
buffer.write(name);
|
||||
if (i < arguments.length - 1) {
|
||||
buffer.write(', ');
|
||||
}
|
||||
}
|
||||
buffer.writeln(') {');
|
||||
buffer.write(' ');
|
||||
if (procedure.returnType != 'void') {
|
||||
buffer.write('return ');
|
||||
}
|
||||
buffer.write(procedure.functionPointerName);
|
||||
buffer.write('(');
|
||||
for (final (i, argument) in arguments.indexed) {
|
||||
buffer.write(argument.name);
|
||||
if (i < arguments.length - 1) {
|
||||
buffer.write(', ');
|
||||
}
|
||||
}
|
||||
buffer.writeln(');');
|
||||
buffer.writeln('}');
|
||||
buffer.writeln();
|
||||
}
|
||||
|
||||
buffer.writeln();
|
||||
|
||||
dartApiWinCTmpFile.writeAsStringSync(buffer.toString());
|
||||
|
||||
try {
|
||||
// Run clang-format on the generated file.
|
||||
final clangFormatResult = Process.runSync(
|
||||
clangFormatBin,
|
||||
['-i', dartApiWinCTmpFile.path],
|
||||
// Allows us to specify the path to the clang-format binary without the
|
||||
// .exe extension on Windows.
|
||||
runInShell: Platform.isWindows,
|
||||
);
|
||||
if (clangFormatResult.exitCode != 0) {
|
||||
print(clangFormatResult.stdout);
|
||||
print(clangFormatResult.stderr);
|
||||
exitCode = 1;
|
||||
} else {
|
||||
final changed =
|
||||
!dartApiWinCFile.existsSync() ||
|
||||
dartApiWinCTmpFile.readAsStringSync() !=
|
||||
dartApiWinCFile.readAsStringSync();
|
||||
if (changed) {
|
||||
if (checkUpToDate) {
|
||||
exitCode = 1;
|
||||
} else {
|
||||
dartApiWinCTmpFile.copySync(dartApiWinCFile.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
dartApiWinCTmpFile.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
typedef Procedure =
|
||||
({
|
||||
String name,
|
||||
String returnType,
|
||||
List<({String name, String type})> arguments,
|
||||
});
|
||||
|
||||
extension on Procedure {
|
||||
String get typedefName => '${name}Type';
|
||||
String get functionPointerName => '${name}Fn';
|
||||
}
|
||||
@@ -22,9 +22,6 @@ LibTest/collection/ListMixin/ListMixin_class_A01_t03: Slow, Pass
|
||||
LibTest/core/List/List_class_A01_t02: Slow, Pass
|
||||
LibTest/core/List/List_class_A01_t03: Slow, Pass
|
||||
|
||||
[ $runtime == dart_precompiled && $system == windows ]
|
||||
LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # https://dartbug.com/40579 Dart C API symbols not available.
|
||||
|
||||
[ $runtime == dart_precompiled && $simulator ]
|
||||
LibTest/collection/ListBase/ListBase_class_A01_t01: SkipSlow # Issue 43036
|
||||
LibTest/collection/ListMixin/ListMixin_class_A01_t01: SkipSlow # Issue 43036
|
||||
|
||||
@@ -79,18 +79,6 @@ native_assets/*: SkipByDesign # Only intended to run on host oses with AOT binar
|
||||
[ $compiler != dart2analyzer && $compiler != fasta && $runtime != dart_precompiled && $runtime != vm ]
|
||||
*: SkipByDesign # FFI is a VM-only feature. (This test suite is part of the default set.)
|
||||
|
||||
[ $compiler == dartkp && $system == windows ]
|
||||
vmspecific_ffi_native_handles_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
vmspecific_ffi_native_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
vmspecific_function_gc_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
vmspecific_handle_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
vmspecific_object_gc_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
vmspecific_regress_37100_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
vmspecific_regress_37511_callbacks_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
vmspecific_regress_37511_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
vmspecific_regress_37780_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
vmspecific_regress_51794_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
|
||||
|
||||
# These tests trigger and catch an abort (intentionally) and terminate the VM.
|
||||
# They're incompatible with ASAN because not all memory is freed when aborting and
|
||||
# with AppJit because the abort the VM before it can generate a snapshot.
|
||||
|
||||
@@ -18,7 +18,6 @@ import 'dylib_utils.dart';
|
||||
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
|
||||
|
||||
testLeafCall() {
|
||||
// Note: This test currently fails on Windows AOT: https://dartbug.com/40579
|
||||
// Regular calls should transition generated -> native.
|
||||
final isThreadInGenerated = ffiTestFunctions
|
||||
.lookupFunction<Int8 Function(), int Function()>("IsThreadInGenerated");
|
||||
|
||||
Reference in New Issue
Block a user