From 9aae918fcc2b53df64874378f0305e27eca07892 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Sat, 1 Apr 2023 14:51:41 -0500 Subject: [PATCH] added: starting work on using `dyncallback` --- compiler/build.sh | 12 ++- compiler/src/wasm_emit.c | 50 +++++---- compiler/src/wasm_runtime.c | 2 + shared/include/dyncall/dyncall_args.h | 78 ++++++++++++++ shared/include/dyncall/dyncall_callback.h | 58 +++++++++++ shared/include/dyncall/dyncall_value.h | 98 ++++++++++++++++++ .../lib/linux_x86_64/lib/libdyncallback_s.a | Bin 0 -> 14726 bytes 7 files changed, 270 insertions(+), 28 deletions(-) create mode 100644 shared/include/dyncall/dyncall_args.h create mode 100644 shared/include/dyncall/dyncall_callback.h create mode 100644 shared/include/dyncall/dyncall_value.h create mode 100644 shared/lib/linux_x86_64/lib/libdyncallback_s.a diff --git a/compiler/build.sh b/compiler/build.sh index ed80b613..7510b749 100755 --- a/compiler/build.sh +++ b/compiler/build.sh @@ -2,11 +2,14 @@ . ../settings.sh +# Enable Dynamic call +USE_DYNCALL=1 + # Temporary flag ENABLE_DEBUG_INFO=1 C_FILES="onyx astnodes builtins checker clone doc entities errors lex parser symres types utils wasm_emit wasm_runtime " -LIBS="-L$CORE_DIR/lib -l$RUNTIME_LIBRARY -Wl,-rpath=$CORE_DIR/lib:./ -lpthread -ldl -lm ../shared/lib/linux_$ARCH/lib/libdyncall_s.a" +LIBS="-L$CORE_DIR/lib -l$RUNTIME_LIBRARY -Wl,-rpath=$CORE_DIR/lib:./ -lpthread -ldl -lm" INCLUDES="-I./include -I../shared/include -I../shared/include/dyncall" WARNINGS='-Wimplicit -Wmisleading-indentation -Wparentheses -Wsequence-point -Wreturn-type -Wshift-negative-value -Wunused-but-set-parameter -Wunused-but-set-variable -Wunused-function -Wunused-label -Wmaybe-uninitialized -Wsign-compare -Wstrict-overflow -Wduplicated-branches -Wduplicated-cond -Wtrigraphs -Waddress -Wlogical-op' @@ -25,7 +28,12 @@ if [ "$ENABLE_DEBUG_INFO" = "1" ]; then FLAGS="$FLAGS -DENABLE_DEBUG_INFO" fi -FLAGS="$FLAGS -DENABLE_RUN_WITH_WASMER -DUSE_DYNCALL" +FLAGS="$FLAGS -DENABLE_RUN_WITH_WASMER" + +if [ "$USE_DYNCALL" = "1" ]; then + LIBS="$LIBS ../shared/lib/linux_$ARCH/lib/libdyncall_s.a ../shared/lib/linux_$ARCH/lib/libdyncallback_s.a" + FLAGS="$FLAGS -DUSE_DYNCALL" +fi sudo mkdir -p "$BIN_DIR" diff --git a/compiler/src/wasm_emit.c b/compiler/src/wasm_emit.c index 4841b8a2..2c377c02 100644 --- a/compiler/src/wasm_emit.c +++ b/compiler/src/wasm_emit.c @@ -4121,38 +4121,37 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { debug_end_function(mod); } -static char encode_type_as_dyncall_symbol(Type *t) { +static void encode_type_as_dyncall_symbol(char *out, Type *t) { if (type_struct_is_just_one_basic_value(t)) { Type *inner = type_struct_is_just_one_basic_value(t); - return encode_type_as_dyncall_symbol(inner); + encode_type_as_dyncall_symbol(out, inner); } - if (t->kind == Type_Kind_Slice) return 's'; - if (t->kind == Type_Kind_Pointer) return 'p'; - if (t->kind == Type_Kind_MultiPointer) return 'p'; - if (t->kind == Type_Kind_Enum) return encode_type_as_dyncall_symbol(t->Enum.backing); - if (t->kind == Type_Kind_Basic) { + else if (t->kind == Type_Kind_Slice) strncat(out, "s", 64); + else if (t->kind == Type_Kind_Pointer) strncat(out, "p", 64); + else if (t->kind == Type_Kind_MultiPointer) strncat(out, "p", 64); + else if (t->kind == Type_Kind_Enum) encode_type_as_dyncall_symbol(out, t->Enum.backing); + else if (t->kind == Type_Kind_Basic) { TypeBasic* basic = &t->Basic; - if (basic->flags & Basic_Flag_Boolean) return 'i'; - if (basic->flags & Basic_Flag_Integer) { - if (basic->size <= 4) return 'i'; - if (basic->size == 8) return 'l'; + if (basic->flags & Basic_Flag_Boolean) strncat(out, "i", 64); + else if (basic->flags & Basic_Flag_Integer) { + if (basic->size <= 4) strncat(out, "i", 64); + if (basic->size == 8) strncat(out, "l", 64); } - if (basic->flags & Basic_Flag_Pointer) return 'p'; - if (basic->flags & Basic_Flag_Float) { - if (basic->size <= 4) return 'f'; - if (basic->size == 8) return 'd'; + else if (basic->flags & Basic_Flag_Pointer) strncat(out, "p", 64); + else if (basic->flags & Basic_Flag_Float) { + if (basic->size <= 4) strncat(out, "f", 64); + if (basic->size == 8) strncat(out, "d", 64); } - if (basic->flags & Basic_Flag_SIMD) return 'v'; - if (basic->flags & Basic_Flag_Type_Index) return 'i'; - if (basic->size == 0) return 'v'; + else if (basic->flags & Basic_Flag_SIMD) strncat(out, "v", 64); + else if (basic->flags & Basic_Flag_Type_Index) strncat(out, "i", 64); + else strncat(out, "v", 64); } - - if (t->kind == Type_Kind_Distinct) { - return encode_type_as_dyncall_symbol(t->Distinct.base_type); + else if (t->kind == Type_Kind_Distinct) { + encode_type_as_dyncall_symbol(out, t->Distinct.base_type); } - return 'v'; + else strncat(out, "v", 64); } static void emit_foreign_function(OnyxWasmModule* mod, AstFunction* fd) { @@ -4165,12 +4164,11 @@ static void emit_foreign_function(OnyxWasmModule* mod, AstFunction* fd) { if (fd->is_foreign_dyncall) { module = bh_aprintf(global_heap_allocator, "dyncall:%b", fd->foreign_module->text, fd->foreign_module->length); - char type_encoding[64] = {0}; - type_encoding[0] = encode_type_as_dyncall_symbol(fd->type->Function.return_type); + char type_encoding[65] = {0}; + encode_type_as_dyncall_symbol(type_encoding, fd->type->Function.return_type); - int index = 1; bh_arr_each(AstParam, param, fd->params) { - type_encoding[index++] = encode_type_as_dyncall_symbol(param->local->type); + encode_type_as_dyncall_symbol(type_encoding, param->local->type); } name = bh_aprintf(global_heap_allocator, "%b:%s", fd->foreign_name->text, fd->foreign_name->length, type_encoding); diff --git a/compiler/src/wasm_runtime.c b/compiler/src/wasm_runtime.c index 78745a7e..75e9b69c 100644 --- a/compiler/src/wasm_runtime.c +++ b/compiler/src/wasm_runtime.c @@ -6,6 +6,7 @@ #ifdef USE_DYNCALL #include "dyncall.h" + #include "dyncall_callback.h" static DCCallVM *dcCallVM; #endif @@ -225,6 +226,7 @@ static wasm_trap_t *__wasm_dyncall(void *env, const wasm_val_vec_t *args, wasm_v arg_idx++; dcArgInt(dcCallVM, args->data[arg_idx++].of.i32); break; + default: assert(("bad dynamic call type", 0)); } } diff --git a/shared/include/dyncall/dyncall_args.h b/shared/include/dyncall/dyncall_args.h new file mode 100644 index 00000000..94a455d9 --- /dev/null +++ b/shared/include/dyncall/dyncall_args.h @@ -0,0 +1,78 @@ +/* + + Package: dyncall + Library: dyncallback + File: dyncallback/dyncall_args.h + Description: Callback's Arguments VM - Interface + License: + + Copyright (c) 2007-2022 Daniel Adler , + Tassilo Philipp + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + + +#ifndef DYNCALL_ARGS_H +#define DYNCALL_ARGS_H + +/* + * dyncall args C API + * + * dyncall args provides serialized access to arguments of a function call. + * related concepts: callback + * + */ + +#include "dyncall.h" + +#include "dyncall_value.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DCArgs DCArgs; + +/* functions to retrieve callback's params in callback handler */ +DC_API DCbool dcbArgBool (DCArgs* p); +DC_API DCchar dcbArgChar (DCArgs* p); +DC_API DCshort dcbArgShort (DCArgs* p); +DC_API DCint dcbArgInt (DCArgs* p); +DC_API DClong dcbArgLong (DCArgs* p); +DC_API DClonglong dcbArgLongLong (DCArgs* p); +DC_API DCuchar dcbArgUChar (DCArgs* p); +DC_API DCushort dcbArgUShort (DCArgs* p); +DC_API DCuint dcbArgUInt (DCArgs* p); +DC_API DCulong dcbArgULong (DCArgs* p); +DC_API DCulonglong dcbArgULongLong(DCArgs* p); +DC_API DCfloat dcbArgFloat (DCArgs* p); +DC_API DCdouble dcbArgDouble (DCArgs* p); +DC_API DCpointer dcbArgPointer (DCArgs* p); +/* for trivial aggrs: 'target' points to space to copy aggr to, returns 'target' + for C++ non-trivial aggrs: target is ignored, returns ptr to aggr arg */ +DC_API DCpointer dcbArgAggr (DCArgs* p, DCpointer target); + +/* helper func to put a to be returned struct-by-value into the 'result' + param of the callback handler; for C++ non-trivial aggrs, pass NULL in + 'ret', then copy aggr into result->p */ +DC_API void dcbReturnAggr(DCArgs *args, DCValue *result, DCpointer ret); + +#ifdef __cplusplus +} +#endif + +#endif /* DYNCALL_ARGS_H */ + diff --git a/shared/include/dyncall/dyncall_callback.h b/shared/include/dyncall/dyncall_callback.h new file mode 100644 index 00000000..73da3c1e --- /dev/null +++ b/shared/include/dyncall/dyncall_callback.h @@ -0,0 +1,58 @@ +/* + + Package: dyncall + Library: dyncallback + File: dyncallback/dyncall_callback.h + Description: Callback - Interface + License: + + Copyright (c) 2007-2022 Daniel Adler , + Tassilo Philipp + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + +#ifndef DYNCALL_CALLBACK_H +#define DYNCALL_CALLBACK_H + +#include "dyncall_args.h" +#include "dyncall_signature.h" +#include "dyncall_value.h" + +typedef struct DCCallback DCCallback; + +/* callback handler: + - handlers return value signature char (see dyncall_signature.h) of callback's return value type + - callback return value is written to the corresponding type's field of result + - if callback return value is an aggregate (by value), use dcbReturnAggr() as a helper to write to result +*/ +typedef DCsigchar (DCCallbackHandler)(DCCallback* pcb, DCArgs* args, DCValue* result, void* userdata); + +#ifdef __cplusplus +extern "C" { +#endif + +DCCallback* dcbNewCallback (const DCsigchar* signature, DCCallbackHandler* funcptr, void* userdata); +DCCallback* dcbNewCallback2 (const DCsigchar* signature, DCCallbackHandler* funcptr, void* userdata, DCaggr *const * aggrs); +void dcbInitCallback (DCCallback* pcb, const DCsigchar* signature, DCCallbackHandler* handler, void* userdata); +void dcbInitCallback2(DCCallback* pcb, const DCsigchar* signature, DCCallbackHandler* handler, void* userdata, DCaggr *const * aggrs); +void dcbFreeCallback (DCCallback* pcb); +void* dcbGetUserData (DCCallback* pcb); + +#ifdef __cplusplus +} +#endif + +#endif /* DYNCALL_CALLBACK_H */ + diff --git a/shared/include/dyncall/dyncall_value.h b/shared/include/dyncall/dyncall_value.h new file mode 100644 index 00000000..6a4cd863 --- /dev/null +++ b/shared/include/dyncall/dyncall_value.h @@ -0,0 +1,98 @@ +/* + + Package: dyncall + Library: dyncall + File: dyncall/dyncall_value.h + Description: Value variant type + License: + + Copyright (c) 2007-2018 Daniel Adler , + Tassilo Philipp + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +*/ + + +/* + + dyncall value variant + + a value variant union-type that carries all supported dyncall types. + + REVISION + 2007/12/11 initial + +*/ + +#ifndef DYNCALL_VALUE_H +#define DYNCALL_VALUE_H + +#include "dyncall_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef union DCValue_ DCValue; + +union DCValue_ +{ +/* dyncallback assembly pulls value directly from DCValue structs, without */ +/* knowledge about types used, so lay it out as needed at compile time, here */ +#if defined(DC__Endian_BIG) && (defined(DC__Arch_PPC32) || defined(DC__Arch_MIPS) || defined(DC__Arch_Sparc)) + DCbool B; + struct { DCchar c_pad[3]; DCchar c; }; + struct { DCuchar C_pad[3]; DCuchar C; }; + struct { DCshort s_pad; DCshort s; }; + struct { DCshort S_pad; DCshort S; }; + DCint i; + DCuint I; +#elif defined(DC__Endian_BIG) && (defined(DC__Arch_PPC64) || defined(DC__Arch_MIPS64) || defined(DC__Arch_Sparc64)) + struct { DCbool B_pad; DCbool B; }; + struct { DCchar c_pad[7]; DCchar c; }; + struct { DCuchar C_pad[7]; DCuchar C; }; + struct { DCshort s_pad[3]; DCshort s; }; + struct { DCshort S_pad[3]; DCshort S; }; + struct { DCint i_pad; DCint i; }; + struct { DCint I_pad; DCuint I; }; +#else + DCbool B; + DCchar c; + DCuchar C; + DCshort s; + DCushort S; + DCint i; + DCuint I; +#endif + DClong j; + DCulong J; + DClonglong l; + DCulonglong L; +/* floats on mips are right justified in fp-registers on big endian targets, as they aren't promoted */ +#if defined(DC__Endian_BIG) && (defined(DC__Arch_MIPS) || defined(DC__Arch_MIPS64)) + struct { DCfloat f_pad; DCfloat f; }; +#else + DCfloat f; +#endif + DCdouble d; + DCpointer p; + DCstring Z; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* DYNCALL_VALUE_H */ + diff --git a/shared/lib/linux_x86_64/lib/libdyncallback_s.a b/shared/lib/linux_x86_64/lib/libdyncallback_s.a new file mode 100644 index 0000000000000000000000000000000000000000..377a6f8005f5354377ff844447e6435bf86dce99 GIT binary patch literal 14726 zcmdU03vg7|dA^b`C>X5uBZ<;b&xOcDc5ERqq2Q|S71D|e5wBM;4_9j4>XFi9tEr)k2ZEd|Cy7egZ_(>9uPrXf?N)wpgdD%ugwbgXn7 ztN(ZI|L>kX+C_jfNqT1PJ?DJ?S(Tng4pk7rme&~!I@hzJnhhq9hGVvEIYlAshw6RhtxkZMG z-M@11P&`Vt3Dc8^hF?6`lGsqhKtMGsVPOnY5LRx`hb9%LXZ10=)(JP52lY0?CkOz)@G08~Yo(_W|2F2Ipcp~bh2rh>RN}W5eGXCj*4lYy8yky^ zQ&yYa#>SJ0v=_yU!Smc$CJrtRzrr#449|NQkQ6?z;mbArl!j9`75Qz(ssAwsioxNO z;LpBAeFLOqKUCFkEZFCI90TT z#^58VG))PdAC6~Qs0qfsWcrZRqBM9T;h|(?%wr2aSY(qp#uVB}4-RMkS_dg@&K(~o z4DIgFqlam3M3+Gt@HhIC9neTF2YvOqmMc0v02_mMhnt@0u4|Ctfsuf@&|>HV^e-V;g$NY znH$t)IrJZ7MPB-^QrQqW!{uK_hQginK&2`FA;u{x3&*)aJIm0;ZGAV<&Ax}oID@ez zv z(}kKhfajR()Mw}KINGdOo%#v421{ygSG{|}%@&rq6Zics$^hA|nVP*vCGwU#0kv3r zSMDz@yHc{VDm%YIbgnAuT%~lbGDbLzTI`tt=2lqT zeG_WxR7gSy=cH@s=#vLz_L9 zdhiVG#!d~o`DG6@V?gDCGaC!k^Jm<=)5yb#Savm1=Mr&>_IguR=5EEoTehn%eZ~Ot zPJIr0KcrUWf^So5s^PTL*yg04n+#sU2$_=Vubd}#XEk&89_ z5loSWeCTaLYD>u5+2AF70Gi+{h4k|5kxMIH{)SdcfVQ65`Q=CJ^TD^X^Yt%xf#&7| z6j6Qf!JTq*#jU1~f=E0TeO4>n3o~`@h3Pt1eT!7>z||3&;@I}9m~MS;D(DjUPPh-g z;pRe@3B7y{v!1(c%5B&|SdcK?UK)Mb`I_{~?0ju{`_=D&6-IFr691d3QM8ZnlsmDi zmXus}CxUO%p4-iRcLq&(^{*hDJ#x7|(^5fZ4at-gtFhtc>aHGRp=Fr|mlmS&l&hQV zaVM~0rf$D*?kHl(eyf<8R2CJp7tXzMBfg5gxDvhC@aiAYU?#&)QH5s)-A~=8Mq~O8 zUw?l8d}-j)xb2~FOHGm~P}@_-QuE}CgOzX@taBjcoxtY`2S1~dJ?QK=-RSq+>>JBy z$WKwbe)^oD|3`f&y26fcoWwRInQ81sXMUqzTSU2Copsy84HT z9-7MsukZ-xj$eU}|I}=sB51vQ#_wg)HbFCgE;Q}t@1}xg_f%t?Va6Hy^lRQ}xZ9d- z*4iICxnkDrr)=;Fz9P*y`iiLiuhcZWx`XC#9H~lMcUmiPd~S)NVwTm}5$>(WYN8`ixI9vjDDwaswW233DNIE)VH)rDB!ExH%dxLgr z^d-+u4B12D_Ry#o8+>qGx!z1Xl^Gu=Zw2uhWhkZ5SR|Di9U6^Naf$f4#deJ(GO1rTL{h#&eHi@jEg%#l}Vxb4Kb#;GMtC(zp9p>?%OL*$`)%aQns}jI| zD?SE{;aYVH@fE5uNlBa$fJ}mnwoFCZ~)ev6Kb;p?jVGBLL8yP28s|wxp zhOgqgoL|fFXBnU4yc#2v_87m5^$0%B_`?SNn~d)=aQGHbE;axNaz^|jrGdX{@MGo& z{xt*tQ^vnz;HMd%G4MASU(Wq1_J5D@CIg>ld^_W6tP%EKjK>*YM|p%F(*rx@?4`yQ zVav!4^syQ4tb%{63jPq`(7%(rUX4pAEjxrotw?e>JW4)T#6uK^!#%yhzP@L}U7;sB z`gT6~L^#~Cu_D^abMyZ{FZh@@79BrCm7-OQd_Ccz$Y_jm$`urjj=bOlEAE<&K({P;AMgLc;;FmNU->&e#rQxeIoF)eqU?=-B1@ zg8z$#aNF8m7y9#htk--*9NkahjPuHhfk$~~{)dixA5_Z3Qg z@8kMr3|#CvYT#dEdp^&&X^+ix=;y9hh-2Wg&h#6&tPjTwT-Jw^1}^JE!N6sGm^N@( zA82Y*LF|qDo3%e;Q;KJaA1cZmOD`@+A@U0pA|NdU}BK zv;|0o|2E1M+$ zYmk}@&LAQb&N$-gY2-BdZ+dK%o^;eeMtag|CMOiUP;(8Zt+sH6e3j%A$`)!U+iXc#H~XLUvr z;L0if@Q}Ucn2n|Td*{jDJ3;lNU-h?=8r5V}$Lx`7R(i!5r8!QT=iK~}0(r;C*A^m! zGM9Je$Rjo>Ell$C`-R#&C|~9Gi-hygDx9Ji9Whr%@k=7F97+n!n?As@`q)$-Dx7W+ zOFh^&-Pxz zBltjz=l&*j?q5St8)`#g3NNA8PAazm==*?E_%*zQp7y;4pdSWK;ZeMV{(Oo48gL3t zcnSTpCHm{YDSQMkq3`zTfGP!4Ls)ke{=HT3 zSQY$O6&&{lD%tY~Rq(G=!T*wQ*#8rrlvUpmc8T%Fc=_-F%VWkBF1ha!-qHQ!_V(`Z zlU-eVf_>q>_U+xlFsTuh`Xw#xK33Tck>bUWN~h%ya8(SIwyfXsBekw$ZI?5Q_%489 zdnEXmxcz}+Ef)OCj6XzJy@vm}#;>2T&uh4THomOk`WgFO4afdK%6*jk(X?k+!!b1r z|Lz1useb$>l8=T;_fE|u9#-VmtVe$P{hkg=0+_aV6>84hC$C{ey zJ=kQ^dtXx{z5AL1^zLq|r}wg^_NUr=p6cEEbT{q0+j~0mPIG(D^YjyHDA4QXe&}C_ zopke0(ysBXg`URj;ilI2(;ubZj+@+kA9Msdp)b(g)HwSg!0IA~o4dCEqTHyxLCL*O zEh@K_%6*HHrXZhs_xZae&1YQkg+A4zMJCuLcahU+bX`k zAY)X>_@*toiz;JH@W&aq1>o5K{pb3EIR^|ef56~Bz__VD%ecYL-_YW_vDiz&PP4vW zW!zSA>GcBFt8MFW%)Kc7@r`7J*)9!cV6Ye(NR;k3F)kJLycJ{Y6_1|Us8yeF0C z4;fq<2$MWL!-_f{8B{R{sqpwXRi(HwR8>pSW;CK?4n$&>H$ECm;6DyUrW;@JztI+A z(Jfu$a&B33-);u}LVSz7gcsE~oVs2_{De8Dt|b^4FF{4_jV2>gO!$+8E9qacBA6QT3+@x|lSH3qC)Fm%}~#0DATK@#AjJzfMX% z5b}xi1n8pX&EHV*_bix~u}!$$|3|2HlyiRSr+d!Fz69r93B^7u@0{XcmH*IV*(`wZ zS2hN3aDRW%r0{b*QYhn{AOgzSDQO-R$k@~b1xwB>kR{> ze2G6zbV=*1U)D2P+T;xqt~UQt*e~}