[vm/ffi] Support varargs

This CL introduces `VarArgs` to `NativeFunction` signatures. The
`VarArgs` type takes a single type argument. This type argument is a
subtype of `NativeType` if there is a single variadic argument, and a
record with native types if there are multiple variadic arguments.
For example:
`NativeFunction<Void Function(Pointer<Char>, VarArgs<(Int32,Int32)>)>`
for calling refering to a `printf` binding with two `int32_t` arguments
passed as variadic arguments.

The logic of the native calling conventions are detailed in
https://dart-review.googlesource.com/c/sdk/+/278342.
Here we explain how this influences the FFI pipeline.

First, now that `VarArgs` is part of signatures, we have to unwrap
that when with the C types in the CFE transform and checking (analyzer
is in a separate CL), and also in the marshaller when looking up the
C type of arguments.

Second, we have to deal with `BothNativeLocations`. On windows x64,
floating point arguments must be passed both in FPU _and_ CPU
registers. For FFI calls, we solve this in the argument moves by just
copying to both locations. For FFI callbacks, we just take the FPU
register location (which avoids an extra bitcast).

Third, on System-V, we have to pass an upper bound of the number of
XMM registers used in AL. This means we instead RAX, we use R13 for the
target address. For variadic calls, we always pass 8 in AL as the valid
upper bound. We could consider passing the actual number of XMM
registers used.
We keep using RAX as default register for the function address on non-
variadic calls, because changing to R13 (the first free) register
creates more spilling in leaf calls. R13 is callee-saved while RAX is
not, so using R13 instead of RAX causes us to have to spill the value
from RAX on leaf calls.

Fourth, on both x64 and RISC-V, we pass floats in integer locations.
`EmitNativeMove` has been modified to deal with this, so that we do not
have to insert more `BitCastInstr`s.

The tests are generated by a test generator: `tests/ffi/generator/`.

The formatter doesn't support records yet, so the tests are not properly
formatted.
Bug: https://github.com/dart-lang/sdk/issues/50798

TEST=tests/ffi/*_varargs_*

Closes: https://github.com/dart-lang/sdk/issues/38578
Closes: https://github.com/dart-lang/sdk/issues/49460
Closes: https://github.com/dart-lang/sdk/issues/50858

Change-Id: I6a6296fe972527f8a54ac75a630131769e3cc540
Cq-Include-Trybots: luci.dart.try:vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-reload-linux-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-nnbd-linux-debug-ia32-try,vm-kernel-win-debug-ia32-try,vm-kernel-linux-debug-x64-try,vm-kernel-mac-debug-x64-try,vm-kernel-win-debug-x64-try,vm-kernel-nnbd-win-release-ia32-try,vm-kernel-nnbd-win-debug-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-kernel-precomp-android-release-arm64c-try,vm-kernel-precomp-android-release-arm_x64-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-precomp-ffi-qemu-linux-release-riscv64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-precomp-asan-linux-release-x64-try,vm-kernel-msan-linux-release-x64-try,vm-kernel-precomp-msan-linux-release-x64-try,app-kernel-linux-debug-x64-try,vm-kernel-mac-release-arm64-try,vm-kernel-nnbd-mac-debug-arm64-try,vm-kernel-nnbd-mac-debug-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/276921
Reviewed-by: Devon Carew <devoncarew@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Daco Harkes 2023-01-20 10:30:41 +00:00
parent 7e60e54575
commit 6ef57b86c1
32 changed files with 5437 additions and 661 deletions

View file

@ -123,6 +123,7 @@ additionalExports = (ffi::nullptr,
ffi::UnsignedLongLong,
ffi::UnsignedShort,
ffi::Unsized,
ffi::VarArgs,
ffi::Void,
ffi::WChar)
@ -218,6 +219,7 @@ additionalExports = (ffi::nullptr,
ffi::UnsignedLongLong,
ffi::UnsignedShort,
ffi::Unsized,
ffi::VarArgs,
ffi::Void,
ffi::WChar)

View file

@ -123,6 +123,7 @@ additionalExports = (ffi::nullptr,
ffi::UnsignedLongLong,
ffi::UnsignedShort,
ffi::Unsized,
ffi::VarArgs,
ffi::Void,
ffi::WChar)
@ -218,6 +219,7 @@ additionalExports = (ffi::nullptr,
ffi::UnsignedLongLong,
ffi::UnsignedShort,
ffi::Unsized,
ffi::VarArgs,
ffi::Void,
ffi::WChar)

View file

@ -185,6 +185,7 @@ class FfiTransformer extends Transformer {
final Class unionClass;
final Class abiSpecificIntegerClass;
final Class abiSpecificIntegerMappingClass;
final Class varArgsClass;
final Class ffiNativeClass;
final Class nativeFieldWrapperClass1Class;
final Class ffiStructLayoutClass;
@ -341,6 +342,7 @@ class FfiTransformer extends Transformer {
index.getClass('dart:ffi', 'AbiSpecificInteger'),
abiSpecificIntegerMappingClass =
index.getClass('dart:ffi', 'AbiSpecificIntegerMapping'),
varArgsClass = index.getClass('dart:ffi', 'VarArgs'),
ffiNativeClass = index.getClass('dart:ffi', 'FfiNative'),
nativeFieldWrapperClass1Class =
index.getClass('dart:nativewrappers', 'NativeFieldWrapperClass1'),
@ -614,12 +616,40 @@ class FfiTransformer extends Transformer {
final DartType? returnType = convertNativeTypeToDartType(fun.returnType,
allowCompounds: true, allowHandle: true);
if (returnType == null) return null;
final List<DartType> argumentTypes = fun.positionalParameters
.map((t) =>
convertNativeTypeToDartType(t,
allowCompounds: true, allowHandle: true) ??
dummyDartType)
.toList();
final argumentTypes = <DartType>[];
bool seenVarArgs = false;
for (final paramDartType in fun.positionalParameters) {
if (seenVarArgs) {
// VarArgs is not last.
return null;
}
if (paramDartType is InterfaceType &&
paramDartType.classNode == varArgsClass) {
seenVarArgs = true;
final typeArgument = paramDartType.typeArguments.single;
if (typeArgument is RecordType) {
if (typeArgument.named.isNotEmpty) {
// Named record fields are not supported.
return null;
}
for (final paramDartType in typeArgument.positional) {
argumentTypes.add(
convertNativeTypeToDartType(paramDartType,
allowCompounds: true, allowHandle: true) ??
dummyDartType,
);
}
} else {
return null;
}
} else {
argumentTypes.add(
convertNativeTypeToDartType(paramDartType,
allowCompounds: true, allowHandle: true) ??
dummyDartType,
);
}
}
if (argumentTypes.contains(dummyDartType)) return null;
return FunctionType(argumentTypes, returnType, Nullability.legacy);
}

View file

@ -755,6 +755,7 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
klass == structClass ||
klass == unionClass ||
klass == abiSpecificIntegerClass ||
klass == varArgsClass ||
classNativeTypes[klass] != null) {
return null;
}

View file

@ -7,6 +7,7 @@
// therefore not allowed to use `dart_api.h`. (The flutter/flutter integration
// tests will run dart tests using this library only.)
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/types.h>
@ -1175,4 +1176,28 @@ DART_EXPORT int64_t WCharMaxValue() {
return WCHAR_MAX;
}
struct VarArgs {
int32_t a;
};
DART_EXPORT int64_t VariadicStructVarArgs(VarArgs a0, ...) {
va_list var_args;
va_start(var_args, a0);
VarArgs a1 = va_arg(var_args, VarArgs);
va_end(var_args);
std::cout << "VariadicStructVarArgs"
<< "(" << a0.a << ", " << a1.a << ")"
<< "\n";
int64_t result = 0;
result += a0.a;
result += a1.a;
std::cout << "result = " << result << "\n";
return result;
}
} // namespace dart

File diff suppressed because it is too large Load diff

View file

@ -3538,6 +3538,20 @@ void FlowGraphCompiler::EmitNativeMove(
const compiler::ffi::NativeLocation& destination,
const compiler::ffi::NativeLocation& source,
TemporaryRegisterAllocator* temp) {
if (destination.IsBoth()) {
// Copy to both.
const auto& both = destination.AsBoth();
EmitNativeMove(both.location(0), source, temp);
EmitNativeMove(both.location(1), source, temp);
return;
}
if (source.IsBoth()) {
// Copy from one of both.
const auto& both = source.AsBoth();
EmitNativeMove(destination, both.location(0), temp);
return;
}
const auto& src_payload_type = source.payload_type();
const auto& dst_payload_type = destination.payload_type();
const auto& src_container_type = source.container_type();
@ -3554,10 +3568,6 @@ void FlowGraphCompiler::EmitNativeMove(
// This function does not deal with sign conversions yet.
ASSERT(src_payload_type.IsSigned() == dst_payload_type.IsSigned());
// This function does not deal with bit casts yet.
ASSERT(src_container_type.IsFloat() == dst_container_type.IsFloat());
ASSERT(src_container_type.IsInt() == dst_container_type.IsInt());
// If the location, payload, and container are equal, we're done.
if (source.Equals(destination) && src_payload_type.Equals(dst_payload_type) &&
src_container_type.Equals(dst_container_type)) {

View file

@ -874,8 +874,7 @@ void FlowGraphCompiler::EmitNativeMoveArchitecture(
const compiler::ffi::NativeLocation& source) {
const auto& src_type = source.payload_type();
const auto& dst_type = destination.payload_type();
ASSERT(src_type.IsFloat() == dst_type.IsFloat());
ASSERT(src_type.IsInt() == dst_type.IsInt());
ASSERT(src_type.IsSigned() == dst_type.IsSigned());
ASSERT(src_type.IsPrimitive());
ASSERT(dst_type.IsPrimitive());
@ -939,8 +938,23 @@ void FlowGraphCompiler::EmitNativeMoveArchitecture(
}
} else if (destination.IsFpuRegisters()) {
// Fpu Registers should only contain doubles and registers only ints.
UNIMPLEMENTED();
const auto& dst = destination.AsFpuRegisters();
ASSERT(src_size == dst_size);
ASSERT(src.num_regs() == 1);
switch (src_size) {
case 4:
__ fmvwx(dst.fpu_reg(), src.reg_at(0));
return;
case 8:
#if XLEN == 32
UNIMPLEMENTED();
#else
__ fmvdx(dst.fpu_reg(), src.reg_at(0));
#endif
return;
default:
UNREACHABLE();
}
} else {
ASSERT(destination.IsStack());
@ -956,8 +970,23 @@ void FlowGraphCompiler::EmitNativeMoveArchitecture(
ASSERT(src_type.Equals(dst_type));
if (destination.IsRegisters()) {
// Fpu Registers should only contain doubles and registers only ints.
UNIMPLEMENTED();
const auto& dst = destination.AsRegisters();
ASSERT(src_size == dst_size);
ASSERT(dst.num_regs() == 1);
switch (src_size) {
case 4:
__ fmvxw(dst.reg_at(0), src.fpu_reg());
return;
case 8:
#if XLEN == 32
UNIMPLEMENTED();
#else
__ fmvxd(dst.reg_at(0), src.fpu_reg());
#endif
return;
default:
UNREACHABLE();
}
} else if (destination.IsFpuRegisters()) {
const auto& dst = destination.AsFpuRegisters();

View file

@ -851,8 +851,6 @@ void FlowGraphCompiler::EmitNativeMoveArchitecture(
const compiler::ffi::NativeLocation& source) {
const auto& src_type = source.payload_type();
const auto& dst_type = destination.payload_type();
ASSERT(src_type.IsFloat() == dst_type.IsFloat());
ASSERT(src_type.IsInt() == dst_type.IsInt());
ASSERT(src_type.IsSigned() == dst_type.IsSigned());
ASSERT(src_type.IsPrimitive());
ASSERT(dst_type.IsPrimitive());
@ -901,8 +899,18 @@ void FlowGraphCompiler::EmitNativeMoveArchitecture(
}
} else if (destination.IsFpuRegisters()) {
// Fpu Registers should only contain doubles and registers only ints.
UNIMPLEMENTED();
const auto& dst = destination.AsFpuRegisters();
ASSERT(src_size == dst_size);
switch (dst_size) {
case 8:
__ movq(dst.fpu_reg(), src_reg);
return;
case 4:
__ movd(dst.fpu_reg(), src_reg);
return;
default:
UNREACHABLE();
}
} else {
ASSERT(destination.IsStack());
@ -933,8 +941,20 @@ void FlowGraphCompiler::EmitNativeMoveArchitecture(
ASSERT(src_type.Equals(dst_type));
if (destination.IsRegisters()) {
// Fpu Registers should only contain doubles and registers only ints.
UNIMPLEMENTED();
ASSERT(src_size == dst_size);
const auto& dst = destination.AsRegisters();
ASSERT(dst.num_regs() == 1);
const auto dst_reg = dst.reg_at(0);
switch (dst_size) {
case 8:
__ movq(dst_reg, src.fpu_reg());
return;
case 4:
__ movl(dst_reg, src.fpu_reg());
return;
default:
UNREACHABLE();
}
} else if (destination.IsFpuRegisters()) {
const auto& dst = destination.AsFpuRegisters();

View file

@ -4171,14 +4171,17 @@ void NativeEntryInstr::SaveArgument(
ASSERT(pointer_loc.IsStack());
// It's already on the stack, so we don't have to save it.
}
} else {
ASSERT(nloc.IsMultiple());
} else if (nloc.IsMultiple()) {
const auto& multiple = nloc.AsMultiple();
const intptr_t num = multiple.locations().length();
// Save the argument registers, in reverse order.
for (intptr_t i = num; i-- > 0;) {
SaveArgument(compiler, *multiple.locations().At(i));
}
} else {
ASSERT(nloc.IsBoth());
const auto& both = nloc.AsBoth();
SaveArgument(compiler, both.location(0));
}
}
@ -6752,9 +6755,19 @@ LocationSummary* FfiCallInstr::MakeLocationSummaryInternal(
}
}
#if defined(TARGET_ARCH_X64) && !defined(DART_TARGET_OS_WINDOWS)
// Only use R13 if really needed, having R13 free causes less spilling.
const Register target_address =
marshaller_.contains_varargs()
? R13
: CallingConventions::kFirstNonArgumentRegister; // RAX
summary->set_in(TargetAddressIndex(),
Location::RegisterLocation(target_address));
#else
summary->set_in(TargetAddressIndex(),
Location::RegisterLocation(
CallingConventions::kFirstNonArgumentRegister));
#endif
for (intptr_t i = 0, n = marshaller_.NumDefinitions(); i < n; ++i) {
summary->set_in(i, marshaller_.LocInFfiCall(i));
}

View file

@ -1300,6 +1300,12 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ movq(compiler::Assembler::VMTagAddress(), target_address);
#endif
if (marshaller_.contains_varargs() &&
CallingConventions::kVarArgFpuRegisterCount != kNoRegister) {
// TODO(http://dartbug.com/38578): Use the number of used FPU registers.
__ LoadImmediate(CallingConventions::kVarArgFpuRegisterCount,
CallingConventions::kFpuArgumentRegisters);
}
__ CallCFunction(target_address, /*restore_rsp=*/true);
#if !defined(PRODUCT)
@ -1329,6 +1335,10 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ TransitionGeneratedToNative(target_address, FPREG, temp,
/*enter_safepoint=*/true);
if (marshaller_.contains_varargs() &&
CallingConventions::kVarArgFpuRegisterCount != kNoRegister) {
__ LoadImmediate(CallingConventions::kVarArgFpuRegisterCount, 8);
}
__ CallCFunction(target_address, /*restore_rsp=*/true);
// Update information in the thread object and leave the safepoint.
@ -1345,6 +1355,10 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// Calls RBX within a safepoint. RBX and R12 are clobbered.
__ movq(RBX, target_address);
if (marshaller_.contains_varargs() &&
CallingConventions::kVarArgFpuRegisterCount != kNoRegister) {
__ LoadImmediate(CallingConventions::kVarArgFpuRegisterCount, 8);
}
__ call(temp);
}

