Skip to content

Commit 5c1796f

Browse files
blaugoldCommit Queue
authored andcommitted
[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 #40579 Fixes #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 <[email protected]> Reviewed-by: Daco Harkes <[email protected]> Reviewed-by: Alexander Thomas <[email protected]>
1 parent 1830689 commit 5c1796f

File tree

9 files changed

+2861
-29
lines changed

9 files changed

+2861
-29
lines changed

PRESUBMIT.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,35 @@ def _CheckDevCompilerSync(input_api, output_api):
529529
return []
530530

531531

532+
def _CheckDartApiWinCSync(input_api, output_api):
533+
"""Ensure that dart_api_win.c is up-to-date."""
534+
GENERATOR = "runtime/tools/generate_dart_api_win_c.dart"
535+
DART_API_H = "runtime/include/dart_api.h"
536+
DART_NATIVe_API_H = "runtime/include/dart_native_api.h"
537+
538+
files = [git_file.LocalPath() for git_file in input_api.AffectedTextFiles()]
539+
540+
if (GENERATOR in files or DART_API_H in files or
541+
DART_NATIVe_API_H in files):
542+
# Run the generator with `--check-up-to-date` to see if the output is
543+
# up-to-date.
544+
args = [
545+
"tools/sdks/dart-sdk/bin/dart",
546+
GENERATOR,
547+
"--check-up-to-date",
548+
]
549+
try:
550+
subprocess.run(args, check=True)
551+
except subprocess.CalledProcessError as e:
552+
return [
553+
output_api.PresubmitError(
554+
f"Make sure to re-run {GENERATOR} when it or its inputs "
555+
"change.")
556+
]
557+
558+
return []
559+
560+
532561
def _CommonChecks(input_api, output_api):
533562
results = []
534563
results.extend(_CheckValidHostsInDEPS(input_api, output_api))
@@ -544,6 +573,7 @@ def _CommonChecks(input_api, output_api):
544573
results.extend(_CheckAnalyzerFiles(input_api, output_api))
545574
results.extend(_CheckNoNewObservatoryServiceTests(input_api, output_api))
546575
results.extend(_CheckDevCompilerSync(input_api, output_api))
576+
results.extend(_CheckDartApiWinCSync(input_api, output_api))
547577
return results
548578

549579

runtime/bin/BUILD.gn

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,9 @@ executable("run_vm_tests") {
11231123
shared_library("entrypoints_verification_test") {
11241124
deps = [ ":dart" ]
11251125
sources = [ "entrypoints_verification_test.cc" ]
1126+
if (is_win) {
1127+
sources += [ "dart_api_win.c" ]
1128+
}
11261129
include_dirs = [ ".." ]
11271130
}
11281131

@@ -1149,19 +1152,17 @@ shared_library("ffi_test_functions") {
11491152
"ffi_test/ffi_test_functions_generated_2.cc",
11501153
"ffi_test/ffi_test_functions_vmspecific.cc",
11511154
]
1155+
1156+
if (is_win) {
1157+
sources += [ "dart_api_win.c" ]
1158+
}
1159+
11521160
if (is_win && current_cpu == "x64") {
11531161
sources += [ "ffi_test/clobber_x64_win.S" ]
11541162
} else if (!is_win) {
11551163
sources += [ "ffi_test/clobber_$current_cpu.S" ]
11561164
}
11571165
include_dirs = [ ".." ]
1158-
1159-
if (is_win) {
1160-
# TODO(dartbug.com/40579): This wrongly links in dart.exe on precompiled.
1161-
libs = [ "dart.lib" ]
1162-
abs_root_out_dir = rebase_path(root_out_dir)
1163-
ldflags = [ "/LIBPATH:$abs_root_out_dir" ]
1164-
}
11651166
}
11661167

11671168
# DartLibFuzzer only "exists" for restricted configurations.

runtime/bin/dart_api_win.c

Lines changed: 2609 additions & 0 deletions
Large diffs are not rendered by default.

runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@
3333
#include <iostream>
3434
#include <limits>
3535

36-
// TODO(dartbug.com/40579): This requires static linking to either link
37-
// dart.exe or dartaotruntime.exe on Windows.
38-
// The sample currently fails on Windows in AOT mode.
3936
#include "include/dart_api.h"
4037
#include "include/dart_native_api.h"
4138

runtime/tests/vm/vm.status

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,6 @@ dart/kernel_determinism_test: SkipSlow
279279
dart/regress_48196_test: SkipSlow
280280
dart/regress_52703_test: SkipSlow
281281

282-
[ $compiler == dartkp && $system == windows ]
283-
dart/isolates/dart_api_create_lightweight_isolate_test: SkipByDesign # https://dartbug.com/40579 Dart C API symbols not available.
284-
285282
[ $compiler == dartkp && $simulator ]
286283
dart/awaiter_stacks/stream_methods_test/1: Pass, Slow
287284
dart/isolates/fast_object_copy2_test*: Skip # Uses ffi which is not available on simulated architectures
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
import 'dart:ffi';
7+
8+
final repoRoot = File.fromUri(Platform.script).parent.parent.parent.path;
9+
final runtimeRoot = '$repoRoot/runtime';
10+
final buildtoolsRoot = '$repoRoot/buildtools';
11+
final clangBinDir =
12+
'$buildtoolsRoot/$currentPlatformBuildtoolsSubdir/clang/bin';
13+
final clangFormatBin = '$clangBinDir/clang-format';
14+
15+
final currentPlatformBuildtoolsSubdir = switch (Abi.current()) {
16+
Abi.macosX64 => 'mac-x64',
17+
Abi.macosArm64 => 'mac-arm64',
18+
Abi.linuxX64 => 'linux-x64',
19+
Abi.linuxArm64 => 'linux-arm64',
20+
Abi.windowsX64 => 'win-x64',
21+
Abi.windowsArm64 => 'win-arm64',
22+
_ => throw UnimplementedError(),
23+
};
24+
25+
void main(List<String> args) {
26+
final checkUpToDate = args.contains('--check-up-to-date');
27+
28+
final dartApiHFile = File('$runtimeRoot/include/dart_api.h');
29+
final dartNativeApiHFile = File('$runtimeRoot/include/dart_native_api.h');
30+
final dartApiWinCFile = File('$runtimeRoot/bin/dart_api_win.c');
31+
final dartApiWinCTmpFile = File('$runtimeRoot/bin/dart_api_win_tmp.c');
32+
final dartApiHContents = dartApiHFile.readAsStringSync();
33+
final dartNativeApiHContents = dartNativeApiHFile.readAsStringSync();
34+
35+
final procedureRegexp = RegExp(
36+
r'(DART_\w+\s+)+(?<returnType>[\w\s\*]+)\s+(?<name>\w+)\((?<arguments>[^)]*)\);',
37+
multiLine: true,
38+
);
39+
40+
final matches = [
41+
...procedureRegexp.allMatches(dartApiHContents),
42+
...procedureRegexp.allMatches(dartNativeApiHContents),
43+
];
44+
45+
final procedures = <Procedure>[];
46+
47+
for (final match in matches) {
48+
final returnType = match.namedGroup('returnType')!;
49+
final name = match.namedGroup('name')!;
50+
final argumentsString = match.namedGroup('arguments') ?? '';
51+
final argumentList =
52+
argumentsString.split(',').where((arg) => arg != 'void').map((arg) {
53+
final parts = arg.trim().split(' ');
54+
return (
55+
type: parts.sublist(0, parts.length - 1).join(' '),
56+
name: parts[parts.length - 1],
57+
);
58+
}).toList();
59+
procedures.add((
60+
name: name,
61+
returnType: returnType,
62+
arguments: argumentList,
63+
));
64+
}
65+
66+
final buffer = StringBuffer();
67+
68+
buffer.writeln('''
69+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
70+
// for details. All rights reserved. Use of this source code is governed by a
71+
// BSD-style license that can be found in the LICENSE file.
72+
73+
// DO NOT EDIT. This file is generated by runtime/tools/generate_dart_api_win_c.dart.
74+
75+
#include <windows.h>
76+
77+
#include <include/dart_api.h>
78+
#include <include/dart_native_api.h>
79+
80+
''');
81+
82+
// Generate typedefs for all procedures.
83+
for (final procedure in procedures) {
84+
buffer.write('typedef ');
85+
buffer.write(procedure.returnType);
86+
buffer.write(' (*');
87+
buffer.write(procedure.typedefName);
88+
buffer.write(')(');
89+
for (final (i, argument) in procedure.arguments.indexed) {
90+
buffer.write(argument.type);
91+
if (i < procedure.arguments.length - 1) {
92+
buffer.write(', ');
93+
}
94+
}
95+
buffer.writeln(');');
96+
}
97+
98+
buffer.writeln();
99+
100+
// Generate function pointers for all procedures.
101+
for (final procedure in procedures) {
102+
buffer.write('static ');
103+
buffer.write(procedure.typedefName);
104+
buffer.write(' ');
105+
buffer.write(procedure.functionPointerName);
106+
buffer.writeln(' = NULL;');
107+
}
108+
109+
buffer.writeln();
110+
111+
// Generate the DllMain function that initializes all function pointers.
112+
buffer.writeln('''
113+
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
114+
if (fdwReason == DLL_PROCESS_ATTACH) {
115+
HMODULE process = GetModuleHandle(NULL);
116+
''');
117+
118+
for (final procedure in procedures) {
119+
buffer.write(' ');
120+
buffer.write(procedure.functionPointerName);
121+
buffer.write(' = (');
122+
buffer.write(procedure.typedefName);
123+
buffer.write(') GetProcAddress(process, "');
124+
buffer.write(procedure.name);
125+
buffer.writeln('");');
126+
}
127+
128+
buffer.writeln('''
129+
}
130+
131+
return TRUE;
132+
}''');
133+
134+
buffer.writeln();
135+
136+
// Generate redirecting implementations for all procedures.
137+
for (final procedure in procedures) {
138+
final (:name, :returnType, :arguments) = procedure;
139+
buffer.write(returnType);
140+
buffer.write(' ');
141+
buffer.write(name);
142+
buffer.write('(');
143+
for (final (i, (:name, :type)) in arguments.indexed) {
144+
buffer.write(type);
145+
buffer.write(' ');
146+
buffer.write(name);
147+
if (i < arguments.length - 1) {
148+
buffer.write(', ');
149+
}
150+
}
151+
buffer.writeln(') {');
152+
buffer.write(' ');
153+
if (procedure.returnType != 'void') {
154+
buffer.write('return ');
155+
}
156+
buffer.write(procedure.functionPointerName);
157+
buffer.write('(');
158+
for (final (i, argument) in arguments.indexed) {
159+
buffer.write(argument.name);
160+
if (i < arguments.length - 1) {
161+
buffer.write(', ');
162+
}
163+
}
164+
buffer.writeln(');');
165+
buffer.writeln('}');
166+
buffer.writeln();
167+
}
168+
169+
buffer.writeln();
170+
171+
dartApiWinCTmpFile.writeAsStringSync(buffer.toString());
172+
173+
try {
174+
// Run clang-format on the generated file.
175+
final clangFormatResult = Process.runSync(
176+
clangFormatBin,
177+
['-i', dartApiWinCTmpFile.path],
178+
// Allows us to specify the path to the clang-format binary without the
179+
// .exe extension on Windows.
180+
runInShell: Platform.isWindows,
181+
);
182+
if (clangFormatResult.exitCode != 0) {
183+
print(clangFormatResult.stdout);
184+
print(clangFormatResult.stderr);
185+
exitCode = 1;
186+
} else {
187+
final changed =
188+
!dartApiWinCFile.existsSync() ||
189+
dartApiWinCTmpFile.readAsStringSync() !=
190+
dartApiWinCFile.readAsStringSync();
191+
if (changed) {
192+
if (checkUpToDate) {
193+
exitCode = 1;
194+
} else {
195+
dartApiWinCTmpFile.copySync(dartApiWinCFile.path);
196+
}
197+
}
198+
}
199+
} finally {
200+
dartApiWinCTmpFile.deleteSync();
201+
}
202+
}
203+
204+
typedef Procedure =
205+
({
206+
String name,
207+
String returnType,
208+
List<({String name, String type})> arguments,
209+
});
210+
211+
extension on Procedure {
212+
String get typedefName => '${name}Type';
213+
String get functionPointerName => '${name}Fn';
214+
}

tests/co19/co19-kernel.status

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ LibTest/collection/ListMixin/ListMixin_class_A01_t03: Slow, Pass
2222
LibTest/core/List/List_class_A01_t02: Slow, Pass
2323
LibTest/core/List/List_class_A01_t03: Slow, Pass
2424

25-
[ $runtime == dart_precompiled && $system == windows ]
26-
LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # https://dartbug.com/40579 Dart C API symbols not available.
27-
2825
[ $runtime == dart_precompiled && $simulator ]
2926
LibTest/collection/ListBase/ListBase_class_A01_t01: SkipSlow # Issue 43036
3027
LibTest/collection/ListMixin/ListMixin_class_A01_t01: SkipSlow # Issue 43036

tests/ffi/ffi.status

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,6 @@ native_assets/*: SkipByDesign # Only intended to run on host oses with AOT binar
7979
[ $compiler != dart2analyzer && $compiler != fasta && $runtime != dart_precompiled && $runtime != vm ]
8080
*: SkipByDesign # FFI is a VM-only feature. (This test suite is part of the default set.)
8181

82-
[ $compiler == dartkp && $system == windows ]
83-
vmspecific_ffi_native_handles_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
84-
vmspecific_ffi_native_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
85-
vmspecific_function_gc_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
86-
vmspecific_handle_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
87-
vmspecific_object_gc_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
88-
vmspecific_regress_37100_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
89-
vmspecific_regress_37511_callbacks_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
90-
vmspecific_regress_37511_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
91-
vmspecific_regress_37780_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
92-
vmspecific_regress_51794_test: SkipByDesign # Symbols are not exposed on purpose and are not linked in Windows Precompiled. dartbug.com/40579
93-
9482
# These tests trigger and catch an abort (intentionally) and terminate the VM.
9583
# They're incompatible with ASAN because not all memory is freed when aborting and
9684
# with AppJit because the abort the VM before it can generate a snapshot.

tests/ffi/vmspecific_leaf_call_test.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import 'dylib_utils.dart';
1818
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
1919

2020
testLeafCall() {
21-
// Note: This test currently fails on Windows AOT: https://dartbug.com/40579
2221
// Regular calls should transition generated -> native.
2322
final isThreadInGenerated = ffiTestFunctions
2423
.lookupFunction<Int8 Function(), int Function()>("IsThreadInGenerated");

0 commit comments

Comments
 (0)