Files
TempleOS-Rijn/TempleOS/Kernel/Hash1a.CPP
T
2018-09-08 21:45:45 +02:00

809 lines
17 KiB
C++

/****
$SP,"<1>",1$
Tasks have symbol tables. If symbol not found, parent task's table
is searched. Adam task has system-wide symbols.
SYMBOL LOOK-UP (Especially, used by JIT compiler and Loader)
1) Symbol text name is hashed -- Adding the ASCII values of all
chars with shifts.
2) Hash body array is indexed.
3) Linked-lst is traversed until match of text and type-of-entry
4) If not found, next hash table is searched.
Duplicate entries are allowed--they overshadow old entries.
ADDRESS-TO-SYMBOL LOOK-UP (Slow because not important--could add tree)
1)PSect Cache is scanned.
2)"next_added,last_added" que links are traversed. This speeds look-up
instead of scanning hash table because hash table is so sparce.
See $LK,"PSectFind","MN:PSectFind"$().
****/
asm {
USE64
// ************************************
// IN: RSI=Addr of string
// OUT: RAX
SYS_HASH_STR::
XOR RAX,RAX
OR RSI,RSI
JZ @@15
PUSH RSI
PUSH RBX
XOR RBX,RBX
JMP @@10
@@05: SHL1 RBX
ADC RBX,RAX
@@10: LODSB
OR AL,AL
JNZ @@05
MOV RAX,RBX
SHR RBX,16
ADC RAX,RBX
POP RBX
POP RSI
@@15: RET
// ************************************
// IN: RAX=HASHED_STRING
// RSI=STR
// RBX=TYPE MASK
// RDI=TABLE
// RCX=INSTANCE, NOT ZERO
// OUT: RAX=ENTRY OR ZERO NOT FOUND
// RDX=POINTER TO POINTER TO ENTRY
// RCX IF NOT FOUND ENOUGH, DECREMENTED BY NUM MATCHES
// ZERO FLAG SET NOT FOUND
SYS_HASH_SINGLE_TABLE_FIND1::
MOV RCX,1
SYS_HASH_SINGLE_TABLE_FIND::
OR RCX,RCX
JNZ @@05
XOR RAX,RAX
RET
@@05: AND RAX,U64 CHashTable.mask[RDI]
MOV RDX,U64 CHashTable.body[RDI]
LEA RDX,U64 [RDX+RAX*8]
@@10: MOV RAX,U64 [RDX]
OR RAX,RAX
JNZ @@15
RET
@@15: TEST U32 CHash.type[RAX],EBX
JZ @@30
PUSH RAX
PUSH RDI
PUSH RSI
MOV RDI,U64 CHash.str[RAX]
@@20: LODSB
CMP U8 [RDI],AL
JNE @@25
INC RDI
OR AL,AL
JNZ @@20
POP RSI
POP RDI
POP RAX
LOOP @@30
INC U32 CHash.use_cnt[RAX]
OR RAX,RAX
RET
@@25: POP RSI
POP RDI
POP RAX
@@30: LEA RDX,U64 CHash.next[RAX]
JMP @@10
// ************************************
// IN: RSI=STR
// RBX=TYPE MASK
// RDI=TABLE
// RCX=INSTANCE NUM
// OUT: RAX=ENTRY OR ZERO NOT FOUND
// ZERO FLAG SET NOT FOUND
SYS_HASH_FIND1::
MOV RCX,1
SYS_HASH_FIND::
PUSH RDI
CALL SYS_HASH_STR
@@05: PUSH RAX
CALL SYS_HASH_SINGLE_TABLE_FIND
JNZ @@15
POP RAX
@@10: MOV RDI,U64 CHashTable.next[RDI]
OR RDI,RDI
JNZ @@05
POP RDI
XOR RAX,RAX
RET
@@15: ADD RSP,8
POP RDI
OR RAX,RAX
RET
// ************************************
// IN: RSI=STR
// RDI=TABLE
// OUT: RAX=BUCKET
SYS_HASH_BUCKET_FIND::
PUSH RDX
CALL SYS_HASH_STR
AND RAX,U64 CHashTable.mask[RDI]
MOV RDX,U64 CHashTable.body[RDI]
LEA RAX,U64 [RDX+RAX*8]
POP RDX
RET
// ************************************
//IN: RSI
// RCX=INSTANCE
//OUT: RAX
// ZERO FLAG SET NOT FOUND
SYS_EXT_FIND1::
MOV RCX,1
SYS_EXT_FIND::
PUSH RDI
PUSH RBX
XOR RAX,RAX
MOV RDI,U64 FS:CTask.hash_table[RAX]
MOV RBX,HTT_FUN|HTT_EXPORT_SYS_SYM
CALL SYS_HASH_FIND
JZ @@15
@@05: TEST U32 CHash.type[RAX],HTT_FUN
JZ @@10
MOV RAX,U64 CHashFun.executable_addr[RAX]
JMP @@15
@@10: MOV RAX,U64 CHashExport.val[RAX]
@@15: POP RBX
POP RDI
OR RAX,RAX
RET
_EXT_FIND::
ENTER 0
PUSH RSI
PUSH RDI
XOR RAX,RAX
MOV RDI,U64 FS:CTask.hash_table[RAX]
MOV RSI,U64 SF_ARG1[RBP]
MOV RCX,U64 SF_ARG2[RBP]
MOV RBX,HTT_FUN|HTT_EXPORT_SYS_SYM
CALL SYS_HASH_FIND
JZ @@15
@@05: TEST U32 CHash.type[RAX],HTT_FUN
JZ @@10
MOV RAX,U64 CHashFun.executable_addr[RAX]
JMP @@15
@@10: MOV RAX,U64 CHashExport.val[RAX]
@@15: POP RDI
POP RSI
LEAVE
RET1 16
// ************************************
_HASH_STR::
ENTER 0
PUSH RSI
MOV RSI,U64 SF_ARG1[RBP]
CALL SYS_HASH_STR
POP RSI
LEAVE
RET1 8
// ************************************
_HASH_FIND::
ENTER 0
PUSH RSI
PUSH RDI
MOV RSI,U64 SF_ARG1[RBP]
MOV RDI,U64 SF_ARG2[RBP]
MOV RBX,U64 SF_ARG3[RBP]
MOV RCX,U64 SF_ARG4[RBP]
CALL SYS_HASH_FIND
POP RDI
POP RSI
LEAVE
RET1 32
// ************************************
_HASH_SINGLE_TABLE_FIND::
ENTER 0
PUSH RSI
PUSH RDI
MOV RSI,U64 SF_ARG1[RBP]
MOV RDI,U64 SF_ARG2[RBP]
MOV RBX,U64 SF_ARG3[RBP]
MOV RCX,U64 SF_ARG4[RBP]
CALL SYS_HASH_STR
CALL SYS_HASH_SINGLE_TABLE_FIND
POP RDI
POP RSI
LEAVE
RET1 32
// ************************************
_HASH_BUCKET_FIND::
ENTER 0
PUSH RSI
PUSH RDI
MOV RSI,U64 SF_ARG1[RBP]
MOV RDI,U64 SF_ARG2[RBP]
CALL SYS_HASH_BUCKET_FIND
POP RDI
POP RSI
LEAVE
RET1 16
// ************************************
_HASH_ADD::
ENTER 0
PUSH RSI
PUSH RDI
MOV RCX,U64 SF_ARG1[RBP]
MOV RSI,U64 CHash.str[RCX]
MOV RDI,U64 SF_ARG2[RBP]
CALL SYS_HASH_BUCKET_FIND
MOV RCX,U64 SF_ARG1[RBP]
PUSHFD
CLI
MOV RBX,U64 [RAX]
MOV U64 CHash.next[RCX],RBX
MOV U64 [RAX],RCX
//QueIns RCX
MOV RBX,U64 CHashTable.last_added[RDI]
MOV U64 CHash.next_added[RCX],RDI
MOV U64 CHash.last_added[RCX],RBX
MOV U64 CHash.next_added[RBX],RCX
MOV U64 CHashTable.last_added[RDI],RCX
POPFD
POP RDI
POP RSI
LEAVE
RET1 16
// ************************************
_HASH_ADD_AFTER::
ENTER 0
PUSH RDI
MOV RCX,U64 SF_ARG1[RBP]
MOV RDI,U64 SF_ARG3[RBP]
PUSHFD
CLI
MOV RAX,SF_ARG2[RBP]
MOV RBX,U64 [RAX]
MOV U64 CHash.next[RCX],RBX
MOV U64 [RAX],RCX
//QueIns RCX
MOV RBX,U64 CHashTable.last_added[RDI]
MOV U64 CHash.next_added[RCX],RDI
MOV U64 CHash.last_added[RCX],RBX
MOV U64 CHash.next_added[RBX],RCX
MOV U64 CHashTable.last_added[RDI],RCX
POPFD
POP RDI
LEAVE
RET1 24
// ************************************
_HASH_REPLACE::
ENTER 0
PUSH RSI
PUSH RDI
MOV RCX,U64 SF_ARG1[RBP]
OR RCX,RCX
JZ @@15
MOV RSI,U64 CHash.str[RCX]
XOR RBX,RBX
MOV EBX,U32 CHash.type[RCX]
AND EBX,~HTF_FLAGS_MASK&0xFFFFFFFF
MOV RDI,U64 SF_ARG2[RBP]
MOV RCX,U64 SF_ARG3[RBP]
CALL SYS_HASH_STR
PUSHFD
CLI
CALL SYS_HASH_SINGLE_TABLE_FIND
MOV RCX,U64 SF_ARG1[RBP]
JZ @@05 //Z set by find
//QueRem RAX,QueIns RCX
MOV RBX,U64 CHash.next_added[RAX]
MOV U64 CHash.next_added[RCX],RBX
MOV U64 CHash.last_added[RBX],RCX
MOV RBX,U64 CHash.last_added[RAX]
MOV U64 CHash.last_added[RCX],RBX
MOV U64 CHash.next_added[RBX],RCX
MOV RBX,U64 CHash.next[RAX]
JMP @@10
//QueIns RCX
@@05: MOV RBX,U64 CHashTable.last_added[RDI]
MOV U64 CHash.next_added[RCX],RDI
MOV U64 CHash.last_added[RCX],RBX
MOV U64 CHash.next_added[RBX],RCX
MOV U64 CHashTable.last_added[RDI],RCX
MOV RBX,RAX
@@10: MOV U64 CHash.next[RCX],RBX
MOV U64 [RDX],RCX
POPFD
OR RAX,RAX
JZ @@15
PUSH_C_REGS
PUSH RAX
CALL &HashDel
POP_C_REGS
@@15: POP RDI
POP RSI
LEAVE
RET1 24
// ************************************
_HASH_REM_DEL::
ENTER 0
PUSH RSI
PUSH RDI
MOV RCX,U64 SF_ARG1[RBP]
OR RCX,RCX
JZ @@10
MOV RSI,U64 CHash.str[RCX]
XOR RBX,RBX
MOV EBX,U32 CHash.type[RCX]
AND EBX,~HTF_FLAGS_MASK&0xFFFFFFFF
MOV RDI,U64 SF_ARG2[RBP]
MOV RCX,U64 SF_ARG3[RBP]
CALL SYS_HASH_STR
PUSHFD
CLI
CALL SYS_HASH_SINGLE_TABLE_FIND
JZ @@05
CMP RAX,U64 SF_ARG1[RBP]
JNE @@05
MOV RBX,U64 CHash.next[RAX]
MOV U64 [RDX],RBX
//QueRem RAX
MOV RBX,U64 CHash.next_added[RAX]
MOV RDX,U64 CHash.last_added[RAX]
MOV U64 CHash.next_added[RDX],RBX
MOV U64 CHash.last_added[RBX],RDX
POPFD
PUSH_C_REGS
PUSH RAX
CALL &HashDel
POP_C_REGS
POP RDI
POP RSI
MOV RAX,1
LEAVE
RET1 24
@@05: POPFD
@@10: POP RDI
POP RSI
XOR RAX,RAX
LEAVE
RET1 24
}
_extern _HASH_STR I64 HashStr(U8 *st);
_extern _HASH_FIND CHash *
HashFind(U8 *st,CHashTable *table,I64 mask,I64 instance=1);
_extern _HASH_SINGLE_TABLE_FIND CHash *
HashSingleTableFind(U8 *st,CHashTable *table,I64 mask,I64 instance=1);
_extern _HASH_BUCKET_FIND CHash **
HashBucketFind(U8 *st,CHashTable *table);
_extern _HASH_ADD U0 HashAdd(CHash *temph,CHashTable *table);
_extern _HASH_REPLACE U0 HashReplace(CHash *temph,CHashTable *table,
I64 instance=1);//instance must match temph's
_extern _HASH_REM_DEL Bool HashRemDel(CHash *temph,CHashTable *table,
I64 instance=1);//instance must match temph's
I64 HashTypeNum(CHash *temph)
{
if (temph)
return Bsf(temph->type&HTT_TYPE_MASK);
else
return -1;
}
CHashTable *HashTableNew(I64 size,CTask *mem_task=NULL)
{
CHashTable *tempht;
tempht=CAlloc(sizeof(CHashTable),mem_task);
tempht->body=CAlloc(size<<3,mem_task);
tempht->mask=size-1;
tempht->next_added=tempht->last_added=tempht;
return tempht;
}
U0 HashDel(CHashSrcSym *temph)
{
if (!temph) return;
if (!(temph->type&HTT_DICT_WORD))
Free(temph->str);
if (temph->type & HTG_SRC_SYM) {
Free(temph->src_link);
Free(temph->index);
Free(temph->import_name);
LinkedLstDel(temph->ie_lst);
if (temph->type & (HTT_FUN | HTT_EXPORT_SYS_SYM))
Free(temph->debug);
if (temph->type & (HTT_FUN | HTT_CLASS))
//Assumes code not on heap, so doesn't Free.
//$LK,"MemberLstDel","MN:MemberLstDel"$() is an import to the Kernel module
MemberLstDel(temph);
else if (temph->type&HTT_DEFINE_STR)
Free(temph(CHashDefineStr *)->data);
else if (temph->type & HTT_GLBL_VAR) {
if (!(temph(CHashGlblVar *)->flags&GVAF_ALIAS))
Free(temph(CHashGlblVar *)->data_addr);
LinkedLstDel(temph(CHashGlblVar *)->dim.next);
if (temph(CHashGlblVar *)->fun_ptr)
HashDel(temph(CHashGlblVar *)->fun_ptr-temph(CHashGlblVar *)->fun_ptr->ptr_cnt);
}
} else if (temph->type & HTT_FILE)
Free(temph(CHashGeneric *)->user_data0);
Free(temph);
}
U0 HashTableDel(CHashTable *tempht)
{
CHashSrcSym *temph,*temph1;
if (!tempht) return;
temph=tempht->next_added;
while (temph!=tempht) {
temph1=temph->next_added;
HashDel(temph);
temph=temph1;
}
Free(tempht->body);
Free(tempht);
}
CHashGeneric *HashGenericAdd(U8 *name,I64 type,I64 u0=0,I64 u1=0,I64 u2=0,CTask *task=NULL)
{
if (!task) task=Fs;
CHashGeneric *result=CAlloc(sizeof(CHashGeneric),task);
result->type=type;
result->user_data0=u0;
result->user_data1=u1;
result->user_data2=u2;
result->str=StrNew(name,task);
HashAdd(result,task->hash_table);
return result;
}
I64 NamedFramePtr(U8 *name,CTask *task=NULL)
{
CHashGeneric *temph;
if (!task) task=Fs;
if (temph=HashFind(name,task->hash_table,HTT_NAMED_FRAME_PTR))
return temph->user_data0;
else
return 0;
}
CHashGeneric *NamedFramePtrAdd(U8 *name,I64 val,CTask *task=NULL)
{
return HashGenericAdd(name,HTT_NAMED_FRAME_PTR,val,0,0,task);
}
I64 NamedFramePtrSet(U8 *name,I64 val,CTask *task=NULL)
{
CHashGeneric *temph;
if (!task) task=Fs;
if (temph=HashFind(name,task->hash_table,HTT_NAMED_FRAME_PTR))
return LXchgU64(&temph->user_data0,val);
else
return 0;
}
I64 NamedFramePtrDel(U8 *name,CTask *task=NULL)
{
CHashGeneric *temph;
I64 result=0;
if (!task) task=Fs;
if (temph=HashFind(name,task->hash_table,HTT_NAMED_FRAME_PTR)) {
result=temph->user_data0;
HashRemDel(temph,task->hash_table);
}
return result;
}
U0 LoadOneImport(U8 **_src,U8 *module_base,I64 ld_flags)
{
U8 *src=*_src,*ptr2,*st_ptr;
I64 i,etype;
CHashExport *tempess=NULL;
CHashImport *tempiss;
Bool first=TRUE;
CHashFun *tempf;
CHashGlblVar *tempg;
while (etype=*src++) {
i=*src(U32 *)++;
st_ptr=src;
src+=StrLen(st_ptr)+1;
if (*st_ptr) {
if (!first) {
*_src=st_ptr-5;
return;
} else {
first=FALSE;
if (!(tempess=HashFind(st_ptr,Fs->hash_table,HTG_ALL-HTT_IMPORT_SYS_SYM))) {
if (!(ld_flags & LDF_SILENT)) {
PutDefine("ST_UNRESOLVED_REFERENCE");
"" st_ptr;
NewLine;
}
tempiss=CAlloc(sizeof(CHashImport));
tempiss->str=StrNew(st_ptr);
tempiss->type=HTT_IMPORT_SYS_SYM;
tempiss->module_header_entry=st_ptr-5;
tempiss->module_base=module_base;
HashAdd(tempiss,Fs->hash_table);
}
}
}
if (tempess) {
ptr2=module_base+i;
if (tempess->type & HTT_FUN) {
tempf=tempess;
if (etype & 1)
i=tempf->executable_addr;
else
i=tempf->executable_addr-module_base;
} else if (tempess->type & HTT_GLBL_VAR) {
tempg=tempess;
if (etype & 1)
i=tempg->data_addr;
else
i=tempg->data_addr-module_base;
} else {
if (etype & 1)
i=tempess->val;
else
i=tempess->val(U8 *)-module_base;
}
if (etype<=EIE_LIT_U8)
*ptr2(U8 *)+=i;
else if (etype<=EIE_LIT_U16)
*ptr2(U16 *)+=i;
else if (etype<=EIE_LIT_U32)
*ptr2(U32 *)+=i;
else if (etype<=EIE_LIT_U64)
*ptr2(I64 *)+=i;
}
}
*_src=src-1;
}
U0 SysSymImportsResolve(U8 *st_ptr,I64 ld_flags)
{
CHashImport *tempiss;
U8 *ptr;
while (tempiss=HashSingleTableFind(st_ptr,Fs->hash_table,HTT_IMPORT_SYS_SYM)) {
ptr=tempiss->module_header_entry;
LoadOneImport(&ptr,tempiss->module_base,ld_flags);
tempiss->type=HTT_INVALID;
}
}
U0 LoadPass1(U8 *src,U8 *module_base,I64 ld_flags)
{
U8 *ptr2,*ptr3,*st_ptr;
I64 i,j,cnt,etype;
CHashExport *tempess=NULL;
while (etype=*src++) {
i=*src(U32 *)++;
st_ptr=src;
src+=StrLen(st_ptr)+1;
switch (etype) {
case EIE_REL_EXPORT:
case EIE_LIT_EXPORT:
tempess=CAlloc(sizeof(CHashExport));
tempess->str=StrNew(st_ptr);
tempess->type=HTT_EXPORT_SYS_SYM|HTF_LIT;
if (etype==EIE_LIT_EXPORT)
tempess->val=i;
else
tempess->val=i+module_base;
HashAdd(tempess,Fs->hash_table);
SysSymImportsResolve(st_ptr,ld_flags);
break;
case EIE_REL_U8...EIE_LIT_U64:
src=st_ptr-5;
LoadOneImport(&src,module_base,ld_flags);
break;
case EIE_ABS_ADDR:
if (ld_flags & LDF_NO_ABSS)
src+=i*sizeof(U32);
else {
cnt=i;
for (j=0;j<cnt;j++) {
ptr2=module_base+*src(U32 *)++;
*ptr2(U32 *)+=module_base;
}
}
break;
case EIE_HEAP_GLBL:
ptr3=MAlloc(*src(I64 *)++);
if (*st_ptr) {
tempess=CAlloc(sizeof(CHashExport));
tempess->str=StrNew(st_ptr);
tempess->type=HTT_EXPORT_SYS_SYM|HTF_LIT;
tempess->val=ptr3;
HashAdd(tempess,Fs->hash_table);
}
cnt=i;
for (j=0;j<cnt;j++) {
ptr2=module_base+*src(U32 *)++;
*ptr2(I64 *)+=ptr3;
}
break;
}
}
}
U0 LoadPass2(U8 *src,U8 *module_base,I64)
{
U8 *st_ptr;
I64 i,etype;
while (etype=*src++) {
i=*src(U32 *)++;
st_ptr=src;
src+=StrLen(st_ptr)+1;
switch (etype) {
case EIE_MAIN:
Call(i+module_base);
break;
case EIE_ABS_ADDR:
src+=sizeof(U32)*i;
break;
case EIE_HEAP_GLBL:
src+=8+sizeof(U32)*i;
break;
}
}
}
U8 *Load(U8 *filename,I64 ld_flags=0,CBinFile *bfh_addr=INVALID_PTR)
{ //See $LK,"BinRep","MN:BinRep"$().
//bfh_addr==INVALID_PTR means don't care what load addr.
U8 *fbuf,*module_base,*absname;
I64 size,module_alignment,module_org;
CBinFile *bfh2;
CBinFilePatchTableWithOrg *bfpt;
fbuf=DftExt(filename,"BIN.Z");
if (!(bfh2=FileRead(fbuf,&size))) {
Free(fbuf);
return NULL;
}
//See $LK,"Loader Patch Table Generation","FF:::/TempleOS/Compiler/CmpMain.CPP,EIE_MODULE_ALIGNMENT"$
bfpt=bfh2(U8 *)+bfh2->loader_patch_table_offset;
module_alignment=bfpt->module_alignment;
if (bfpt->eie_module_org==EIE_MODULE_ORG)
module_org=bfpt->module_org;
else
module_org=INVALID_PTR;
if (!module_alignment || Bsf(module_alignment)!=Bsr(module_alignment))
throw(EXCEPT_BIN_MODULE);
#assert sizeof(CBinFile)==16
if (bfh_addr==INVALID_PTR) {
if (module_org==INVALID_PTR) {
if (Fs->code_heap!=Fs->data_heap) {
if (module_alignment<16)
module_alignment=16;
bfh_addr=MAllocAligned(size,module_alignment,Fs->code_heap,module_alignment-sizeof(CBinFile));
} else if (module_alignment>8)
bfh_addr=MAllocAligned(size,module_alignment,NULL,module_alignment-sizeof(CBinFile));
else { //Less than 2Gig system memory
bfh_addr=bfh2;
goto lo_skip; //File is already in code heap area, don't copy.
}
} else
bfh_addr=module_org;
}
MemCpy(bfh_addr,bfh2,size);
Free(bfh2);
lo_skip:
module_base=bfh_addr(U8 *)+sizeof(CBinFile);
if (ArcXSum(bfh_addr,bfh_addr->file_size)!=TEMPLEOS_XSUM)
throw(EXCEPT_ARC_XSUM);
absname=FileNameAbs(fbuf);
Free(fbuf);
fbuf=StrNew(absname);
FileExtRem(fbuf);
if (fbuf[1]==':' && StrLen(fbuf)>2)
HashGenericAdd(fbuf+2,HTT_MODULE|HTF_PUBLIC,bfh_addr);
Free(fbuf);
LoadPass1(bfh_addr(U8 *)+bfh_addr->loader_patch_table_offset,module_base,ld_flags);
if (!(ld_flags&LDF_JUST_LOAD))
LoadPass2(bfh_addr(U8 *)+bfh_addr->loader_patch_table_offset,module_base,ld_flags);
Free(absname);
return bfh_addr;
}
U0 LoadKernel()
{
HashGenericAdd(KERNEL_MODULE_NAME,HTT_MODULE|HTF_PUBLIC,sys_boot_base-sizeof(CBinFile));
//Abs patches done here $LK,"CBinFilePatchTableWithoutOrg","FF:D:/TempleOS/Kernel/KStart.CPP,CBinFilePatchTableWithoutOrg"$.
LoadPass1(sys_boot_patch_table_base,sys_boot_base,LDF_NO_ABSS|LDF_SILENT);
//No main routines
// LoadPass2(sys_boot_patch_table_base,sys_boot_base,0);
}
�"  Cat"8�Cbs"X Dog"t�Dog"� Bird!D\`!h��!�\�!(\,!0@�XHX`X`T`T`THTHXHlHl`�x�\�x�\�H�`HHH`\H\`pHp`4H4`x�x�x�\�\\\x$\$x8\8xL\Lx`\`x\x0\0xD\Dx\xx88����8����8����x����xxH`H,H,4,4,4`4`H`!hh!HTXT!����8@d
!P\�!\�4�!T�d�!d�d!d��!p�`�!\�\�!\��!�!<L!LL(!L(�(!�(t!T\ !\�T!4�`�!`T!t�0!�0H,!H,H!H<!�!�`�!`�`�!`�p�!��d!d`�!`�T