View file

@ -6,11 +6,14 @@
#include "platform/assert.h"
#include "platform/globals.h"
#include "vm/class_id.h"
#include "vm/compiler/ffi/frame_rebase.h"
#include "vm/compiler/ffi/native_calling_convention.h"
#include "vm/compiler/ffi/native_location.h"
#include "vm/compiler/ffi/native_type.h"
#include "vm/exceptions.h"
#include "vm/log.h"
#include "vm/object_store.h"
#include "vm/raw_object.h"
#include "vm/stack_frame.h"
#include "vm/symbols.h"
@ -31,19 +34,39 @@ const NativeFunctionType* NativeFunctionTypeFromFunctionType(
const char** error) {
ASSERT(c_signature.NumOptionalParameters() == 0);
ASSERT(c_signature.NumOptionalPositionalParameters() == 0);
ObjectStore* object_store = IsolateGroup::Current()->object_store();
const intptr_t num_arguments =
c_signature.num_fixed_parameters() - kNativeParamsStartAt;
auto& argument_representations =
*new ZoneGrowableArray<const NativeType*>(zone, num_arguments);
AbstractType& arg_type = AbstractType::Handle(zone);
intptr_t variadic_arguments_index = NativeFunctionType::kNoVariadicArguments;
for (intptr_t i = 0; i < num_arguments; i++) {
AbstractType& arg_type = AbstractType::Handle(
zone, c_signature.ParameterTypeAt(i + kNativeParamsStartAt));
const auto rep = NativeType::FromAbstractType(zone, arg_type, error);
if (*error != nullptr) {
return nullptr;
arg_type = c_signature.ParameterTypeAt(i + kNativeParamsStartAt);
const bool varargs = arg_type.type_class() == object_store->varargs_class();
if (varargs) {
arg_type = TypeArguments::Handle(zone, arg_type.arguments()).TypeAt(0);
variadic_arguments_index = i;
ASSERT(arg_type.IsRecordType());
const auto& record_type = RecordType::Cast(arg_type);
const intptr_t num_fields = record_type.NumFields();
auto& field_type = AbstractType::Handle(zone);
for (intptr_t i = 0; i < num_fields; i++) {
field_type ^= record_type.FieldTypeAt(i);
const auto rep = NativeType::FromAbstractType(zone, field_type, error);
if (*error != nullptr) {
return nullptr;
}
argument_representations.Add(rep);
}
} else {
const auto rep = NativeType::FromAbstractType(zone, arg_type, error);
if (*error != nullptr) {
return nullptr;
}
argument_representations.Add(rep);
}
argument_representations.Add(rep);
}
const auto& result_type =
@ -55,7 +78,8 @@ const NativeFunctionType* NativeFunctionTypeFromFunctionType(
}
const auto result = new (zone)
NativeFunctionType(argument_representations, *result_representation);
NativeFunctionType(argument_representations, *result_representation,
variadic_arguments_index);
return result;
}
@ -81,8 +105,38 @@ AbstractTypePtr BaseMarshaller::CType(intptr_t arg_index) const {
return c_signature_.result_type();
}
Zone* zone = Thread::Current()->zone();
const auto& parameter_types =
Array::Handle(zone, c_signature_.parameter_types());
const intptr_t parameter_type_length = parameter_types.Length();
const intptr_t last_param_index = parameter_type_length - 1;
const auto& last_arg_type = AbstractType::Handle(
zone, c_signature_.ParameterTypeAt(last_param_index));
ObjectStore* object_store = IsolateGroup::Current()->object_store();
const bool has_varargs =
last_arg_type.type_class() == object_store->varargs_class();
// Skip #0 argument, the function pointer.
return c_signature_.ParameterTypeAt(arg_index + kNativeParamsStartAt);
const intptr_t real_arg_index = arg_index + kNativeParamsStartAt;
if (has_varargs && real_arg_index >= last_param_index) {
// The C-type is nested in a VarArgs.
const auto& var_args_type_arg = AbstractType::Handle(
zone, TypeArguments::Handle(zone, last_arg_type.arguments()).TypeAt(0));
if (var_args_type_arg.IsRecordType()) {
const intptr_t index_in_record = real_arg_index - last_param_index;
const auto& record_type = RecordType::Cast(var_args_type_arg);
ASSERT(index_in_record < record_type.NumFields());
return record_type.FieldTypeAt(index_in_record);
} else {
ASSERT(!var_args_type_arg.IsNull());
return var_args_type_arg.ptr();
}
}
ASSERT(!AbstractType::Handle(c_signature_.ParameterTypeAt(real_arg_index))
.IsNull());
return c_signature_.ParameterTypeAt(real_arg_index);
}
// Keep consistent with Function::FfiCSignatureReturnsStruct.
@ -433,6 +487,11 @@ Location CallMarshaller::LocInFfiCall(intptr_t def_index_global) const {
return SelectFpuLocationInIL(zone_, loc);
}
if (loc.IsBoth()) {
const auto& fpu_reg_loc = loc.AsBoth().location(0).AsFpuRegisters();
return SelectFpuLocationInIL(zone_, fpu_reg_loc);
}
ASSERT(loc.IsRegisters());
return loc.AsLocation();
}
@ -555,12 +614,15 @@ class CallbackArgumentTranslator : public ValueObject {
if (arg.AsPointerToMemory().pointer_location().IsRegisters()) {
argument_slots_required_ += 1;
}
} else {
ASSERT(arg.IsMultiple());
} else if (arg.IsMultiple()) {
const auto& multiple = arg.AsMultiple();
for (intptr_t i = 0; i < multiple.locations().length(); i++) {
AllocateArgument(*multiple.locations().At(i));
}
} else {
ASSERT(arg.IsBoth());
const auto& both = arg.AsBoth();
AllocateArgument(both.location(0));
}
}
@ -613,16 +675,22 @@ class CallbackArgumentTranslator : public ValueObject {
pointer_translated, pointer_ret_loc, arg.payload_type().AsCompound());
}
ASSERT(arg.IsMultiple());
const auto& multiple = arg.AsMultiple();
NativeLocations& multiple_locations =
*new (zone) NativeLocations(multiple.locations().length());
for (intptr_t i = 0; i < multiple.locations().length(); i++) {
multiple_locations.Add(
&TranslateArgument(zone, *multiple.locations().At(i)));
if (arg.IsMultiple()) {
const auto& multiple = arg.AsMultiple();
NativeLocations& multiple_locations =
*new (zone) NativeLocations(multiple.locations().length());
for (intptr_t i = 0; i < multiple.locations().length(); i++) {
multiple_locations.Add(
&TranslateArgument(zone, *multiple.locations().At(i)));
}
return *new (zone) MultipleNativeLocations(
multiple.payload_type().AsCompound(), multiple_locations);
}
return *new (zone) MultipleNativeLocations(
multiple.payload_type().AsCompound(), multiple_locations);
ASSERT(arg.IsBoth());
const auto& both = arg.AsBoth();
// We only need one.
return TranslateArgument(zone, both.location(0));
}
intptr_t argument_slots_used_ = 0;

View file

