[PATCH v11 0/6] MR9265: crt: Run MSVC constructors and destructors
There are several motivations to enable ctor/dtor support: 1. ASan. This is not just about my wine-asan work, this is important for @bernhardu's PE asan work as well. The compiler generates ctors that poisons memory around global variables so misuses of them can be caught. This is the same both of our implementations of ASan. 2. Building third-party codebases against wine's CRT. This is not necessary for mingw-gcc or llvm-mingw, but the MSVC frontend of clang is another option for cross-compiling to Windows. However right now that requires Windows SDK. Bringing wine CRT to the point of being functional enough will give us another option. Could be useful for things like Proton. 3. (Controversial) Many dlls perform init/deinit tasks in `DllMain`. Some of them might benefit from a conversion to ctor/dtor. Since nothing in wine uses ctor/dtor right now, this change shouldn't have any impact by itself. -- v11: crt: Run MSVC constructors and destructors. ntdll: Add initterm implementation to the import library. makedep: Allow linking a module’s own static library object files into the module itself. makedep: Add ntdll to default imports too when -nodefaultlibs is used and ntdll is imported. winegcc: Merge .CRT sections for windows targets. winebuild: Generate start and end symbols for .CRT sections. https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui@codeweavers.com> --- tools/winebuild/build.h | 1 + tools/winebuild/spec16.c | 1 + tools/winebuild/spec32.c | 27 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/tools/winebuild/build.h b/tools/winebuild/build.h index 3acf987aab2..0cabc4648db 100644 --- a/tools/winebuild/build.h +++ b/tools/winebuild/build.h @@ -301,6 +301,7 @@ extern void output_imports( DLLSPEC *spec ); extern void output_import_lib( DLLSPEC *spec, struct strarray files ); extern void output_static_lib( const char *output_name, struct strarray files, int create ); extern void output_exports( DLLSPEC *spec ); +extern void output_crt_sections(void); extern int load_res32_file( const char *name, DLLSPEC *spec ); extern void output_resources( DLLSPEC *spec ); extern void output_bin_resources( DLLSPEC *spec, unsigned int start_rva ); diff --git a/tools/winebuild/spec16.c b/tools/winebuild/spec16.c index face1c551d9..d16e97eabe6 100644 --- a/tools/winebuild/spec16.c +++ b/tools/winebuild/spec16.c @@ -790,6 +790,7 @@ void output_spec16_file( DLLSPEC *spec16 ) output_stubs( spec16 ); output_exports( spec32 ); output_imports( spec16 ); + output_crt_sections(); if (!strcmp( spec16->dll_name, "kernel" )) output_asm_relays16(); if (needs_get_pc_thunk) output_get_pc_thunk(); if (spec16->main_module) diff --git a/tools/winebuild/spec32.c b/tools/winebuild/spec32.c index 7099e82e0f6..ef280b7f51e 100644 --- a/tools/winebuild/spec32.c +++ b/tools/winebuild/spec32.c @@ -703,6 +703,32 @@ static void output_load_config(void) } +void output_crt_sections(void) +{ + /* Generate the start/end symbols for .CRT$X?? sections. The start symbol is put into + * .CRT$X?A, the end .CRT$X?Z. Since the linker sort .CRT$X?? sections by name, these symbols + * will end up at the right location.*/ + static const char sections[] = "ict"; + int i; + for (i = 0; sections[i]; i++) + { + char *symbol_name = strmake( "__x%c_a", sections[i] ); + output( "\t.section .CRT$X%cA\n", toupper( sections[i] ) ); + output( "\t.globl %s\n", asm_name( symbol_name ) ); + output( "\t.balign %u\n", get_ptr_size() ); + output( "%s:\n", asm_name( symbol_name ) ); + output( "\t%s 0\n", get_asm_ptr_keyword() ); + + symbol_name = strmake( "__x%c_z", sections[i] ); + output( "\t.section .CRT$X%cZ\n", toupper( sections[i] ) ); + output( "\t.globl %s\n", asm_name( symbol_name ) ); + output( "\t.balign %u\n", get_ptr_size() ); + output( "%s:\n", asm_name( symbol_name ) ); + output( "\t%s 0\n", get_asm_ptr_keyword() ); + } +} + + /******************************************************************* * output_module * @@ -841,6 +867,7 @@ void output_spec32_file( DLLSPEC *spec ) output_imports( spec ); if (needs_get_pc_thunk) output_get_pc_thunk(); output_load_config(); + output_crt_sections(); output_resources( spec ); output_gnu_stack_note(); close_output_file(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui@codeweavers.com> On mingw targets this is already done by ld's linker script. --- tools/winegcc/winegcc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/winegcc/winegcc.c b/tools/winegcc/winegcc.c index f8651804fa2..f5eb2a81d83 100644 --- a/tools/winegcc/winegcc.c +++ b/tools/winegcc/winegcc.c @@ -628,6 +628,9 @@ static struct strarray get_link_args( const char *output_name ) strarray_add( &link_args, strmake( "-Wl,-filealign:%s,-align:%s,-driver", file_align, section_align )); + /* Merge .CRT sections into .rdata */ + strarray_add( &link_args, "-Wl,-merge:.CRT=.rdata" ); + strarray_addall( &link_args, flags ); return link_args; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Jacek Caban <jacek@codeweavers.com> --- tools/makedep.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/makedep.c b/tools/makedep.c index efc7517f0c8..a26fa44834e 100644 --- a/tools/makedep.c +++ b/tools/makedep.c @@ -2278,6 +2278,7 @@ static struct strarray get_default_imports( const struct makefile *make, struct { STRARRAY_FOR_EACH( imp, &imports ) if (!strcmp( imp, "winecrt0" )) return ret; strarray_add( &ret, "winecrt0" ); + if (strarray_exists( imports, "ntdll" )) strarray_add( &ret, "ntdll" ); if (compiler_rt) strarray_add( &ret, compiler_rt ); return ret; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Jacek Caban <jacek@codeweavers.com> --- tools/makedep.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/makedep.c b/tools/makedep.c index a26fa44834e..dcfaebe9ff6 100644 --- a/tools/makedep.c +++ b/tools/makedep.c @@ -2322,6 +2322,9 @@ static struct strarray add_import_libs( const struct makefile *make, struct stra /* add crt import lib only when adding the default imports libs */ if (is_crt_module( name ) && type != IMPORT_TYPE_DEFAULT) continue; + /* when importing itself, skip the import and add object files instead */ + if (make->importlib && !strcmp( make->importlib, name )) continue; + if (name[0] == '-') { switch (name[1]) @@ -3363,7 +3366,12 @@ static void output_source_one_arch( struct makefile *make, struct incl_file *sou if (source->file->flags & FLAG_C_UNIX) strarray_add( &make->unixobj_files, obj_name ); else if (source->file->flags & FLAG_C_IMPLIB) + { strarray_add( &make->implib_files[arch], obj_name ); + /* if we're importing ourselves, add importlib object files to object_files as well */ + if (make->importlib && strarray_exists( make->imports, make->importlib )) + strarray_add( &make->object_files[arch], obj_name ); + } else if (!(source->file->flags & FLAG_TESTDLL)) strarray_add( &make->object_files[arch], obj_name ); else -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui@codeweavers.com> --- dlls/ntdll/Makefile.in | 2 ++ dlls/ntdll/initterm.c | 44 ++++++++++++++++++++++++++++++++++++++++ dlls/ntdll/initterm_e.c | 45 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 dlls/ntdll/initterm.c create mode 100644 dlls/ntdll/initterm_e.c diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in index 3c0dfa7a895..6649b638dcd 100644 --- a/dlls/ntdll/Makefile.in +++ b/dlls/ntdll/Makefile.in @@ -21,6 +21,8 @@ SOURCES = \ exception.c \ handletable.c \ heap.c \ + initterm.c \ + initterm_e.c \ large_int.c \ loader.c \ locale.c \ diff --git a/dlls/ntdll/initterm.c b/dlls/ntdll/initterm.c new file mode 100644 index 00000000000..93ba25b91d6 --- /dev/null +++ b/dlls/ntdll/initterm.c @@ -0,0 +1,44 @@ +/* + * _initterm fallback implementation + * + * Copyright 2025 Yuxuan Shui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <process.h> +#include "minwindef.h" + +#include "wine/asm.h" + +#if 0 +#pragma makedep implib +#endif + +/* This is a fallback version of msvcrt's _initterm. Since _initterm is used in winecrt0, + * if the final object is linked with a version of msvcrt without the _initterm function, this + * version will be used. */ +void CDECL _initterm( _PVFV *start,_PVFV *end ) +{ + _PVFV *current = start; + + while (current < end) + { + if (*current) (**current)(); + current++; + } +} + +__ASM_GLOBAL_IMPORT(_initterm) diff --git a/dlls/ntdll/initterm_e.c b/dlls/ntdll/initterm_e.c new file mode 100644 index 00000000000..99d899c81cf --- /dev/null +++ b/dlls/ntdll/initterm_e.c @@ -0,0 +1,45 @@ +/* + * _initterm_e fallback implementation + * + * Copyright 2025 Yuxuan Shui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <process.h> +#include "minwindef.h" + +#include "wine/asm.h" + +#if 0 +#pragma makedep implib +#endif + +/* This is a fallback version of msvcrt's _initterm_e. Since _initterm_e is used in winecrt0, + * if the final object is linked with a version of msvcrt without the _initterm_e function, this + * version will be used. */ +int CDECL _initterm_e( _PIFV *table, _PIFV *end ) +{ + int res = 0; + + while (!res && table < end) + { + if (*table) res = (**table)(); + table++; + } + return res; +} + +__ASM_GLOBAL_IMPORT(_initterm_e) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
From: Yuxuan Shui <yshui@codeweavers.com> --- dlls/msvcrt/crt_init.h | 49 ++++++++++++++++++++++++++++++++ dlls/msvcrt/crt_main.c | 4 +-- dlls/msvcrt/crt_wmain.c | 4 +-- dlls/msvcrt/data.c | 5 ++++ dlls/ntdll/Makefile.in | 2 +- dlls/winecrt0/crt_dllmain.c | 21 +++++++++++++- include/msvcrt/corecrt_startup.h | 1 + 7 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 dlls/msvcrt/crt_init.h diff --git a/dlls/msvcrt/crt_init.h b/dlls/msvcrt/crt_init.h new file mode 100644 index 00000000000..5128f133b6a --- /dev/null +++ b/dlls/msvcrt/crt_init.h @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Yuxuan Shui for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep implib +#endif + +#include <process.h> +#include <stdlib.h> +#include "minwindef.h" + +extern _PIFV __xi_a[]; +extern _PIFV __xi_z[]; +extern _PVFV __xc_a[]; +extern _PVFV __xc_z[]; +extern _PVFV __xt_a[]; +extern _PVFV __xt_z[]; + +static __cdecl void do_global_dtors(void) +{ + _initterm(__xt_a, __xt_z); +} + +static void do_global_ctors(void) +{ + if (_initterm_e(__xi_a, __xi_z) != 0) return; + _initterm(__xc_a, __xc_z); + +#ifdef _UCRT + _crt_atexit(do_global_dtors); +#else + _onexit((_onexit_t)do_global_dtors); +#endif +} diff --git a/dlls/msvcrt/crt_main.c b/dlls/msvcrt/crt_main.c index eb785e30bb0..fe900295cf1 100644 --- a/dlls/msvcrt/crt_main.c +++ b/dlls/msvcrt/crt_main.c @@ -23,12 +23,11 @@ #endif #include <stdarg.h> -#include <stdlib.h> -#include <process.h> #include "windef.h" #include "winbase.h" #include "winternl.h" +#include "crt_init.h" int __cdecl main(int argc, char **argv, char **env); @@ -54,6 +53,7 @@ int __cdecl mainCRTStartup(void) __getmainargs(&argc, &argv, &env, 0, &new_mode); #endif _set_app_type(get_nt_header()->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI ? _crt_gui_app : _crt_console_app); + do_global_ctors(); ret = main(argc, argv, env); diff --git a/dlls/msvcrt/crt_wmain.c b/dlls/msvcrt/crt_wmain.c index 79755d1a6e9..c3ea3fffd6f 100644 --- a/dlls/msvcrt/crt_wmain.c +++ b/dlls/msvcrt/crt_wmain.c @@ -23,12 +23,11 @@ #endif #include <stdarg.h> -#include <stdlib.h> -#include <process.h> #include "windef.h" #include "winbase.h" #include "winternl.h" +#include "crt_init.h" int __cdecl wmain(int argc, WCHAR **argv, WCHAR **env); @@ -54,6 +53,7 @@ int __cdecl wmainCRTStartup(void) __wgetmainargs(&argc, &argv, &env, 0, &new_mode); #endif _set_app_type(get_nt_header()->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI ? _crt_gui_app : _crt_console_app); + do_global_ctors(); ret = wmain(argc, argv, env); diff --git a/dlls/msvcrt/data.c b/dlls/msvcrt/data.c index 6e764e52eae..5b3d78009e9 100644 --- a/dlls/msvcrt/data.c +++ b/dlls/msvcrt/data.c @@ -22,6 +22,7 @@ #include <math.h> #include "msvcrt.h" #include <winnls.h> +#include "wine/asm.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(msvcrt); @@ -557,6 +558,8 @@ void CDECL _initterm(_INITTERMFUN *start,_INITTERMFUN *end) } } +__ASM_GLOBAL_IMPORT(_initterm) + /********************************************************************* * _initterm_e (MSVCRT.@) * @@ -581,6 +584,8 @@ int CDECL _initterm_e(_INITTERM_E_FN *table, _INITTERM_E_FN *end) return res; } +__ASM_GLOBAL_IMPORT(_initterm_e) + /********************************************************************* * __set_app_type (MSVCRT.@) */ diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in index 6649b638dcd..12f4e9117ac 100644 --- a/dlls/ntdll/Makefile.in +++ b/dlls/ntdll/Makefile.in @@ -2,7 +2,7 @@ EXTRADEFS = -D_NTSYSTEM_ -D_ACRTIMP= -DWINBASEAPI= MODULE = ntdll.dll UNIXLIB = ntdll.so IMPORTLIB = ntdll -IMPORTS = $(TOMCRYPT_PE_LIBS) $(MUSL_PE_LIBS) +IMPORTS = $(TOMCRYPT_PE_LIBS) $(MUSL_PE_LIBS) ntdll EXTRAINCL = $(TOMCRYPT_PE_CFLAGS) UNIX_CFLAGS = $(UNWIND_CFLAGS) $(HWLOC_CFLAGS) UNIX_LIBS = $(IOKIT_LIBS) $(COREFOUNDATION_LIBS) $(CORESERVICES_LIBS) $(RT_LIBS) $(PTHREAD_LIBS) $(UNWIND_LIBS) $(I386_LIBS) $(PROCSTAT_LIBS) $(HWLOC_LIBS) diff --git a/dlls/winecrt0/crt_dllmain.c b/dlls/winecrt0/crt_dllmain.c index 181760c884a..08a3336a9cc 100644 --- a/dlls/winecrt0/crt_dllmain.c +++ b/dlls/winecrt0/crt_dllmain.c @@ -22,12 +22,31 @@ #include <stdarg.h> #include <stdio.h> +#include <process.h> #include "windef.h" #include "winbase.h" +extern _PIFV __xi_a[]; +extern _PIFV __xi_z[]; +extern _PVFV __xc_a[]; +extern _PVFV __xc_z[]; +extern _PVFV __xt_a[]; +extern _PVFV __xt_z[]; + BOOL WINAPI DllMainCRTStartup( HINSTANCE inst, DWORD reason, void *reserved ) { - return DllMain( inst, reason, reserved ); + BOOL ret; + + if (reason == DLL_PROCESS_ATTACH) + { + if (_initterm_e( __xi_a, __xi_z ) != 0) return FALSE; + _initterm( __xc_a, __xc_z ); + } + + ret = DllMain( inst, reason, reserved ); + + if (reason == DLL_PROCESS_DETACH) _initterm( __xt_a, __xt_z ); + return ret; } #endif diff --git a/include/msvcrt/corecrt_startup.h b/include/msvcrt/corecrt_startup.h index c1c519da618..4094cb16150 100644 --- a/include/msvcrt/corecrt_startup.h +++ b/include/msvcrt/corecrt_startup.h @@ -26,6 +26,7 @@ typedef void (__cdecl *_PVFV)(void); typedef int (__cdecl *_PIFV)(void); typedef void (__cdecl *_PVFI)(int); +_ACRTIMP void __cdecl _initterm(_PVFV *, _PVFV *); _ACRTIMP int __cdecl _initterm_e(_PIFV *, _PIFV *); typedef struct _onexit_table_t { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
On Wed Jan 14 01:16:32 2026 +0000, Yuxuan Shui wrote:
changed this line in [version 11 of the diff](/wine/wine/-/merge_requests/9265/diffs?diff_id=237429&start_sha=25e6ed5ff438db98b1db77eb0e8fa1b6b0caa5ed#0719c2159befb363c9a867ff09219d50ac8dc799_38_36) Thanks, it makes sense to me now.
I've updated this MR to use your version. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_126886
This merge request was approved by Jacek Caban. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265
That ntdll import lib isn't very nice. I'm probably missing something, but why can't this go into winecrt0? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_127052
On Sat Jan 17 01:07:01 2026 +0000, Alexandre Julliard wrote:
That ntdll import lib isn't very nice. I'm probably missing something, but why can't this go into winecrt0? certain things has `libmsvcrt*.a` and `libwinecrt0.a` in reversed order during linking. for example:
``` tools/winegcc/winegcc -o dlls/msvcr70/tests/x86_64-windows/msvcr70_test.exe --wine-objdir . -b x86_64-w64-mingw32 \ dlls/msvcr70/tests/x86_64-windows/msvcr70.o dlls/msvcr70/tests/x86_64-windows/testlist.o \ -Wl,--debug-file,dlls/msvcr70/tests/x86_64-windows/msvcr70_test.exe.debug \ dlls/winecrt0/x86_64-windows/libwinecrt0.a dlls/msvcr70/x86_64-windows/libmsvcr70.a \ dlls/kernel32/x86_64-windows/libkernel32.a dlls/ntdll/x86_64-windows/libntdll.a ``` the result is `do_global_ctors` (in `crt_main.o` in `libmsvcr70.a`) can't find `_initterm_e`. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_127259
On Sat Jan 17 01:07:01 2026 +0000, Yuxuan Shui wrote:
certain things has `libmsvcrt*.a` and `libwinecrt0.a` in reversed order during linking. for example: ``` tools/winegcc/winegcc -o dlls/msvcr70/tests/x86_64-windows/msvcr70_test.exe --wine-objdir . -b x86_64-w64-mingw32 \ dlls/msvcr70/tests/x86_64-windows/msvcr70.o dlls/msvcr70/tests/x86_64-windows/testlist.o \
-Wl,--debug-file,dlls/msvcr70/tests/x86_64-windows/msvcr70_test.exe.debug \ dlls/winecrt0/x86_64-windows/libwinecrt0.a dlls/msvcr70/x86_64-windows/libmsvcr70.a \ dlls/kernel32/x86_64-windows/libkernel32.a dlls/ntdll/x86_64-windows/libntdll.a ``` the result is `do_global_ctors` (in `crt_main.o` in `libmsvcr70.a`) can't find `_initterm_e`. Sure, we'd need to provide a replacement `_initterm_e` in msvcr70. That doesn't seem a strong enough reason to move stuff to ntdll.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_127262
On Sat Jan 17 10:49:51 2026 +0000, Alexandre Julliard wrote:
Sure, we'd need to provide a replacement `_initterm_e` in msvcr70. That doesn't seem a strong enough reason to move stuff to ntdll. would you be open to the [`libwinecrtend.a` option](https://gitlab.winehq.org/wine/wine/-/merge_requests/9265/diffs?commit_id=e7...
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_127318
On Mon Jan 19 13:59:29 2026 +0000, Yuxuan Shui wrote:
would you be open to the [`libwinecrtend.a` option](https://gitlab.winehq.org/wine/wine/-/merge_requests/9265/diffs?commit_id=e7... I think @jacek's idea is to use the initterm/initterm_e implementations from msvcrt whenever possible. If we put the fallback in `winecrt0` then `winecrt0` will always used the alternative implementation.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_127319
`winecrt0` will always use the alternative implementation.
Or maybe not? I just realized I am not 100% clear on how the linker will treat symbols from within the same archive. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9265#note_127320
participants (5)
-
Alexandre Julliard (@julliard) -
Jacek Caban -
Jacek Caban (@jacek) -
Yuxuan Shui -
Yuxuan Shui (@yshui)