finish new udt baking; pull members & enum vals out of sub-udt lists, makes it much harder to layout & parallelize - instead pull them aside into their own chunk lists, require that UDTs just point to their first member; that way we can go wide when baking UDT members/enum-vals

This commit is contained in:
Ryan Fleury
2025-08-28 09:37:25 -07:00
parent b8414c4bfd
commit e4ac3febd0
5 changed files with 236 additions and 55 deletions
+112
View File
@@ -840,6 +840,8 @@ rdim_unit_chunk_list_concat_in_place(RDIM_UnitChunkList *dst, RDIM_UnitChunkList
////////////////////////////////
//~ rjf: [Building] Type Info Building
//- rjf: type nodes
RDI_PROC RDIM_Type **
rdim_array_from_type_list(RDIM_Arena *arena, RDIM_TypeList list)
{
@@ -914,6 +916,116 @@ rdim_type_chunk_list_concat_in_place(RDIM_TypeChunkList *dst, RDIM_TypeChunkList
rdim_memzero_struct(to_push);
}
//- rjf: UDT members
RDI_PROC RDIM_UDTMember *
rdim_udt_member_chunk_list_push(RDIM_Arena *arena, RDIM_UDTMemberChunkList *list, RDI_U64 cap)
{
RDIM_UDTMemberChunkNode *n = list->last;
if(n == 0 || n->count >= n->cap)
{
n = rdim_push_array(arena, RDIM_UDTMemberChunkNode, 1);
n->cap = cap;
n->base_idx = list->total_count;
n->v = rdim_push_array(arena, RDIM_UDTMember, n->cap);
RDIM_SLLQueuePush(list->first, list->last, n);
list->chunk_count += 1;
}
RDIM_UDTMember *result = &n->v[n->count];
result->chunk = n;
n->count += 1;
list->total_count += 1;
return result;
}
RDI_PROC RDI_U64
rdim_idx_from_udt_member(RDIM_UDTMember *member)
{
RDI_U64 idx = 0;
if(member != 0 && member->chunk != 0)
{
idx = member->chunk->base_idx + (member - member->chunk->v) + 1;
}
return idx;
}
RDI_PROC void
rdim_udt_member_chunk_list_concat_in_place(RDIM_UDTMemberChunkList *dst, RDIM_UDTMemberChunkList *to_push)
{
for(RDIM_UDTMemberChunkNode *n = to_push->first; n != 0; n = n->next)
{
n->base_idx += dst->total_count;
}
if(dst->last != 0 && to_push->first != 0)
{
dst->last->next = to_push->first;
dst->last = to_push->last;
dst->chunk_count += to_push->chunk_count;
dst->total_count += to_push->total_count;
}
else if(dst->first == 0)
{
rdim_memcpy_struct(dst, to_push);
}
rdim_memzero_struct(to_push);
}
//- rjf: UDT enum values
RDI_PROC RDIM_UDTEnumVal *
rdim_udt_enum_val_chunk_list_push(RDIM_Arena *arena, RDIM_UDTEnumValChunkList *list, RDI_U64 cap)
{
RDIM_UDTEnumValChunkNode *n = list->last;
if(n == 0 || n->count >= n->cap)
{
n = rdim_push_array(arena, RDIM_UDTEnumValChunkNode, 1);
n->cap = cap;
n->base_idx = list->total_count;
n->v = rdim_push_array(arena, RDIM_UDTEnumVal, n->cap);
RDIM_SLLQueuePush(list->first, list->last, n);
list->chunk_count += 1;
}
RDIM_UDTEnumVal *result = &n->v[n->count];
result->chunk = n;
n->count += 1;
list->total_count += 1;
return result;
}
RDI_PROC RDI_U64
rdim_idx_from_udt_enum_val(RDIM_UDTEnumVal *enum_val)
{
RDI_U64 idx = 0;
if(enum_val != 0 && enum_val->chunk != 0)
{
idx = enum_val->chunk->base_idx + (enum_val - enum_val->chunk->v) + 1;
}
return idx;
}
RDI_PROC void
rdim_udt_enum_val_chunk_list_concat_in_place(RDIM_UDTEnumValChunkList *dst, RDIM_UDTEnumValChunkList *to_push)
{
for(RDIM_UDTEnumValChunkNode *n = to_push->first; n != 0; n = n->next)
{
n->base_idx += dst->total_count;
}
if(dst->last != 0 && to_push->first != 0)
{
dst->last->next = to_push->first;
dst->last = to_push->last;
dst->chunk_count += to_push->chunk_count;
dst->total_count += to_push->total_count;
}
else if(dst->first == 0)
{
rdim_memcpy_struct(dst, to_push);
}
rdim_memzero_struct(to_push);
}
//- rjf: UDTs
RDI_PROC RDIM_UDT *
rdim_udt_chunk_list_push(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDI_U64 cap)
{
+18 -14
View File
@@ -723,6 +723,7 @@ struct RDIM_TypeChunkList
typedef struct RDIM_UDTMember RDIM_UDTMember;
struct RDIM_UDTMember
{
struct RDIM_UDTMemberChunkNode *chunk;
RDIM_UDTMember *next;
RDI_MemberKind kind;
RDIM_String8 name;
@@ -730,13 +731,6 @@ struct RDIM_UDTMember
RDI_U32 off;
};
typedef struct RDIM_UDTMemberNode RDIM_UDTMemberNode;
struct RDIM_UDTMemberNode
{
RDIM_UDTMemberNode *next;
RDIM_UDTMember *v;
};
typedef struct RDIM_UDTMemberChunkNode RDIM_UDTMemberChunkNode;
struct RDIM_UDTMemberChunkNode
{
@@ -761,18 +755,12 @@ struct RDIM_UDTMemberChunkList
typedef struct RDIM_UDTEnumVal RDIM_UDTEnumVal;
struct RDIM_UDTEnumVal
{
struct RDIM_UDTEnumValChunkNode *chunk;
RDIM_UDTEnumVal *next;
RDIM_String8 name;
RDI_U64 val;
};
typedef struct RDIM_UDTEnumValNode RDIM_UDTEnumValNode;
struct RDIM_UDTEnumValNode
{
RDIM_UDTEnumValNode *next;
RDIM_UDTEnumVal *v;
};
typedef struct RDIM_UDTEnumValChunkNode RDIM_UDTEnumValChunkNode;
struct RDIM_UDTEnumValChunkNode
{
@@ -1012,6 +1000,8 @@ struct RDIM_BakeParams
RDIM_UnitChunkList units;
RDIM_TypeChunkList types;
RDIM_UDTChunkList udts;
RDIM_UDTMemberChunkList members;
RDIM_UDTEnumValChunkList enum_vals;
RDIM_SrcFileChunkList src_files;
RDIM_LineTableChunkList line_tables;
RDIM_SymbolChunkList global_variables;
@@ -1636,12 +1626,26 @@ RDI_PROC void rdim_unit_chunk_list_concat_in_place(RDIM_UnitChunkList *dst, RDIM
////////////////////////////////
//~ rjf: [Building] Type Info & UDT Building
//- rjf: type nodes
RDI_PROC RDIM_Type *rdim_type_chunk_list_push(RDIM_Arena *arena, RDIM_TypeChunkList *list, RDI_U64 cap);
RDI_PROC RDI_U64 rdim_idx_from_type(RDIM_Type *type);
RDI_PROC void rdim_type_chunk_list_concat_in_place(RDIM_TypeChunkList *dst, RDIM_TypeChunkList *to_push);
//- rjf: UDT members
RDI_PROC RDIM_UDTMember *rdim_udt_member_chunk_list_push(RDIM_Arena *arena, RDIM_UDTMemberChunkList *list, RDI_U64 cap);
RDI_PROC RDI_U64 rdim_idx_from_udt_member(RDIM_UDTMember *member);
RDI_PROC void rdim_udt_member_chunk_list_concat_in_place(RDIM_UDTMemberChunkList *dst, RDIM_UDTMemberChunkList *to_push);
//- rjf: UDT enum values
RDI_PROC RDIM_UDTEnumVal *rdim_udt_enum_val_chunk_list_push(RDIM_Arena *arena, RDIM_UDTEnumValChunkList *list, RDI_U64 cap);
RDI_PROC RDI_U64 rdim_idx_from_udt_enum_val(RDIM_UDTEnumVal *enum_val);
RDI_PROC void rdim_udt_enum_val_chunk_list_concat_in_place(RDIM_UDTEnumValChunkList *dst, RDIM_UDTEnumValChunkList *to_push);
//- rjf: UDTs
RDI_PROC RDIM_UDT *rdim_udt_chunk_list_push(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDI_U64 cap);
RDI_PROC RDI_U64 rdim_idx_from_udt(RDIM_UDT *udt);
RDI_PROC void rdim_udt_chunk_list_concat_in_place(RDIM_UDTChunkList *dst, RDIM_UDTChunkList *to_push);
RDI_PROC RDIM_UDTMember *rdim_udt_push_member(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDIM_UDT *udt);
RDI_PROC RDIM_UDTEnumVal *rdim_udt_push_enum_val(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDIM_UDT *udt);
+63 -16
View File
@@ -2266,13 +2266,19 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
if(lane_idx() == 0)
{
p2r2_shared->lanes_udts = push_array(arena, RDIM_UDTChunkList, lane_count());
p2r2_shared->lanes_members = push_array(arena, RDIM_UDTMemberChunkList, lane_count());
p2r2_shared->lanes_enum_vals = push_array(arena, RDIM_UDTEnumValChunkList, lane_count());
}
lane_sync();
//- rjf: do wide fill
{
U64 udts_chunk_cap = 1024;
U64 udts_chunk_cap = 4096;
U64 members_chunk_cap = 4096;
U64 enum_vals_chunk_cap = 4096;
RDIM_UDTChunkList *udts = &p2r2_shared->lanes_udts[lane_idx()];
RDIM_UDTMemberChunkList *members = &p2r2_shared->lanes_members[lane_idx()];
RDIM_UDTEnumValChunkList *enum_vals = &p2r2_shared->lanes_enum_vals[lane_idx()];
Rng1U64 range = lane_range(itype_opl);
for EachInRange(idx, range)
{
@@ -2404,6 +2410,7 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
}
// rjf: process field
RDIM_UDTMember *new_member = 0;
switch(field_kind)
{
//- rjf: unhandled/invalid cases
@@ -2459,11 +2466,12 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
next_read_ptr = name.str+name.size+1;
// rjf: emit member
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_DataField;
mem->name = name;
mem->type = p2r_type_ptr_from_itype(lf->itype);
mem->off = (U32)offset64;
new_member = mem;
}break;
//- rjf: STMEMBER
@@ -2480,10 +2488,11 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
next_read_ptr = name.str+name.size+1;
// rjf: emit member
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_StaticData;
mem->name = name;
mem->type = p2r_type_ptr_from_itype(lf->itype);
new_member = mem;
}break;
//- rjf: METHOD
@@ -2557,27 +2566,30 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
{
default:
{
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_Method;
mem->name = name;
mem->type = method_type;
new_member = mem;
}break;
case CV_MethodProp_Static:
{
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_StaticMethod;
mem->name = name;
mem->type = method_type;
new_member = mem;
}break;
case CV_MethodProp_Virtual:
case CV_MethodProp_PureVirtual:
case CV_MethodProp_Intro:
case CV_MethodProp_PureIntro:
{
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_VirtualMethod;
mem->name = name;
mem->type = method_type;
new_member = mem;
}break;
}
}
@@ -2612,29 +2624,30 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
{
default:
{
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_Method;
mem->name = name;
mem->type = method_type;
new_member = mem;
}break;
case CV_MethodProp_Static:
{
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_StaticMethod;
mem->name = name;
mem->type = method_type;
new_member = mem;
}break;
case CV_MethodProp_Virtual:
case CV_MethodProp_PureVirtual:
case CV_MethodProp_Intro:
case CV_MethodProp_PureIntro:
{
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_VirtualMethod;
mem->name = name;
mem->type = method_type;
new_member = mem;
}break;
}
}break;
@@ -2651,10 +2664,11 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
next_read_ptr = name.str+name.size+1;
// rjf: emit member
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_NestedType;
mem->name = name;
mem->type = p2r_type_ptr_from_itype(lf->itype);
new_member = mem;
}break;
//- rjf: NESTTYPEEX
@@ -2671,10 +2685,11 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
next_read_ptr = name.str+name.size+1;
// rjf: emit member
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_NestedType;
mem->name = name;
mem->type = p2r_type_ptr_from_itype(lf->itype);
new_member = mem;
}break;
//- rjf: BCLASS
@@ -2692,10 +2707,11 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
next_read_ptr = offset_ptr+offset.encoded_size;
// rjf: emit member
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_Base;
mem->type = p2r_type_ptr_from_itype(lf->itype);
mem->off = (U32)offset64;
new_member = mem;
}break;
//- rjf: VBCLASS/IVBCLASS
@@ -2717,9 +2733,10 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
next_read_ptr = (U8 *)(lf+1);
// rjf: emit member
RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt);
RDIM_UDTMember *mem = rdim_udt_member_chunk_list_push(arena, members, members_chunk_cap);
mem->kind = RDI_MemberKind_VirtualBase;
mem->type = p2r_type_ptr_from_itype(lf->itype);
new_member = mem;
}break;
//- rjf: VFUNCTAB
@@ -2735,6 +2752,16 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
}break;
}
// rjf: add member to UDT
if(new_member != 0)
{
if(dst_udt->first_member == 0)
{
dst_udt->first_member = new_member;
}
dst_udt->member_count += 1;
}
// rjf: align-up next field
next_read_ptr = (U8 *)AlignPow2((U64)next_read_ptr, 4);
}
@@ -2825,6 +2852,7 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
}
// rjf: process field
RDIM_UDTEnumVal *new_enum_val = 0;
switch(field_kind)
{
//- rjf: unhandled/invalid cases
@@ -2877,12 +2905,23 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
next_read_ptr = name.str+name.size+1;
// rjf: emit member
RDIM_UDTEnumVal *enum_val = rdim_udt_push_enum_val(arena, udts, dst_udt);
RDIM_UDTEnumVal *enum_val = rdim_udt_enum_val_chunk_list_push(arena, enum_vals, enum_vals_chunk_cap);
enum_val->name = name;
enum_val->val = val64;
new_enum_val = enum_val;
}break;
}
// rjf: push new enum val to udt
if(new_enum_val != 0)
{
if(dst_udt->first_enum_val == 0)
{
dst_udt->first_enum_val = new_enum_val;
}
dst_udt->enum_val_count += 1;
}
// rjf: align-up next field
next_read_ptr = (U8 *)AlignPow2((U64)next_read_ptr, 4);
}
@@ -2898,6 +2937,8 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
}
lane_sync();
RDIM_UDTChunkList *lanes_udts = p2r2_shared->lanes_udts;
RDIM_UDTMemberChunkList *lanes_members = p2r2_shared->lanes_members;
RDIM_UDTEnumValChunkList *lanes_enum_vals = p2r2_shared->lanes_enum_vals;
//////////////////////////////////////////////////////////////
//- rjf: join all UDTs
@@ -2907,10 +2948,14 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
for EachIndex(idx, lane_count())
{
rdim_udt_chunk_list_concat_in_place(&p2r2_shared->all_udts, &lanes_udts[idx]);
rdim_udt_member_chunk_list_concat_in_place(&p2r2_shared->all_members, &lanes_members[idx]);
rdim_udt_enum_val_chunk_list_concat_in_place(&p2r2_shared->all_enum_vals, &lanes_enum_vals[idx]);
}
}
lane_sync();
RDIM_UDTChunkList all_udts = p2r2_shared->all_udts;
RDIM_UDTMemberChunkList all_members = p2r2_shared->all_members;
RDIM_UDTEnumValChunkList all_enum_vals = p2r2_shared->all_enum_vals;
//////////////////////////////////////////////////////////////
//- rjf: produce symbols from all streams
@@ -3957,6 +4002,8 @@ p2r2_convert(Arena *arena, P2R_ConvertParams *params)
result.units = all_units;
result.types = all_types;
result.udts = all_udts;
result.members = all_members;
result.enum_vals = all_enum_vals;
result.src_files = all_src_files;
result.line_tables = all_line_tables;
result.global_variables = all_global_variables;
+4
View File
@@ -111,8 +111,12 @@ struct P2R2_Shared
RDIM_TypeChunkList all_types__pre_typedefs;
RDIM_UDTChunkList *lanes_udts;
RDIM_UDTMemberChunkList *lanes_members;
RDIM_UDTEnumValChunkList *lanes_enum_vals;
RDIM_UDTChunkList all_udts;
RDIM_UDTMemberChunkList all_members;
RDIM_UDTEnumValChunkList all_enum_vals;
RDIM_SymbolChunkList *lanes_procedures;
RDIM_SymbolChunkList *lanes_global_variables;
+39 -25
View File
@@ -778,12 +778,12 @@ rdim2_bake(Arena *arena, RDIM_BakeParams *params)
}
if(lane_idx() == lane_from_task_idx(5))
{
rdim2_shared->baked_udts.members_count = params->udts.total_member_count+1;
rdim2_shared->baked_udts.members_count = params->members.total_count+1;
rdim2_shared->baked_udts.members = push_array(arena, RDI_Member, rdim2_shared->baked_udts.members_count);
}
if(lane_idx() == lane_from_task_idx(6))
{
rdim2_shared->baked_udts.enum_members_count = params->udts.total_enum_val_count+1;
rdim2_shared->baked_udts.enum_members_count = params->enum_vals.total_count+1;
rdim2_shared->baked_udts.enum_members = push_array(arena, RDI_EnumMember, rdim2_shared->baked_udts.enum_members_count);
}
if(lane_idx() == lane_from_task_idx(7))
@@ -919,6 +919,40 @@ rdim2_bake(Arena *arena, RDIM_BakeParams *params)
}
}
//- rjf: bake UDT members
ProfScope("bake UDT members")
{
for EachNode(n, RDIM_UDTMemberChunkNode, params->members.first)
{
Rng1U64 range = lane_range(n->count);
for EachInRange(n_idx, range)
{
RDIM_UDTMember *src_member = &n->v[n_idx];
RDI_Member *dst_member = &rdim2_shared->baked_udts.members[n->base_idx + n_idx + 1];
dst_member->kind = src_member->kind;
dst_member->name_string_idx = rdim_bake_idx_from_string(bake_strings, src_member->name);
dst_member->type_idx = (RDI_U32)rdim_idx_from_type(src_member->type); // TODO(rjf): @u64_to_u32
dst_member->off = src_member->off;
}
}
}
//- rjf: bake UDT enum vals
ProfScope("bake UDT enum vals")
{
for EachNode(n, RDIM_UDTEnumValChunkNode, params->enum_vals.first)
{
Rng1U64 range = lane_range(n->count);
for EachInRange(n_idx, range)
{
RDIM_UDTEnumVal *src_member = &n->v[n_idx];
RDI_EnumMember *dst_member = &rdim2_shared->baked_udts.enum_members[n->base_idx + n_idx + 1];
dst_member->name_string_idx = rdim_bake_idx_from_string(bake_strings, src_member->name);
dst_member->val = src_member->val;
}
}
}
//- rjf: bake UDTs
ProfScope("bake UDTs")
{
@@ -936,40 +970,20 @@ rdim2_bake(Arena *arena, RDIM_BakeParams *params)
dst_udt->line = src_udt->line;
dst_udt->col = src_udt->col;
#if 0
//- rjf: fill members
//- rjf: fill member info
if(src_udt->member_count != 0)
{
dst_udt->member_first = dst_member_idx;
dst_udt->member_first = rdim_idx_from_udt_member(src_udt->first_member);
dst_udt->member_count = src_udt->member_count;
for(RDIM_UDTMember *src_member = src_udt->first_member;
src_member != 0;
src_member = src_member->next, dst_member_idx += 1)
{
RDI_Member *dst_member = &members[dst_member_idx];
dst_member->kind = src_member->kind;
dst_member->name_string_idx = rdim_bake_idx_from_string(strings, src_member->name);
dst_member->type_idx = (RDI_U32)rdim_idx_from_type(src_member->type); // TODO(rjf): @u64_to_u32
dst_member->off = src_member->off;
}
}
//- rjf: fill enum members
else if(src_udt->enum_val_count != 0)
{
dst_udt->flags |= RDI_UDTFlag_EnumMembers;
dst_udt->member_first = dst_enum_member_idx;
dst_udt->member_first = rdim_idx_from_udt_enum_val(src_udt->first_enum_val);
dst_udt->member_count = src_udt->enum_val_count;
for(RDIM_UDTEnumVal *src_member = src_udt->first_enum_val;
src_member != 0;
src_member = src_member->next, dst_enum_member_idx += 1)
{
RDI_EnumMember *dst_member = &enum_members[dst_enum_member_idx];
dst_member->name_string_idx = rdim_bake_idx_from_string(strings, src_member->name);
dst_member->val = src_member->val;
}
}
#endif
}
}
}