@ -105,6 +105,8 @@ class BaseMarshaller : public ZoneAllocated {
// The C Type (expressed in a Dart Type) of the argument at `arg_index`.
//
// Excluding the #0 argument which is the function pointer.
//
// Recurses into VarArgs if needed.
AbstractTypePtr CType(intptr_t arg_index) const;
// Requires boxing or unboxing.
@ -131,6 +133,10 @@ class BaseMarshaller : public ZoneAllocated {
bool ContainsHandles() const;
bool contains_varargs() const {
return native_calling_convention_.contains_varargs();
}
const Function& dart_signature() const { return dart_signature_; }
StringPtr function_name() const { return dart_signature_.name(); }

View file

@ -881,7 +881,7 @@ UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_variadic_register_alignment) {
// `int ioctl(int, unsigned long, ...)`
//
// Binding in Dart with single variadic argument:
// `Int32 Function(Int32, Int64, VarArgs<Pointer<Void>>)`
// `Int32 Function(Int32, Int64, VarArgs<(Pointer<Void>,)>)`
//
// https://github.com/dart-lang/sdk/issues/49460
//

File diff suppressed because it is too large Load diff

View file

@ -557,6 +557,10 @@ void ObjectStore::LazyInitFfiMembers() {
Symbols::_handleNativeFinalizerMessage());
ASSERT(!function.IsNull());
handle_native_finalizer_message_function_.store(function.ptr());
cls = ffi_lib.LookupClass(Symbols::VarArgs());
ASSERT(!cls.IsNull());
varargs_class_.store(cls.ptr());
}
}

View file

@ -57,6 +57,7 @@ class ObjectPointerVisitor;
LAZY_CORE(Function, _object_to_string_function) \
LAZY_INTERNAL(Class, symbol_class) \
LAZY_INTERNAL(Field, symbol_name_field) \
LAZY_FFI(Class, varargs_class) \
LAZY_FFI(Function, handle_finalizer_message_function) \
LAZY_FFI(Function, handle_native_finalizer_message_function) \
LAZY_ASYNC(Type, non_nullable_future_rare_type) \

View file

@ -267,6 +267,7 @@ class ObjectPointerVisitor;
V(UnwindError, "UnwindError") \
V(Value, "value") \
V(Values, "values") \
V(VarArgs, "VarArgs") \
V(WeakArray, "WeakArray") \
V(WeakSerializationReference, "WeakSerializationReference") \
V(_AsyncStarStreamController, "_AsyncStarStreamController") \

View file

@ -77,3 +77,7 @@ abstract class Handle extends NativeType {}
@patch
@pragma("vm:entry-point")
abstract class NativeFunction<T extends Function> extends NativeType {}
@patch
@pragma("vm:entry-point")
abstract class VarArgs<T extends Record> extends NativeType {}

View file

@ -0,0 +1,3 @@
analyzer:
enable-experiment:
- records # TODO(http://dartbug.com/50586): Remove this when records are no longer an experiment.

View file

@ -144,3 +144,70 @@ abstract class Handle extends NativeType {}
/// marker in type signatures.
@unsized
abstract class NativeFunction<T extends Function> extends NativeType {}
/// The types of variadic arguments passed in C.
///
/// The signatures in [NativeFunction] need to specify the exact types of each
/// actual argument used in FFI calls.
///
/// For example take calling `printf` in C.
///
/// ```c
/// int printf(const char *format, ...);
///
/// void call_printf() {
/// int a = 4;
/// double b = 5.5;
/// const char* format = "...";
/// printf(format, a, b);
/// }
/// ```
///
/// To call `printf` directly from Dart with those two argument types, define
/// the native type as follows:
///
/// ```dart
/// /// `int printf(const char *format, ...)` with `int` and `double` as
/// /// varargs.
/// typedef NativePrintfIntDouble =
/// Int Function(Pointer<Char>, VarArgs<(Int, Double)>);
/// ```
///
/// Note the record type inside the `VarArgs` type argument.
///
/// If only a single variadic argument is passed, the record type must
/// contain a trailing comma:
///
/// ```dart continued
/// /// `int printf(const char *format, ...)` with only `int` as varargs.
/// typedef NativePrintfInt = Int Function(Pointer<Char>, VarArgs<(Int,)>);
/// ```
///
/// When a variadic function is called with different variadic argument types,
/// multiple bindings need to be created.
/// To avoid doing multiple [DynamicLibrary.lookup]s for the same symbol, the
/// pointer to the symbol can be cast:
///
/// ```dart continued
/// final dylib = DynamicLibrary.executable();
/// final printfPointer = dylib.lookup('printf');
/// final void Function(Pointer<Char>, int, double) printfIntDouble =
/// printfPointer.cast<NativeFunction<NativePrintfIntDouble>>().asFunction();
/// final void Function(Pointer<Char>, int) printfInt =
/// printfPointer.cast<NativeFunction<NativePrintfInt>>().asFunction();
/// ```
///
/// If no variadic argument is passed, the `VarArgs` must be passed with an
/// empty record type:
///
/// ```dart
/// /// `int printf(const char *format, ...)` with no varargs.
/// typedef NativePrintfNoVarArgs = Int Function(Pointer<Char>, VarArgs<()>);
/// ```
///
/// [VarArgs] must be the last parameter.
///
/// [VarArgs] is not constructible in the Dart code and serves purely as marker
/// in type signatures.
@Since('3.0')
abstract class VarArgs<T extends Record> extends NativeType {}

View file

@ -1,3 +1,6 @@
analyzer:
exclude:
# Do analyze this subfolder in the tests/ even if tests/ is fully excluded.
enable-experiment:
- records # TODO(http://dartbug.com/50586): Remove this when records are no longer an experiment.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,623 @@
// Copyright (c) 2023, 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.
//
// This file has been automatically generated. Please do not edit it manually.
// Generated by tests/ffi/generator/structs_by_value_tests_generator.dart.
//
// SharedObjects=ffi_test_functions
// VMOptions=--enable-experiment=records
// VMOptions=--enable-experiment=records --deterministic --optimization-counter-threshold=90
// VMOptions=--enable-experiment=records --use-slow-path
// VMOptions=--enable-experiment=records --use-slow-path --stacktrace-every=100
import 'dart:ffi';
import "package:expect/expect.dart";
import "package:ffi/ffi.dart";
import 'dylib_utils.dart';
// Reuse the compound classes.
import 'function_structs_by_value_generated_compounds.dart';
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
void main() {
for (int i = 0; i < 100; ++i) {
testVariadicAt1Int64x2Leaf();
testVariadicAt1Doublex2Leaf();
testVariadicAt1Int64x5Leaf();
testVariadicAt1Doublex5Leaf();
testVariadicAt1Int64x20Leaf();
testVariadicAt1Doublex20Leaf();
testVariadicAt1Int64x2Struct8BytesIntInt64Leaf();
testVariadicAt1Doublex2Struct32BytesHomogeneousDoubleDLeaf();
testVariadicAt1DoubleStruct12BytesHomogeneousFloatDoubLeaf();
testVariadicAt1Int32Struct20BytesHomogeneousInt32Int32Leaf();
testVariadicAt1DoubleStruct20BytesHomogeneousFloatDoubLeaf();
testVariadicAt2Int32Int64IntPtrLeaf();
testVariadicAt1DoubleInt64Int32DoubleInt64Int32Leaf();
testVariadicAt1Int64Int32Struct12BytesHomogeneousFloatLeaf();
testVariadicAt11Doublex8FloatStruct12BytesHomogeneousFLeaf();
testVariadicAt1DoubleInt64Int32Struct20BytesHomogeneouLeaf();
testVariadicAt5Doublex5Leaf();
}
}
final variadicAt1Int64x2Leaf =
ffiTestFunctions.lookupFunction<Int64 Function(Int64, VarArgs<(Int64,)>), int Function(int, int)>(
"VariadicAt1Int64x2", isLeaf:true);
/// Single variadic argument.
void testVariadicAt1Int64x2Leaf() {
int a0;
int a1;
a0 = -1;
a1 = 2;
final result = variadicAt1Int64x2Leaf(a0, a1);
print("result = $result");
Expect.equals(1, result);
}
final variadicAt1Doublex2Leaf =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Double,)>), double Function(double, double)>(
"VariadicAt1Doublex2", isLeaf:true);
/// Single variadic argument.
void testVariadicAt1Doublex2Leaf() {
double a0;
double a1;
a0 = -1.0;
a1 = 2.0;
final result = variadicAt1Doublex2Leaf(a0, a1);
print("result = $result");
Expect.approxEquals(1.0, result);
}
final variadicAt1Int64x5Leaf =
ffiTestFunctions.lookupFunction<Int64 Function(Int64, VarArgs<(Int64, Int64, Int64, Int64)>), int Function(int, int, int, int, int)>(
"VariadicAt1Int64x5", isLeaf:true);
/// Variadic arguments.
void testVariadicAt1Int64x5Leaf() {
int a0;
int a1;
int a2;
int a3;
int a4;
a0 = -1;
a1 = 2;
a2 = -3;
a3 = 4;
a4 = -5;
final result = variadicAt1Int64x5Leaf(a0, a1, a2, a3, a4);
print("result = $result");
Expect.equals(-3, result);
}
final variadicAt1Doublex5Leaf =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Double, Double, Double, Double)>), double Function(double, double, double, double, double)>(
"VariadicAt1Doublex5", isLeaf:true);
/// Variadic arguments.
void testVariadicAt1Doublex5Leaf() {
double a0;
double a1;
double a2;
double a3;
double a4;
a0 = -1.0;
a1 = 2.0;
a2 = -3.0;
a3 = 4.0;
a4 = -5.0;
final result = variadicAt1Doublex5Leaf(a0, a1, a2, a3, a4);
print("result = $result");
Expect.approxEquals(-3.0, result);
}
final variadicAt1Int64x20Leaf =
ffiTestFunctions.lookupFunction<Int64 Function(Int64, VarArgs<(Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64)>), int Function(int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int)>(
"VariadicAt1Int64x20", isLeaf:true);
/// Variadic arguments exhaust registers.
void testVariadicAt1Int64x20Leaf() {
int a0;
int a1;
int a2;
int a3;
int a4;
int a5;
int a6;
int a7;
int a8;
int a9;
int a10;
int a11;
int a12;
int a13;
int a14;
int a15;
int a16;
int a17;
int a18;
int a19;
a0 = -1;
a1 = 2;
a2 = -3;
a3 = 4;
a4 = -5;
a5 = 6;
a6 = -7;
a7 = 8;
a8 = -9;
a9 = 10;
a10 = -11;
a11 = 12;
a12 = -13;
a13 = 14;
a14 = -15;
a15 = 16;
a16 = -17;
a17 = 18;
a18 = -19;
a19 = 20;
final result = variadicAt1Int64x20Leaf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19);
print("result = $result");
Expect.equals(10, result);
}
final variadicAt1Doublex20Leaf =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double)>), double Function(double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)>(
"VariadicAt1Doublex20", isLeaf:true);
/// Variadic arguments exhaust registers.
void testVariadicAt1Doublex20Leaf() {
double a0;
double a1;
double a2;
double a3;
double a4;
double a5;
double a6;
double a7;
double a8;
double a9;
double a10;
double a11;
double a12;
double a13;
double a14;
double a15;
double a16;
double a17;
double a18;
double a19;
a0 = -1.0;
a1 = 2.0;
a2 = -3.0;
a3 = 4.0;
a4 = -5.0;
a5 = 6.0;
a6 = -7.0;
a7 = 8.0;
a8 = -9.0;
a9 = 10.0;
a10 = -11.0;
a11 = 12.0;
a12 = -13.0;
a13 = 14.0;
a14 = -15.0;
a15 = 16.0;
a16 = -17.0;
a17 = 18.0;
a18 = -19.0;
a19 = 20.0;
final result = variadicAt1Doublex20Leaf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19);
print("result = $result");
Expect.approxEquals(10.0, result);
}
final variadicAt1Int64x2Struct8BytesIntInt64Leaf =
ffiTestFunctions.lookupFunction<Int64 Function(Int64, VarArgs<(Int64, Struct8BytesInt, Int64)>), int Function(int, int, Struct8BytesInt, int)>(
"VariadicAt1Int64x2Struct8BytesIntInt64", isLeaf:true);
/// Variadic arguments including struct.
void testVariadicAt1Int64x2Struct8BytesIntInt64Leaf() {
int a0;
int a1;
final a2Pointer = calloc<Struct8BytesInt>();
final Struct8BytesInt a2 = a2Pointer.ref;
int a3;
a0 = -1;
a1 = 2;
a2.a0 = -3;
a2.a1 = 4;
a2.a2 = -5;
a3 = 6;
final result = variadicAt1Int64x2Struct8BytesIntInt64Leaf(a0, a1, a2, a3);
print("result = $result");
Expect.equals(3, result);
calloc.free(a2Pointer);
}
final variadicAt1Doublex2Struct32BytesHomogeneousDoubleDLeaf =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Double, Struct32BytesHomogeneousDouble, Double)>), double Function(double, double, Struct32BytesHomogeneousDouble, double)>(
"VariadicAt1Doublex2Struct32BytesHomogeneousDoubleD", isLeaf:true);
/// Variadic arguments including struct.
void testVariadicAt1Doublex2Struct32BytesHomogeneousDoubleDLeaf() {
double a0;
double a1;
final a2Pointer = calloc<Struct32BytesHomogeneousDouble>();
final Struct32BytesHomogeneousDouble a2 = a2Pointer.ref;
double a3;
a0 = -1.0;
a1 = 2.0;
a2.a0 = -3.0;
a2.a1 = 4.0;
a2.a2 = -5.0;
a2.a3 = 6.0;
a3 = -7.0;
final result = variadicAt1Doublex2Struct32BytesHomogeneousDoubleDLeaf(a0, a1, a2, a3);
print("result = $result");
Expect.approxEquals(-4.0, result);
calloc.free(a2Pointer);
}
final variadicAt1DoubleStruct12BytesHomogeneousFloatDoubLeaf =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Struct12BytesHomogeneousFloat, Double)>), double Function(double, Struct12BytesHomogeneousFloat, double)>(
"VariadicAt1DoubleStruct12BytesHomogeneousFloatDoub", isLeaf:true);
/// Variadic arguments including struct.
void testVariadicAt1DoubleStruct12BytesHomogeneousFloatDoubLeaf() {
double a0;
final a1Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a1 = a1Pointer.ref;
double a2;
a0 = -1.0;
a1.a0 = 2.0;
a1.a1 = -3.0;
a1.a2 = 4.0;
a2 = -5.0;
final result = variadicAt1DoubleStruct12BytesHomogeneousFloatDoubLeaf(a0, a1, a2);
print("result = $result");
Expect.approxEquals(-3.0, result);
calloc.free(a1Pointer);
}
final variadicAt1Int32Struct20BytesHomogeneousInt32Int32Leaf =
ffiTestFunctions.lookupFunction<Int32 Function(Int32, VarArgs<(Struct20BytesHomogeneousInt32, Int32)>), int Function(int, Struct20BytesHomogeneousInt32, int)>(
"VariadicAt1Int32Struct20BytesHomogeneousInt32Int32", isLeaf:true);
/// Variadic arguments including struct.
void testVariadicAt1Int32Struct20BytesHomogeneousInt32Int32Leaf() {
int a0;
final a1Pointer = calloc<Struct20BytesHomogeneousInt32>();
final Struct20BytesHomogeneousInt32 a1 = a1Pointer.ref;
int a2;
a0 = -1;
a1.a0 = 2;
a1.a1 = -3;
a1.a2 = 4;
a1.a3 = -5;
a1.a4 = 6;
a2 = -7;
final result = variadicAt1Int32Struct20BytesHomogeneousInt32Int32Leaf(a0, a1, a2);
print("result = $result");
Expect.equals(-4, result);
calloc.free(a1Pointer);
}
final variadicAt1DoubleStruct20BytesHomogeneousFloatDoubLeaf =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Struct20BytesHomogeneousFloat, Double)>), double Function(double, Struct20BytesHomogeneousFloat, double)>(
"VariadicAt1DoubleStruct20BytesHomogeneousFloatDoub", isLeaf:true);
/// Variadic arguments including struct.
void testVariadicAt1DoubleStruct20BytesHomogeneousFloatDoubLeaf() {
double a0;
final a1Pointer = calloc<Struct20BytesHomogeneousFloat>();
final Struct20BytesHomogeneousFloat a1 = a1Pointer.ref;
double a2;
a0 = -1.0;
a1.a0 = 2.0;
a1.a1 = -3.0;
a1.a2 = 4.0;
a1.a3 = -5.0;
a1.a4 = 6.0;
a2 = -7.0;
final result = variadicAt1DoubleStruct20BytesHomogeneousFloatDoubLeaf(a0, a1, a2);
print("result = $result");
Expect.approxEquals(-4.0, result);
calloc.free(a1Pointer);
}
final variadicAt2Int32Int64IntPtrLeaf =
ffiTestFunctions.lookupFunction<Int32 Function(Int32, Int64, VarArgs<(IntPtr,)>), int Function(int, int, int)>(
"VariadicAt2Int32Int64IntPtr", isLeaf:true);
/// Regression test for variadic arguments.
/// https://github.com/dart-lang/sdk/issues/49460
void testVariadicAt2Int32Int64IntPtrLeaf() {
int a0;
int a1;
int a2;
a0 = -1;
a1 = 2;
a2 = -3;
final result = variadicAt2Int32Int64IntPtrLeaf(a0, a1, a2);
print("result = $result");
Expect.equals(-2, result);
}
final variadicAt1DoubleInt64Int32DoubleInt64Int32Leaf =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Int64, Int32, Double, Int64, Int32)>), double Function(double, int, int, double, int, int)>(
"VariadicAt1DoubleInt64Int32DoubleInt64Int32", isLeaf:true);
/// Variadic arguments mixed.
void testVariadicAt1DoubleInt64Int32DoubleInt64Int32Leaf() {
double a0;
int a1;
int a2;
double a3;
int a4;
int a5;
a0 = -1.0;
a1 = 2;
a2 = -3;
a3 = 4.0;
a4 = -5;
a5 = 6;
final result = variadicAt1DoubleInt64Int32DoubleInt64Int32Leaf(a0, a1, a2, a3, a4, a5);
print("result = $result");
Expect.approxEquals(3.0, result);
}
final variadicAt1Int64Int32Struct12BytesHomogeneousFloatLeaf =
ffiTestFunctions.lookupFunction<Double Function(Int64, VarArgs<(Int32, Struct12BytesHomogeneousFloat)>), double Function(int, int, Struct12BytesHomogeneousFloat)>(
"VariadicAt1Int64Int32Struct12BytesHomogeneousFloat", isLeaf:true);
/// Variadic arguments homogenous struct stack alignment on macos_arm64.
void testVariadicAt1Int64Int32Struct12BytesHomogeneousFloatLeaf() {
int a0;
int a1;
final a2Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a2 = a2Pointer.ref;
a0 = -1;
a1 = 2;
a2.a0 = -3.0;
a2.a1 = 4.0;
a2.a2 = -5.0;
final result = variadicAt1Int64Int32Struct12BytesHomogeneousFloatLeaf(a0, a1, a2);
print("result = $result");
Expect.approxEquals(-3.0, result);
calloc.free(a2Pointer);
}
final variadicAt11Doublex8FloatStruct12BytesHomogeneousFLeaf =
ffiTestFunctions.lookupFunction<Double Function(Double, Double, Double, Double, Double, Double, Double, Double, Float, Struct12BytesHomogeneousFloat, Int64, VarArgs<(Int32, Struct12BytesHomogeneousFloat)>), double Function(double, double, double, double, double, double, double, double, double, Struct12BytesHomogeneousFloat, int, int, Struct12BytesHomogeneousFloat)>(
"VariadicAt11Doublex8FloatStruct12BytesHomogeneousF", isLeaf:true);
/// Variadic arguments homogenous struct stack alignment on macos_arm64.
void testVariadicAt11Doublex8FloatStruct12BytesHomogeneousFLeaf() {
double a0;
double a1;
double a2;
double a3;
double a4;
double a5;
double a6;
double a7;
double a8;
final a9Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a9 = a9Pointer.ref;
int a10;
int a11;
final a12Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a12 = a12Pointer.ref;
a0 = -1.0;
a1 = 2.0;
a2 = -3.0;
a3 = 4.0;
a4 = -5.0;
a5 = 6.0;
a6 = -7.0;
a7 = 8.0;
a8 = -9.0;
a9.a0 = 10.0;
a9.a1 = -11.0;
a9.a2 = 12.0;
a10 = -13;
a11 = 14;
a12.a0 = -15.0;
a12.a1 = 16.0;
a12.a2 = -17.0;
final result = variadicAt11Doublex8FloatStruct12BytesHomogeneousFLeaf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
print("result = $result");
Expect.approxEquals(-9.0, result);
calloc.free(a9Pointer);
calloc.free(a12Pointer);
}
final variadicAt1DoubleInt64Int32Struct20BytesHomogeneouLeaf =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Int64, Int32, Struct20BytesHomogeneousInt32, Double, Int64, Int32, Struct12BytesHomogeneousFloat, Int64)>), double Function(double, int, int, Struct20BytesHomogeneousInt32, double, int, int, Struct12BytesHomogeneousFloat, int)>(
"VariadicAt1DoubleInt64Int32Struct20BytesHomogeneou", isLeaf:true);
/// Variadic arguments mixed.
void testVariadicAt1DoubleInt64Int32Struct20BytesHomogeneouLeaf() {
double a0;
int a1;
int a2;
final a3Pointer = calloc<Struct20BytesHomogeneousInt32>();
final Struct20BytesHomogeneousInt32 a3 = a3Pointer.ref;
double a4;
int a5;
int a6;
final a7Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a7 = a7Pointer.ref;
int a8;
a0 = -1.0;
a1 = 2;
a2 = -3;
a3.a0 = 4;
a3.a1 = -5;
a3.a2 = 6;
a3.a3 = -7;
a3.a4 = 8;
a4 = -9.0;
a5 = 10;
a6 = -11;
a7.a0 = 12.0;
a7.a1 = -13.0;
a7.a2 = 14.0;
a8 = -15;
final result = variadicAt1DoubleInt64Int32Struct20BytesHomogeneouLeaf(a0, a1, a2, a3, a4, a5, a6, a7, a8);
print("result = $result");
Expect.approxEquals(-8.0, result);
calloc.free(a3Pointer);
calloc.free(a7Pointer);
}
final variadicAt5Doublex5Leaf =
ffiTestFunctions.lookupFunction<Double Function(Double, Double, Double, Double, Double, VarArgs<()>), double Function(double, double, double, double, double)>(
"VariadicAt5Doublex5", isLeaf:true);
/// Variadic arguments function definition, but not passing any.
void testVariadicAt5Doublex5Leaf() {
double a0;
double a1;
double a2;
double a3;
double a4;
a0 = -1.0;
a1 = 2.0;
a2 = -3.0;
a3 = 4.0;
a4 = -5.0;
final result = variadicAt5Doublex5Leaf(a0, a1, a2, a3, a4);
print("result = $result");
Expect.approxEquals(-3.0, result);
}

View file

@ -0,0 +1,623 @@
// Copyright (c) 2023, 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.
//
// This file has been automatically generated. Please do not edit it manually.
// Generated by tests/ffi/generator/structs_by_value_tests_generator.dart.
//
// SharedObjects=ffi_test_functions
// VMOptions=--enable-experiment=records
// VMOptions=--enable-experiment=records --deterministic --optimization-counter-threshold=90
// VMOptions=--enable-experiment=records --use-slow-path
// VMOptions=--enable-experiment=records --use-slow-path --stacktrace-every=100
import 'dart:ffi';
import "package:expect/expect.dart";
import "package:ffi/ffi.dart";
import 'dylib_utils.dart';
// Reuse the compound classes.
import 'function_structs_by_value_generated_compounds.dart';
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
void main() {
for (int i = 0; i < 100; ++i) {
testVariadicAt1Int64x2();
testVariadicAt1Doublex2();
testVariadicAt1Int64x5();
testVariadicAt1Doublex5();
testVariadicAt1Int64x20();
testVariadicAt1Doublex20();
testVariadicAt1Int64x2Struct8BytesIntInt64();
testVariadicAt1Doublex2Struct32BytesHomogeneousDoubleD();
testVariadicAt1DoubleStruct12BytesHomogeneousFloatDoub();
testVariadicAt1Int32Struct20BytesHomogeneousInt32Int32();
testVariadicAt1DoubleStruct20BytesHomogeneousFloatDoub();
testVariadicAt2Int32Int64IntPtr();
testVariadicAt1DoubleInt64Int32DoubleInt64Int32();
testVariadicAt1Int64Int32Struct12BytesHomogeneousFloat();
testVariadicAt11Doublex8FloatStruct12BytesHomogeneousF();
testVariadicAt1DoubleInt64Int32Struct20BytesHomogeneou();
testVariadicAt5Doublex5();
}
}
final variadicAt1Int64x2 =
ffiTestFunctions.lookupFunction<Int64 Function(Int64, VarArgs<(Int64,)>), int Function(int, int)>(
"VariadicAt1Int64x2");
/// Single variadic argument.
void testVariadicAt1Int64x2() {
int a0;
int a1;
a0 = -1;
a1 = 2;
final result = variadicAt1Int64x2(a0, a1);
print("result = $result");
Expect.equals(1, result);
}
final variadicAt1Doublex2 =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Double,)>), double Function(double, double)>(
"VariadicAt1Doublex2");
/// Single variadic argument.
void testVariadicAt1Doublex2() {
double a0;
double a1;
a0 = -1.0;
a1 = 2.0;
final result = variadicAt1Doublex2(a0, a1);
print("result = $result");
Expect.approxEquals(1.0, result);
}
final variadicAt1Int64x5 =
ffiTestFunctions.lookupFunction<Int64 Function(Int64, VarArgs<(Int64, Int64, Int64, Int64)>), int Function(int, int, int, int, int)>(
"VariadicAt1Int64x5");
/// Variadic arguments.
void testVariadicAt1Int64x5() {
int a0;
int a1;
int a2;
int a3;
int a4;
a0 = -1;
a1 = 2;
a2 = -3;
a3 = 4;
a4 = -5;
final result = variadicAt1Int64x5(a0, a1, a2, a3, a4);
print("result = $result");
Expect.equals(-3, result);
}
final variadicAt1Doublex5 =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Double, Double, Double, Double)>), double Function(double, double, double, double, double)>(
"VariadicAt1Doublex5");
/// Variadic arguments.
void testVariadicAt1Doublex5() {
double a0;
double a1;
double a2;
double a3;
double a4;
a0 = -1.0;
a1 = 2.0;
a2 = -3.0;
a3 = 4.0;
a4 = -5.0;
final result = variadicAt1Doublex5(a0, a1, a2, a3, a4);
print("result = $result");
Expect.approxEquals(-3.0, result);
}
final variadicAt1Int64x20 =
ffiTestFunctions.lookupFunction<Int64 Function(Int64, VarArgs<(Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64)>), int Function(int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int)>(
"VariadicAt1Int64x20");
/// Variadic arguments exhaust registers.
void testVariadicAt1Int64x20() {
int a0;
int a1;
int a2;
int a3;
int a4;
int a5;
int a6;
int a7;
int a8;
int a9;
int a10;
int a11;
int a12;
int a13;
int a14;
int a15;
int a16;
int a17;
int a18;
int a19;
a0 = -1;
a1 = 2;
a2 = -3;
a3 = 4;
a4 = -5;
a5 = 6;
a6 = -7;
a7 = 8;
a8 = -9;
a9 = 10;
a10 = -11;
a11 = 12;
a12 = -13;
a13 = 14;
a14 = -15;
a15 = 16;
a16 = -17;
a17 = 18;
a18 = -19;
a19 = 20;
final result = variadicAt1Int64x20(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19);
print("result = $result");
Expect.equals(10, result);
}
final variadicAt1Doublex20 =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double, Double)>), double Function(double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)>(
"VariadicAt1Doublex20");
/// Variadic arguments exhaust registers.
void testVariadicAt1Doublex20() {
double a0;
double a1;
double a2;
double a3;
double a4;
double a5;
double a6;
double a7;
double a8;
double a9;
double a10;
double a11;
double a12;
double a13;
double a14;
double a15;
double a16;
double a17;
double a18;
double a19;
a0 = -1.0;
a1 = 2.0;
a2 = -3.0;
a3 = 4.0;
a4 = -5.0;
a5 = 6.0;
a6 = -7.0;
a7 = 8.0;
a8 = -9.0;
a9 = 10.0;
a10 = -11.0;
a11 = 12.0;
a12 = -13.0;
a13 = 14.0;
a14 = -15.0;
a15 = 16.0;
a16 = -17.0;
a17 = 18.0;
a18 = -19.0;
a19 = 20.0;
final result = variadicAt1Doublex20(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19);
print("result = $result");
Expect.approxEquals(10.0, result);
}
final variadicAt1Int64x2Struct8BytesIntInt64 =
ffiTestFunctions.lookupFunction<Int64 Function(Int64, VarArgs<(Int64, Struct8BytesInt, Int64)>), int Function(int, int, Struct8BytesInt, int)>(
"VariadicAt1Int64x2Struct8BytesIntInt64");
/// Variadic arguments including struct.
void testVariadicAt1Int64x2Struct8BytesIntInt64() {
int a0;
int a1;
final a2Pointer = calloc<Struct8BytesInt>();
final Struct8BytesInt a2 = a2Pointer.ref;
int a3;
a0 = -1;
a1 = 2;
a2.a0 = -3;
a2.a1 = 4;
a2.a2 = -5;
a3 = 6;
final result = variadicAt1Int64x2Struct8BytesIntInt64(a0, a1, a2, a3);
print("result = $result");
Expect.equals(3, result);
calloc.free(a2Pointer);
}
final variadicAt1Doublex2Struct32BytesHomogeneousDoubleD =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Double, Struct32BytesHomogeneousDouble, Double)>), double Function(double, double, Struct32BytesHomogeneousDouble, double)>(
"VariadicAt1Doublex2Struct32BytesHomogeneousDoubleD");
/// Variadic arguments including struct.
void testVariadicAt1Doublex2Struct32BytesHomogeneousDoubleD() {
double a0;
double a1;
final a2Pointer = calloc<Struct32BytesHomogeneousDouble>();
final Struct32BytesHomogeneousDouble a2 = a2Pointer.ref;
double a3;
a0 = -1.0;
a1 = 2.0;
a2.a0 = -3.0;
a2.a1 = 4.0;
a2.a2 = -5.0;
a2.a3 = 6.0;
a3 = -7.0;
final result = variadicAt1Doublex2Struct32BytesHomogeneousDoubleD(a0, a1, a2, a3);
print("result = $result");
Expect.approxEquals(-4.0, result);
calloc.free(a2Pointer);
}
final variadicAt1DoubleStruct12BytesHomogeneousFloatDoub =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Struct12BytesHomogeneousFloat, Double)>), double Function(double, Struct12BytesHomogeneousFloat, double)>(
"VariadicAt1DoubleStruct12BytesHomogeneousFloatDoub");
/// Variadic arguments including struct.
void testVariadicAt1DoubleStruct12BytesHomogeneousFloatDoub() {
double a0;
final a1Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a1 = a1Pointer.ref;
double a2;
a0 = -1.0;
a1.a0 = 2.0;
a1.a1 = -3.0;
a1.a2 = 4.0;
a2 = -5.0;
final result = variadicAt1DoubleStruct12BytesHomogeneousFloatDoub(a0, a1, a2);
print("result = $result");
Expect.approxEquals(-3.0, result);
calloc.free(a1Pointer);
}
final variadicAt1Int32Struct20BytesHomogeneousInt32Int32 =
ffiTestFunctions.lookupFunction<Int32 Function(Int32, VarArgs<(Struct20BytesHomogeneousInt32, Int32)>), int Function(int, Struct20BytesHomogeneousInt32, int)>(
"VariadicAt1Int32Struct20BytesHomogeneousInt32Int32");
/// Variadic arguments including struct.
void testVariadicAt1Int32Struct20BytesHomogeneousInt32Int32() {
int a0;
final a1Pointer = calloc<Struct20BytesHomogeneousInt32>();
final Struct20BytesHomogeneousInt32 a1 = a1Pointer.ref;
int a2;
a0 = -1;
a1.a0 = 2;
a1.a1 = -3;
a1.a2 = 4;
a1.a3 = -5;
a1.a4 = 6;
a2 = -7;
final result = variadicAt1Int32Struct20BytesHomogeneousInt32Int32(a0, a1, a2);
print("result = $result");
Expect.equals(-4, result);
calloc.free(a1Pointer);
}
final variadicAt1DoubleStruct20BytesHomogeneousFloatDoub =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Struct20BytesHomogeneousFloat, Double)>), double Function(double, Struct20BytesHomogeneousFloat, double)>(
"VariadicAt1DoubleStruct20BytesHomogeneousFloatDoub");
/// Variadic arguments including struct.
void testVariadicAt1DoubleStruct20BytesHomogeneousFloatDoub() {
double a0;
final a1Pointer = calloc<Struct20BytesHomogeneousFloat>();
final Struct20BytesHomogeneousFloat a1 = a1Pointer.ref;
double a2;
a0 = -1.0;
a1.a0 = 2.0;
a1.a1 = -3.0;
a1.a2 = 4.0;
a1.a3 = -5.0;
a1.a4 = 6.0;
a2 = -7.0;
final result = variadicAt1DoubleStruct20BytesHomogeneousFloatDoub(a0, a1, a2);
print("result = $result");
Expect.approxEquals(-4.0, result);
calloc.free(a1Pointer);
}
final variadicAt2Int32Int64IntPtr =
ffiTestFunctions.lookupFunction<Int32 Function(Int32, Int64, VarArgs<(IntPtr,)>), int Function(int, int, int)>(
"VariadicAt2Int32Int64IntPtr");
/// Regression test for variadic arguments.
/// https://github.com/dart-lang/sdk/issues/49460
void testVariadicAt2Int32Int64IntPtr() {
int a0;
int a1;
int a2;
a0 = -1;
a1 = 2;
a2 = -3;
final result = variadicAt2Int32Int64IntPtr(a0, a1, a2);
print("result = $result");
Expect.equals(-2, result);
}
final variadicAt1DoubleInt64Int32DoubleInt64Int32 =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Int64, Int32, Double, Int64, Int32)>), double Function(double, int, int, double, int, int)>(
"VariadicAt1DoubleInt64Int32DoubleInt64Int32");
/// Variadic arguments mixed.
void testVariadicAt1DoubleInt64Int32DoubleInt64Int32() {
double a0;
int a1;
int a2;
double a3;
int a4;
int a5;
a0 = -1.0;
a1 = 2;
a2 = -3;
a3 = 4.0;
a4 = -5;
a5 = 6;
final result = variadicAt1DoubleInt64Int32DoubleInt64Int32(a0, a1, a2, a3, a4, a5);
print("result = $result");
Expect.approxEquals(3.0, result);
}
final variadicAt1Int64Int32Struct12BytesHomogeneousFloat =
ffiTestFunctions.lookupFunction<Double Function(Int64, VarArgs<(Int32, Struct12BytesHomogeneousFloat)>), double Function(int, int, Struct12BytesHomogeneousFloat)>(
"VariadicAt1Int64Int32Struct12BytesHomogeneousFloat");
/// Variadic arguments homogenous struct stack alignment on macos_arm64.
void testVariadicAt1Int64Int32Struct12BytesHomogeneousFloat() {
int a0;
int a1;
final a2Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a2 = a2Pointer.ref;
a0 = -1;
a1 = 2;
a2.a0 = -3.0;
a2.a1 = 4.0;
a2.a2 = -5.0;
final result = variadicAt1Int64Int32Struct12BytesHomogeneousFloat(a0, a1, a2);
print("result = $result");
Expect.approxEquals(-3.0, result);
calloc.free(a2Pointer);
}
final variadicAt11Doublex8FloatStruct12BytesHomogeneousF =
ffiTestFunctions.lookupFunction<Double Function(Double, Double, Double, Double, Double, Double, Double, Double, Float, Struct12BytesHomogeneousFloat, Int64, VarArgs<(Int32, Struct12BytesHomogeneousFloat)>), double Function(double, double, double, double, double, double, double, double, double, Struct12BytesHomogeneousFloat, int, int, Struct12BytesHomogeneousFloat)>(
"VariadicAt11Doublex8FloatStruct12BytesHomogeneousF");
/// Variadic arguments homogenous struct stack alignment on macos_arm64.
void testVariadicAt11Doublex8FloatStruct12BytesHomogeneousF() {
double a0;
double a1;
double a2;
double a3;
double a4;
double a5;
double a6;
double a7;
double a8;
final a9Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a9 = a9Pointer.ref;
int a10;
int a11;
final a12Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a12 = a12Pointer.ref;
a0 = -1.0;
a1 = 2.0;
a2 = -3.0;
a3 = 4.0;
a4 = -5.0;
a5 = 6.0;
a6 = -7.0;
a7 = 8.0;
a8 = -9.0;
a9.a0 = 10.0;
a9.a1 = -11.0;
a9.a2 = 12.0;
a10 = -13;
a11 = 14;
a12.a0 = -15.0;
a12.a1 = 16.0;
a12.a2 = -17.0;
final result = variadicAt11Doublex8FloatStruct12BytesHomogeneousF(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
print("result = $result");
Expect.approxEquals(-9.0, result);
calloc.free(a9Pointer);
calloc.free(a12Pointer);
}
final variadicAt1DoubleInt64Int32Struct20BytesHomogeneou =
ffiTestFunctions.lookupFunction<Double Function(Double, VarArgs<(Int64, Int32, Struct20BytesHomogeneousInt32, Double, Int64, Int32, Struct12BytesHomogeneousFloat, Int64)>), double Function(double, int, int, Struct20BytesHomogeneousInt32, double, int, int, Struct12BytesHomogeneousFloat, int)>(
"VariadicAt1DoubleInt64Int32Struct20BytesHomogeneou");
/// Variadic arguments mixed.
void testVariadicAt1DoubleInt64Int32Struct20BytesHomogeneou() {
double a0;
int a1;
int a2;
final a3Pointer = calloc<Struct20BytesHomogeneousInt32>();
final Struct20BytesHomogeneousInt32 a3 = a3Pointer.ref;
double a4;
int a5;
int a6;
final a7Pointer = calloc<Struct12BytesHomogeneousFloat>();
final Struct12BytesHomogeneousFloat a7 = a7Pointer.ref;
int a8;
a0 = -1.0;
a1 = 2;
a2 = -3;
a3.a0 = 4;
a3.a1 = -5;
a3.a2 = 6;
a3.a3 = -7;
a3.a4 = 8;
a4 = -9.0;
a5 = 10;
a6 = -11;
a7.a0 = 12.0;
a7.a1 = -13.0;
a7.a2 = 14.0;
a8 = -15;
final result = variadicAt1DoubleInt64Int32Struct20BytesHomogeneou(a0, a1, a2, a3, a4, a5, a6, a7, a8);
print("result = $result");
Expect.approxEquals(-8.0, result);
calloc.free(a3Pointer);
calloc.free(a7Pointer);
}
final variadicAt5Doublex5 =
ffiTestFunctions.lookupFunction<Double Function(Double, Double, Double, Double, Double, VarArgs<()>), double Function(double, double, double, double, double)>(
"VariadicAt5Doublex5");
/// Variadic arguments function definition, but not passing any.
void testVariadicAt5Doublex5() {
double a0;
double a1;
double a2;
double a3;
double a4;
a0 = -1.0;
a1 = 2.0;
a2 = -3.0;
a3 = 4.0;
a4 = -5.0;
final result = variadicAt5Doublex5(a0, a1, a2, a3, a4);
print("result = $result");
Expect.approxEquals(-3.0, result);
}

View file

@ -0,0 +1,35 @@
// Copyright (c) 2023, 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.
// VMOptions=--enable-experiment=records
// SharedObjects=ffi_test_functions
import 'dylib_utils.dart';
import 'dart:ffi' as ffi;
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
void main() {
using((arena) {
final structs = arena<VarArgs>(2);
structs[0].a = 1;
structs[1].a = 2;
final result = variadicStructVarArgs(structs[0], structs[1]);
Expect.equals(3, result);
});
}
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
final variadicStructVarArgs = ffiTestFunctions.lookupFunction<
ffi.Int64 Function(VarArgs, ffi.VarArgs<(VarArgs,)>),
int Function(VarArgs, VarArgs)>('VariadicStructVarArgs');
class VarArgs extends ffi.Struct {
@ffi.Int32()
external int a;
}

View file

@ -0,0 +1,34 @@
// Copyright (c) 2023, 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.
// VMOptions=--enable-experiment=records
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
void main() {
if(Platform.isWindows){
// printf is not linked in.
return;
}
using((arena) {
printf('Something\n'.toNativeUtf8(allocator: arena));
printfInt32('Something %i\n'.toNativeUtf8(allocator: arena), 32);
printfInt32x2('Something %i %i\n'.toNativeUtf8(allocator: arena), 32, 64);
});
}
final printf = DynamicLibrary.executable()
.lookupFunction<Void Function(Pointer<Utf8>, VarArgs<()>), void Function(Pointer<Utf8>)>(
'printf');
final printfInt32 = DynamicLibrary.executable().lookupFunction<
Void Function(Pointer<Utf8>, VarArgs<(Int32,)>),
void Function(Pointer<Utf8>, int)>('printf');
final printfInt32x2 = DynamicLibrary.executable().lookupFunction<
Void Function(Pointer<Utf8>, VarArgs<(Int32, Int32)>),
void Function(Pointer<Utf8>, int, int)>('printf');

View file

@ -71,7 +71,7 @@ final primitiveCType = {
PrimitiveType.uint16: "uint16_t",
PrimitiveType.uint32: "uint32_t",
PrimitiveType.uint64: "uint64_t",
PrimitiveType.intptr: "intptr",
PrimitiveType.intptr: "intptr_t",
PrimitiveType.float: "float",
PrimitiveType.double_: "double",
// People should use explicit sizes. But we also want to test `long`.
@ -92,7 +92,7 @@ final primitiveDartCType = {
PrimitiveType.uint16: "Uint16",
PrimitiveType.uint32: "Uint32",
PrimitiveType.uint64: "Uint64",
PrimitiveType.intptr: "Intptr",
PrimitiveType.intptr: "IntPtr",
PrimitiveType.float: "Float",
PrimitiveType.double_: "Double",
PrimitiveType.long: "Long",
@ -456,19 +456,55 @@ class FixedLengthArrayType extends CType {
class FunctionType extends CType {
final List<Member> arguments;
final int? varArgsIndex;
final CType returnValue;
final String reason;
List<CType> get argumentTypes => arguments.map((a) => a.type).toList();
FunctionType(List<CType> argumentTypes, this.returnValue, this.reason)
: this.arguments = generateMemberNames(argumentTypes);
FunctionType(
List<CType> argumentTypes,
this.returnValue,
this.reason, {
this.varArgsIndex,
}) : this.arguments = generateMemberNames(argumentTypes);
FunctionType withVariadicArguments({int index = 1}) {
if (index == 0) {
throw "C does not support varargs at 0th argument";
}
if (arguments.length <= index) {
throw "Cannot start varargs after arguments";
}
return FunctionType(
argumentTypes,
returnValue,
reason,
varArgsIndex: index,
);
}
String get cType =>
throw "Are not represented without function or variable name in C.";
String get dartCType {
final argumentsDartCType = argumentTypes.map((e) => e.dartCType).join(", ");
String argumentsDartCType;
final varArgsIndex_ = varArgsIndex;
if (varArgsIndex_ == null) {
argumentsDartCType = argumentTypes.map((e) => e.dartCType).join(', ');
} else {
final normalArgTypes = argumentTypes.take(varArgsIndex_).toList();
final varArgTypes = argumentTypes.skip(varArgsIndex_).toList();
final normalArgsString =
normalArgTypes.map((e) => e.dartCType).join(', ');
final varArgString = varArgTypes.map((e) => e.dartCType).join(', ');
final unaryRecordType = varArgTypes.length == 1;
if (unaryRecordType) {
argumentsDartCType = '$normalArgsString, VarArgs<($varArgString,)>';
} else {
argumentsDartCType = '$normalArgsString, VarArgs<($varArgString)>';
}
}
return "${returnValue.dartCType} Function($argumentsDartCType)";
}
@ -506,7 +542,9 @@ class FunctionType extends CType {
/// A suitable name based on the signature.
String get cName {
String result = "";
if (arguments.containsComposites && returnValue is FundamentalType) {
if (varArgsIndex != null) {
result = "VariadicAt$varArgsIndex";
} else if (arguments.containsComposites && returnValue is FundamentalType) {
result = "Pass";
} else if (returnValue is StructType &&
argumentTypes.contains(returnValue)) {
@ -546,3 +584,8 @@ extension MemberList on List<Member> {
bool get containsComposites =>
map((m) => m.type is CompositeType).contains(true);
}
extension ListT<T> on List<T> {
Iterable<S> mapWithIndex<S>(S Function(int, T) f) =>
asMap().entries.map((e) => f(e.key, e.value));
}

View file

@ -4,12 +4,14 @@
import 'c_types.dart';
/// All function types to test.
final functions = [
...functionsStructArguments,
...functionsStructReturn,
...functionsReturnArgument,
];
/// Functions that pass structs as arguments.
final functionsStructArguments = [
FunctionType(List.filled(10, struct1byteInt), int64, """
Smallest struct with data.
@ -436,6 +438,7 @@ Returning a bool."""),
Returning a wchar."""),
];
/// Functions that return a struct by value.
final functionsStructReturn = [
FunctionType(struct1byteInt.memberTypes, struct1byteInt, """
Smallest struct with data."""),
@ -521,6 +524,7 @@ Returning a mixed-size union."""),
Returning union with homogenous floats."""),
];
/// Functions that return an argument by value.
final functionsReturnArgument = [
FunctionType(
[struct1byteInt],
@ -687,6 +691,134 @@ final compounds = [
structArrayWChar,
];
/// Function signatures for variadic argument tests.
final functionsVarArgs = [
FunctionType(
varArgsIndex: 1,
[int64, int64],
int64,
"Single variadic argument.",
),
FunctionType(
varArgsIndex: 1,
[double_, double_],
double_,
"Single variadic argument.",
),
FunctionType(
varArgsIndex: 1,
[int64, int64, int64, int64, int64],
int64,
"Variadic arguments.",
),
FunctionType(
varArgsIndex: 1,
[double_, double_, double_, double_, double_],
double_,
"Variadic arguments.",
),
FunctionType(
varArgsIndex: 1,
List.filled(20, int64),
int64,
"Variadic arguments exhaust registers.",
),
FunctionType(
varArgsIndex: 1,
List.filled(20, double_),
double_,
"Variadic arguments exhaust registers.",
),
FunctionType(
varArgsIndex: 1,
[int64, int64, struct8bytesInt, int64],
int64,
"Variadic arguments including struct.",
),
FunctionType(
varArgsIndex: 1,
[double_, double_, struct32bytesDouble, double_],
double_,
"Variadic arguments including struct.",
),
FunctionType(
varArgsIndex: 1,
[double_, struct12bytesFloat, double_],
double_,
"Variadic arguments including struct.",
),
FunctionType(
varArgsIndex: 1,
[int32, struct20bytesInt, int32],
int32,
"Variadic arguments including struct.",
),
FunctionType(
varArgsIndex: 1,
[double_, struct20bytesFloat, double_],
double_,
"Variadic arguments including struct.",
),
FunctionType(
varArgsIndex: 2,
[int32, int64, intptr],
int32,
"""Regression test for variadic arguments.
https://github.com/dart-lang/sdk/issues/49460""",
),
FunctionType(
varArgsIndex: 1,
[double_, int64, int32, double_, int64, int32],
double_,
"Variadic arguments mixed.",
),
FunctionType(
varArgsIndex: 1,
[
int64, // Argument passed normally.
int32, // First argument passed as varargs, misaligning stack.
struct12bytesFloat, // Homogenous float struct, should be aligned.
],
double_,
"Variadic arguments homogenous struct stack alignment on macos_arm64.",
),
FunctionType(
varArgsIndex: 11,
[
...List.filled(8, double_), // Exhaust FPU registers.
float, // Misalign stack.
struct12bytesFloat, // Homogenous struct, not aligned to wordsize on stack.
int64, // Start varargs.
int32, // Misalign stack again.
struct12bytesFloat, // Homogenous struct, aligned to wordsize on stack.
],
double_,
"Variadic arguments homogenous struct stack alignment on macos_arm64.",
),
FunctionType(
varArgsIndex: 1,
[
double_,
int64,
int32,
struct20bytesInt,
double_,
int64,
int32,
struct12bytesFloat,
int64,
],
double_,
"Variadic arguments mixed.",
),
FunctionType(
varArgsIndex: 5,
[double_, double_, double_, double_, double_],
double_,
"Variadic arguments function definition, but not passing any.",
),
];
final struct1byteBool = StructType([bool_]);
final struct1byteInt = StructType([int8]);
final struct3bytesInt = StructType(List.filled(3, uint8));

View file

@ -40,7 +40,9 @@ extension on FunctionType {
return TestType.structReturn;
}
}
throw Exception("Unknown test type: $this");
// No structs, sum the arguments as well.
return TestType.structArguments;
}
}
@ -104,7 +106,7 @@ extension on CType {
case FundamentalType:
final this_ = this as FundamentalType;
final boolToInt = this_.isBool ? ' ? 1 : 0' : '';
return "result += $variableName$boolToInt;\n";
return " result += $variableName$boolToInt;\n";
case StructType:
final this_ = this as StructType;
@ -147,7 +149,7 @@ extension on CType {
switch (this.runtimeType) {
case FundamentalType:
final this_ = this as FundamentalType;
return "$variableName = ${a.nextValue(this_)};\n";
return " $variableName = ${a.nextValue(this_)};\n";
case StructType:
final this_ = this as StructType;
@ -247,14 +249,14 @@ extension on CType {
String dartAllocateStatements(String variableName) {
switch (this.runtimeType) {
case FundamentalType:
return "${dartType} ${variableName};\n";
return " ${dartType} ${variableName};\n";
case StructType:
case UnionType:
return """
final ${variableName}Pointer = calloc<$dartType>();
final ${dartType} ${variableName} = ${variableName}Pointer.ref;
""";
final ${variableName}Pointer = calloc<$dartType>();
final ${dartType} ${variableName} = ${variableName}Pointer.ref;
""";
}
throw Exception("Not implemented for ${this.runtimeType}");
@ -557,7 +559,7 @@ extension on CompositeType {
}
extension on FunctionType {
String dartCallCode({bool isLeaf: false}) {
String dartCallCode({bool isLeaf = false}) {
final a = ArgumentValueAssigner();
final assignValues = arguments.assignValueStatements(a);
final argumentFrees = arguments.dartFreeStatements();
@ -583,24 +585,24 @@ extension on FunctionType {
final namePostfix = isLeaf ? "Leaf" : "";
return """
final $dartName$namePostfix =
ffiTestFunctions.lookupFunction<$dartCType, $dartType>(
"$cName"${isLeaf ? ", isLeaf:true" : ""});
${reason.makeDartDocComment()}
void $dartTestName$namePostfix() {
${arguments.dartAllocateStatements()}
final $dartName$namePostfix =
ffiTestFunctions.lookupFunction<$dartCType, $dartType>(
"$cName"${isLeaf ? ", isLeaf:true" : ""});
${assignValues}
${reason.makeDartDocComment()}
void $dartTestName$namePostfix() {
${arguments.dartAllocateStatements()}
${assignValues}
final result = $dartName$namePostfix($argumentNames);
final result = $dartName$namePostfix($argumentNames);
print("result = \$result");
print("result = \$result");
$expects
$expects
$argumentFrees
}
$argumentFrees
}
""";
}
@ -626,27 +628,27 @@ extension on FunctionType {
// Sum all input values.
buildReturnValue = """
$returnValueType result = 0;
$returnValueType result = 0;
${arguments.addToResultStatements('${dartName}_')}
""";
${arguments.addToResultStatements('${dartName}_')}
""";
assignReturnGlobal = "${dartName}Result = $result;";
break;
case TestType.structReturn:
// Allocate a struct.
buildReturnValue = """
final resultPointer = calloc<${returnValue.dartType}>();
final result = resultPointer.ref;
final resultPointer = calloc<${returnValue.dartType}>();
final result = resultPointer.ref;
${arguments.copyValueStatements("${dartName}_", "result.")}
""";
${arguments.copyValueStatements("${dartName}_", "result.")}
""";
assignReturnGlobal = "${dartName}ResultPointer = resultPointer;";
structsAsPointers = true;
break;
case TestType.structReturnArgument:
buildReturnValue = """
${returnValue.cType} result = ${dartName}_${structReturnArgument.name};
""";
${returnValue.cType} result = ${dartName}_${structReturnArgument.name};
""";
assignReturnGlobal = "${dartName}Result = result;";
break;
}
@ -654,7 +656,7 @@ extension on FunctionType {
final globals = arguments.dartAllocateZeroStatements("${dartName}_");
final copyToGlobals =
arguments.map((a) => '${dartName}_${a.name} = ${a.name};').join("\n");
arguments.map((a) => '${dartName}_${a.name} = ${a.name};').join("\n ");
// Simulate assigning values the same way as in C, so that we know what the
// final return value should be.
@ -690,58 +692,58 @@ extension on FunctionType {
}
return """
typedef ${cName}Type = $dartCType;
typedef ${cName}Type = $dartCType;
// Global variables to be able to test inputs after callback returned.
$globals
// Global variables to be able to test inputs after callback returned.
$globals
// Result variable also global, so we can delete it after the callback.
${returnValue.dartAllocateZeroStatements("${dartName}Result", structsAsPointers: structsAsPointers)}
// Result variable also global, so we can delete it after the callback.
${returnValue.dartAllocateZeroStatements("${dartName}Result", structsAsPointers: structsAsPointers)}
${returnValue.dartType} ${dartName}CalculateResult() {
$buildReturnValue
${returnValue.dartType} ${dartName}CalculateResult() {
$buildReturnValue
$assignReturnGlobal
$assignReturnGlobal
return $result;
}
return $result;
}
${reason.makeDartDocComment()}
${returnValue.dartType} $dartName($argumentss) {
print("$dartName($prints)");
${reason.makeDartDocComment()}
${returnValue.dartType} $dartName($argumentss) {
print("$dartName($prints)");
// In legacy mode, possibly return null.
$returnNull
// In legacy mode, possibly return null.
$returnNull
// In both nnbd and legacy mode, possibly throw.
if (${arguments.firstArgumentName()} == $throwExceptionValue ||
${arguments.firstArgumentName()} == $returnNullValue) {
print("throwing!");
throw Exception("$cName throwing on purpose!");
}
// In both nnbd and legacy mode, possibly throw.
if (${arguments.firstArgumentName()} == $throwExceptionValue ||
${arguments.firstArgumentName()} == $returnNullValue) {
print("throwing!");
throw Exception("$cName throwing on purpose!");
}
$copyToGlobals
$copyToGlobals
final result = ${dartName}CalculateResult();
final result = ${dartName}CalculateResult();
print(\"result = \$result\");
print(\"result = \$result\");
return result;
}
return result;
}
void ${dartName}AfterCallback() {
$afterCallbackFrees
void ${dartName}AfterCallback() {
$afterCallbackFrees
final result = ${dartName}CalculateResult();
final result = ${dartName}CalculateResult();
print(\"after callback result = \$result\");
print(\"after callback result = \$result\");
$afterCallbackExpects
$afterCallbackExpects
$afterCallbackFrees
}
$afterCallbackFrees
}
""";
""";
}
String get dartCallbackTestConstructor {
@ -759,10 +761,10 @@ extension on FunctionType {
}
}
return """
CallbackTest.withCheck("$cName",
Pointer.fromFunction<${cName}Type>($dartName$exceptionalReturn),
${dartName}AfterCallback),
""";
CallbackTest.withCheck("$cName",
Pointer.fromFunction<${cName}Type>($dartName$exceptionalReturn),
${dartName}AfterCallback),
""";
}
String get cCallCode {
@ -796,21 +798,41 @@ extension on FunctionType {
break;
}
final argumentss =
arguments.map((e) => "${e.type.cType} ${e.name}").join(", ");
final argumentss = [
for (final argument in arguments.take(varArgsIndex ?? arguments.length))
"${argument.type.cType} ${argument.name}",
if (varArgsIndex != null) "...",
].join(", ");
final varArgUnpackArguments = [
for (final argument in arguments.skip(varArgsIndex ?? arguments.length))
" ${argument.type.cType} ${argument.name} = va_arg(var_args, ${argument.type.cType});",
].join("\n");
String varArgsUnpack = '';
if (varArgsIndex != null) {
varArgsUnpack = """
va_list var_args;
va_start(var_args, ${arguments[varArgsIndex! - 1].name});
$varArgUnpackArguments
va_end(var_args);
""";
}
return """
// Used for testing structs and unions by value.
${reason.makeCComment()}
DART_EXPORT ${returnValue.cType} $cName($argumentss) {
std::cout << \"$cName\" ${arguments.coutExpression()} << \"\\n\";
// Used for testing structs and unions by value.
${reason.makeCComment()}
DART_EXPORT ${returnValue.cType} $cName($argumentss) {
$varArgsUnpack
$body
std::cout << \"$cName\" ${arguments.coutExpression()} << \"\\n\";
${returnValue.coutStatement("result")}
$body
$returnStatement
}
${returnValue.coutStatement("result")}
$returnStatement
}
""";
}
@ -820,8 +842,11 @@ extension on FunctionType {
final argumentAllocations = arguments.cAllocateStatements();
final assignValues = arguments.assignValueStatements(a);
final argumentss =
arguments.map((e) => "${e.type.cType} ${e.name}").join(", ");
final argumentss = [
for (final argument in arguments.take(varArgsIndex ?? arguments.length))
"${argument.type.cType} ${argument.name}",
if (varArgsIndex != null) "...",
].join(", ");
final argumentNames = arguments.map((e) => e.name).join(", ");
@ -946,17 +971,24 @@ Future<void> writeDartCompounds() async {
}));
}
headerDartCallTest({required bool isNnbd, required int copyrightYear}) {
headerDartCallTest({
required bool isNnbd,
required int copyrightYear,
String vmFlags = '',
}) {
final dartVersion = isNnbd ? '' : dart2dot9;
if (vmFlags.length != 0 && !vmFlags.endsWith(' ')) {
vmFlags += ' ';
}
return """
${headerCommon(copyrightYear: copyrightYear)}
//
// SharedObjects=ffi_test_functions
// VMOptions=
// VMOptions=--deterministic --optimization-counter-threshold=90
// VMOptions=--use-slow-path
// VMOptions=--use-slow-path --stacktrace-every=100
// VMOptions=${vmFlags.trim()}
// VMOptions=$vmFlags--deterministic --optimization-counter-threshold=90
// VMOptions=$vmFlags--use-slow-path
// VMOptions=$vmFlags--use-slow-path --stacktrace-every=100
$dartVersion
@ -975,51 +1007,83 @@ final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
""";
}
Future<void> writeDartCallTest(String nameSuffix, List<FunctionType> functions,
{required bool isLeaf}) async {
await Future.wait([true, false].map((isNnbd) async {
Future<void> writeDartCallTest(
String nameSuffix,
List<FunctionType> functions, {
required bool isLeaf,
required bool isVarArgs,
}) async {
await Future.wait([
true,
if (!isVarArgs) false,
].map((isNnbd) async {
final StringBuffer buffer = StringBuffer();
buffer.write(headerDartCallTest(
isNnbd: isNnbd, copyrightYear: isLeaf ? 2021 : 2020));
isNnbd: isNnbd,
copyrightYear: isVarArgs
? 2023
: isLeaf
? 2021
: 2020,
vmFlags: isVarArgs ? '--enable-experiment=records' : '',
));
final suffix = isLeaf ? 'Leaf' : '';
buffer.write("""
void main() {
for (int i = 0; i < 100; ++i) {
${functions.map((e) => "${e.dartTestName}$suffix();").join("\n")}
}
}
""");
void main() {
for (int i = 0; i < 100; ++i) {
${functions.map((e) => "${e.dartTestName}$suffix();").join("\n ")}
}
}
""");
buffer.writeAll(functions.map((e) => e.dartCallCode(isLeaf: isLeaf)));
final path =
callTestPath(isNnbd: isNnbd, isLeaf: isLeaf, nameSuffix: nameSuffix);
final path = callTestPath(
isNnbd: isNnbd,
isLeaf: isLeaf,
nameSuffix: nameSuffix,
isVarArgs: isVarArgs);
await File(path).writeAsString(buffer.toString());
await runProcess("dart", ["format", path]);
if (!isVarArgs) {
// TODO(https://dartbug.com/50798): Dart format support for records.
await runProcess("dart", ["format", path]);
}
}));
}
String callTestPath(
{required bool isNnbd, required bool isLeaf, String nameSuffix = ''}) {
String callTestPath({
required bool isNnbd,
required bool isLeaf,
String nameSuffix = '',
required bool isVarArgs,
}) {
final folder = isNnbd ? 'ffi' : 'ffi_2';
final suffix = nameSuffix + (isLeaf ? '_leaf' : '');
final baseName = isVarArgs ? 'varargs' : 'structs_by_value';
final suffix = '$nameSuffix${isLeaf ? '_leaf' : ''}';
return Platform.script
.resolve(
"../../$folder/function_structs_by_value_generated${suffix}_test.dart")
"../../$folder/function_${baseName}_generated${suffix}_test.dart")
.toFilePath();
}
headerDartCallbackTest({required bool isNnbd, required int copyrightYear}) {
headerDartCallbackTest({
required bool isNnbd,
required int copyrightYear,
String vmFlags = '',
}) {
final dartVersion = isNnbd ? '' : dart2dot9;
if (vmFlags.length != 0 && !vmFlags.endsWith(' ')) {
vmFlags += ' ';
}
return """
${headerCommon(copyrightYear: copyrightYear)}
//
// SharedObjects=ffi_test_functions
// VMOptions=
// VMOptions=--deterministic --optimization-counter-threshold=20
// VMOptions=--use-slow-path
// VMOptions=--use-slow-path --stacktrace-every=100
// VMOptions=${vmFlags.trim()}
// VMOptions=$vmFlags--deterministic --optimization-counter-threshold=20
// VMOptions=$vmFlags--use-slow-path
// VMOptions=$vmFlags--use-slow-path --stacktrace-every=100
$dartVersion
@ -1049,30 +1113,44 @@ void main() {
""";
}
Future<void> writeDartCallbackTest() async {
await Future.wait([true, false].map((isNnbd) async {
Future<void> writeDartCallbackTest(
List<FunctionType> functions, {
required bool isVarArgs,
}) async {
await Future.wait([
true,
if (!isVarArgs) false,
].map((isNnbd) async {
final StringBuffer buffer = StringBuffer();
buffer.write(headerDartCallbackTest(isNnbd: isNnbd, copyrightYear: 2020));
buffer.write(headerDartCallbackTest(
isNnbd: isNnbd,
copyrightYear: isVarArgs ? 2023 : 2020,
vmFlags: isVarArgs ? '--enable-experiment=records' : '',
));
buffer.write("""
final testCases = [
${functions.map((e) => e.dartCallbackTestConstructor).join("\n")}
];
""");
final testCases = [
${functions.map((e) => e.dartCallbackTestConstructor).join("\n")}
];
""");
buffer.writeAll(functions.map((e) => e.dartCallbackCode(isNnbd: isNnbd)));
final path = callbackTestPath(isNnbd: isNnbd);
final path = callbackTestPath(isNnbd: isNnbd, isVarArgs: isVarArgs);
await File(path).writeAsString(buffer.toString());
await runProcess("dart", ["format", path]);
if (!isVarArgs) {
// TODO(https://dartbug.com/50798): Dart format support for records.
await runProcess("dart", ["format", path]);
}
}));
}
String callbackTestPath({required bool isNnbd}) {
String callbackTestPath({required bool isNnbd, required bool isVarArgs}) {
final folder = isNnbd ? "ffi" : "ffi_2";
final baseName = isVarArgs ? "varargs" : "structs_by_value";
return Platform.script
.resolve(
"../../$folder/function_callbacks_structs_by_value_generated_test.dart")
"../../$folder/function_callbacks_${baseName}_generated_test.dart")
.toFilePath();
}
@ -1080,6 +1158,7 @@ headerC({required int copyrightYear}) {
return """
${headerCommon(copyrightYear: copyrightYear)}
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/types.h>
@ -1125,6 +1204,8 @@ Future<void> writeC() async {
buffer.writeAll(compounds.map((e) => e.cDefinition));
buffer.writeAll(functions.map((e) => e.cCallCode));
buffer.writeAll(functions.map((e) => e.cCallbackCode));
buffer.writeAll(functionsVarArgs.map((e) => e.cCallCode));
buffer.writeAll(functionsVarArgs.map((e) => e.cCallbackCode));
buffer.write(footerC);
@ -1143,13 +1224,14 @@ Generates structs by value tests.
Generates:
- $ccPath
- ${compoundsPath(isNnbd: true)}
- ${callbackTestPath(isNnbd: true)}
- ${callTestPath(isNnbd: true, isLeaf: false)}
- ${callTestPath(isNnbd: true, isLeaf: true)}
- ${callbackTestPath(isNnbd: true, isVarArgs: false)}
- ${callTestPath(isNnbd: true, isLeaf: false, isVarArgs: false)}
- ${callTestPath(isNnbd: true, isLeaf: true, isVarArgs: false)}
- ${callTestPath(isNnbd: true, isLeaf: true, isVarArgs: true)}
- ${compoundsPath(isNnbd: false)}
- ${callbackTestPath(isNnbd: false)}
- ${callTestPath(isNnbd: false, isLeaf: false)}
- ${callTestPath(isNnbd: false, isLeaf: true)}
- ${callbackTestPath(isNnbd: false, isVarArgs: false)}
- ${callTestPath(isNnbd: false, isLeaf: false, isVarArgs: false)}
- ${callTestPath(isNnbd: false, isLeaf: true, isVarArgs: false)}
""");
}
@ -1162,11 +1244,16 @@ void main(List<String> arguments) async {
await Future.wait([
writeDartCompounds(),
for (bool isLeaf in [false, true]) ...[
writeDartCallTest('_args', functionsStructArguments, isLeaf: isLeaf),
writeDartCallTest('_ret', functionsStructReturn, isLeaf: isLeaf),
writeDartCallTest('_ret_arg', functionsReturnArgument, isLeaf: isLeaf),
writeDartCallTest('_args', functionsStructArguments,
isLeaf: isLeaf, isVarArgs: false),
writeDartCallTest('_ret', functionsStructReturn,
isLeaf: isLeaf, isVarArgs: false),
writeDartCallTest('_ret_arg', functionsReturnArgument,
isLeaf: isLeaf, isVarArgs: false),
writeDartCallTest('', functionsVarArgs, isLeaf: isLeaf, isVarArgs: true),
],
writeDartCallbackTest(),
writeDartCallbackTest(functions, isVarArgs: false),
writeDartCallbackTest(functionsVarArgs, isVarArgs: true),
writeC(),
]);
}

View file

@ -0,0 +1,34 @@
// Copyright (c) 2023, 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.
// SharedObjects=ffi_test_functions
// VMOptions=--enable-experiment=records
import 'dylib_utils.dart';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
void main() {
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
// Try to lookup a symbol that exists, but don't use it.
// With both valid and invalid C signatures.
print(ffiTestFunctions.lookupFunction<
Void Function(Pointer<Utf8>, VarArgs<(Int32, Int32)>),
void Function(Pointer<Utf8>, int, int)>('PassObjectToC'));
// Error: a named record field.
print(ffiTestFunctions.lookupFunction< //# 1: compile-time error
Void Function(Pointer<Utf8>, VarArgs<(Int32, Int32, {Int32 foo})>), //# 1: compile-time error
void Function(Pointer<Utf8>, int, int)>('PassObjectToC')); //# 1: compile-time error
// Error: VarArgs not last.
print(ffiTestFunctions.lookupFunction< //# 2: compile-time error
Void Function(Pointer<Utf8>, VarArgs<(Int32, Int32)>, Int32), //# 2: compile-time error
void Function(Pointer<Utf8>, int, int, int)>('PassObjectToC')); //# 2: compile-time error
}

View file

@ -7,6 +7,7 @@
import 'dart:collection';
import 'dart:io';
import 'package:_fe_analyzer_shared/src/sdk/allowed_experiments.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
@ -18,6 +19,7 @@ import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/overlay_file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/util/comment.dart';
import 'package:path/path.dart' as path;
@ -84,6 +86,13 @@ Future<bool> validateLibrary(Directory dir) async {
return validDocs;
}
final Future<AllowedExperiments> allowedExperiments = () async {
final allowedExperimentsFile =
File('sdk/lib/_internal/allowed_experiments.json');
final contents = await allowedExperimentsFile.readAsString();
return parseAllowedExperiments(contents);
}();
Future<bool> verifyFile(
AnalysisHelper analysisHelper,
String coreLibName,
@ -93,7 +102,10 @@ Future<bool> verifyFile(
final text = file.readAsStringSync();
var parseResult = parseString(
content: text,
featureSet: FeatureSet.latestLanguageVersion(),
featureSet: FeatureSet.fromEnableFlags2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: (await allowedExperiments).sdkDefaultExperiments,
),
path: file.path,
throwIfDiagnostics: false,
);
@ -486,7 +498,7 @@ class AnalysisHelper {
final String libraryName;
final resourceProvider =
OverlayResourceProvider(PhysicalResourceProvider.INSTANCE);
final pathRoot = Platform.isWindows ? r'c:\' : '/';
final pathRoot = Directory('sdk/lib/').absolute.path;
late AnalysisContextCollection collection;
int index = 0;