MVMObject * MVM_proc_clargs(MVMThreadContext *tc) { MVMInstance * const instance = tc->instance; MVMObject *clargs = instance->clargs; if (!clargs) { clargs = MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_array_type); MVMROOT(tc, clargs, { const MVMint64 num_clargs = instance->num_clargs; MVMint64 count; MVMString *prog_string = MVM_string_utf8_decode(tc, instance->VMString, instance->prog_name, strlen(instance->prog_name)); MVMObject *boxed_str = MVM_repr_box_str(tc, instance->boot_types.BOOTStr, prog_string); MVM_repr_push_o(tc, clargs, boxed_str); for (count = 0; count < num_clargs; count++) { char *raw_clarg = instance->raw_clargs[count]; MVMString *string = MVM_string_utf8_decode(tc, instance->VMString, raw_clarg, strlen(raw_clarg)); boxed_str = MVM_repr_box_str(tc, instance->boot_types.BOOTStr, string); MVM_repr_push_o(tc, clargs, boxed_str); } });
/* Read handler. */ static void on_read(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf) { ReadInfo *ri = (ReadInfo *)handle->data; MVMThreadContext *tc = ri->tc; MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); MVMAsyncTask *t = MVM_io_eventloop_get_active_work(tc, ri->work_idx); MVM_repr_push_o(tc, arr, t->body.schedulee); if (nread >= 0) { MVMROOT(tc, t, { MVMROOT(tc, arr, { MVMArray *res_buf; /* Push the sequence number. */ MVMObject *seq_boxed = MVM_repr_box_int(tc, tc->instance->boot_types.BOOTInt, ri->seq_number++); MVM_repr_push_o(tc, arr, seq_boxed); /* Produce a buffer and push it. */ res_buf = (MVMArray *)MVM_repr_alloc_init(tc, ri->buf_type); res_buf->body.slots.i8 = (MVMint8 *)buf->base; res_buf->body.start = 0; res_buf->body.ssize = buf->len; res_buf->body.elems = nread; MVM_repr_push_o(tc, arr, (MVMObject *)res_buf); /* Finally, no error. */ MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTStr); }); });
MVMObject * MVM_radix(MVMThreadContext *tc, MVMint64 radix, MVMString *str, MVMint64 offset, MVMint64 flag) { MVMObject *result; MVMint64 zvalue = 0; MVMint64 zbase = 1; MVMint64 chars = MVM_string_graphs(tc, str); MVMint64 value = zvalue; MVMint64 base = zbase; MVMint64 pos = -1; MVMuint16 neg = 0; MVMint64 ch; if (radix > 36) { MVM_exception_throw_adhoc(tc, "Cannot convert radix of %d (max 36)", radix); } ch = (offset < chars) ? MVM_string_get_grapheme_at_nocheck(tc, str, offset) : 0; if ((flag & 0x02) && (ch == '+' || ch == '-')) { neg = (ch == '-'); offset++; ch = (offset < chars) ? MVM_string_get_grapheme_at_nocheck(tc, str, offset) : 0; } while (offset < chars) { if (ch >= '0' && ch <= '9') ch = ch - '0'; else if (ch >= 'a' && ch <= 'z') ch = ch - 'a' + 10; else if (ch >= 'A' && ch <= 'Z') ch = ch - 'A' + 10; else break; if (ch >= radix) break; zvalue = zvalue * radix + ch; zbase = zbase * radix; offset++; pos = offset; if (ch != 0 || !(flag & 0x04)) { value=zvalue; base=zbase; } if (offset >= chars) break; ch = MVM_string_get_grapheme_at_nocheck(tc, str, offset); if (ch != '_') continue; offset++; if (offset >= chars) break; ch = MVM_string_get_grapheme_at_nocheck(tc, str, offset); } if (neg || flag & 0x01) { value = -value; } /* initialize the object */ result = MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_array_type); MVMROOT(tc, result, { MVMObject *box_type = MVM_hll_current(tc)->int_box_type; MVMROOT(tc, box_type, { MVMObject *boxed = MVM_repr_box_int(tc, box_type, value); MVM_repr_push_o(tc, result, boxed); boxed = MVM_repr_box_int(tc, box_type, base); MVM_repr_push_o(tc, result, boxed); boxed = MVM_repr_box_int(tc, box_type, pos); MVM_repr_push_o(tc, result, boxed); }); });
/* Signal callback; dispatches schedulee to the queue. */ static void signal_cb(uv_signal_t *handle, int sig_num) { SignalInfo *si = (SignalInfo *)handle->data; MVMThreadContext *tc = si->tc; MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); MVMAsyncTask *t = (MVMAsyncTask *)MVM_repr_at_pos_o(tc, tc->instance->event_loop_active, si->work_idx); MVM_repr_push_o(tc, arr, t->body.schedulee); MVMROOT(tc, t, { MVMROOT(tc, arr, { MVMObject *sig_num_boxed = MVM_repr_box_int(tc, tc->instance->boot_types.BOOTInt, sig_num); MVM_repr_push_o(tc, arr, sig_num_boxed); }); });
/* Completion handler for an asynchronous write. */ static void on_write(uv_write_t *req, int status) { SpawnWriteInfo *wi = (SpawnWriteInfo *)req->data; MVMThreadContext *tc = wi->tc; MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); MVMAsyncTask *t = (MVMAsyncTask *)MVM_repr_at_pos_o(tc, tc->instance->event_loop_active, wi->work_idx); MVM_repr_push_o(tc, arr, t->body.schedulee); if (status >= 0) { MVMROOT(tc, arr, { MVMROOT(tc, t, { MVMObject *bytes_box = MVM_repr_box_int(tc, tc->instance->boot_types.BOOTInt, wi->buf.len); MVM_repr_push_o(tc, arr, bytes_box); }); });
/* Dumps data from all threads into an array of per-thread data. */ static MVMObject * dump_data(MVMThreadContext *tc) { MVMObject *threads_array; ProfDumpStrs pds; /* We'll allocate the data in gen2, but as we want to keep it, but to be * sure we don't trigger a GC run. */ MVM_gc_allocate_gen2_default_set(tc); /* Some string constants to re-use. */ pds.total_time = str(tc, "total_time"); pds.call_graph = str(tc, "call_graph"); pds.name = str(tc, "name"); pds.id = str(tc, "id"); pds.file = str(tc, "file"); pds.line = str(tc, "line"); pds.entries = str(tc, "entries"); pds.spesh_entries = str(tc, "spesh_entries"); pds.jit_entries = str(tc, "jit_entries"); pds.inlined_entries = str(tc, "inlined_entries"); pds.inclusive_time = str(tc, "inclusive_time"); pds.exclusive_time = str(tc, "exclusive_time"); pds.callees = str(tc, "callees"); pds.allocations = str(tc, "allocations"); pds.type = str(tc, "type"); pds.count = str(tc, "count"); pds.spesh = str(tc, "spesh"); pds.jit = str(tc, "jit"); pds.gcs = str(tc, "gcs"); pds.time = str(tc, "time"); pds.full = str(tc, "full"); pds.cleared_bytes = str(tc, "cleared_bytes"); pds.retained_bytes = str(tc, "retained_bytes"); pds.promoted_bytes = str(tc, "promoted_bytes"); pds.gen2_roots = str(tc, "gen2_roots"); pds.osr = str(tc, "osr"); pds.deopt_one = str(tc, "deopt_one"); pds.deopt_all = str(tc, "deopt_all"); pds.spesh_time = str(tc, "spesh_time"); pds.native_lib = str(tc, "native library"); /* Build up threads array. */ /* XXX Only main thread for now. */ threads_array = new_array(tc); if (tc->prof_data) MVM_repr_push_o(tc, threads_array, dump_thread_data(tc, &pds, tc->prof_data)); /* Switch back to default allocation and return result; */ MVM_gc_allocate_gen2_default_clear(tc); return threads_array; }
/* Read handler. */ static void on_read(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf) { ReadInfo *ri = (ReadInfo *)handle->data; MVMThreadContext *tc = ri->tc; MVMObject *arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); MVMAsyncTask *t = (MVMAsyncTask *)MVM_repr_at_pos_o(tc, tc->instance->event_loop_active, ri->work_idx); MVM_repr_push_o(tc, arr, t->body.schedulee); if (nread > 0) { MVMROOT(tc, t, { MVMROOT(tc, arr, { /* Push the sequence number. */ MVMObject *seq_boxed = MVM_repr_box_int(tc, tc->instance->boot_types.BOOTInt, ri->seq_number++); MVM_repr_push_o(tc, arr, seq_boxed); /* Either need to produce a buffer or decode characters. */ if (ri->ds) { MVMString *str; MVMObject *boxed_str; MVM_string_decodestream_add_bytes(tc, ri->ds, buf->base, nread); str = MVM_string_decodestream_get_all(tc, ri->ds); boxed_str = MVM_repr_box_str(tc, tc->instance->boot_types.BOOTStr, str); MVM_repr_push_o(tc, arr, boxed_str); } else { MVMArray *res_buf = (MVMArray *)MVM_repr_alloc_init(tc, ri->buf_type); res_buf->body.slots.i8 = buf->base; res_buf->body.start = 0; res_buf->body.ssize = nread; res_buf->body.elems = nread; MVM_repr_push_o(tc, arr, (MVMObject *)res_buf); } /* Finally, no error. */ MVM_repr_push_o(tc, arr, tc->instance->boot_types.BOOTStr); }); });
/* Sets the passed thread context's thread up so that we'll run a finalize * handler on it in the near future. */ static void finalize_handler_caller(MVMThreadContext *tc, void *sr_data) { MVMObject *handler = MVM_hll_current(tc)->finalize_handler; if (handler) { /* Drain the finalizing queue to an array. */ MVMObject *drain = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); while (tc->num_finalizing > 0) MVM_repr_push_o(tc, drain, tc->finalizing[--tc->num_finalizing]); /* Invoke the handler. */ handler = MVM_frame_find_invokee(tc, handler, NULL); MVM_args_setup_thunk(tc, NULL, MVM_RETURN_VOID, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_INV_ARG)); tc->cur_frame->args[0].o = drain; STABLE(handler)->invoke(tc, handler, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_INV_ARG), tc->cur_frame->args); } }
/* Returns the lines (backtrace) of an exception-object as an array. */ MVMObject * MVM_exception_backtrace_strings(MVMThreadContext *tc, MVMObject *ex_obj) { MVMException *ex; MVMFrame *cur_frame; MVMObject *arr; if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) ex = (MVMException *)ex_obj; else MVM_exception_throw_adhoc(tc, "Can only throw an exception object"); cur_frame = ex->body.origin; arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); MVMROOT(tc, arr, { while (cur_frame != NULL) { MVMObject *pobj = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTStr); MVM_repr_set_str(tc, pobj, cur_frame->static_info->body.name); MVM_repr_push_o(tc, arr, pobj); cur_frame = cur_frame->caller; } });
/* Dumps data from a single thread. */ static MVMObject * dump_thread_data(MVMThreadContext *tc, ProfDumpStrs *pds, const MVMProfileThreadData *ptd) { MVMObject *thread_hash = new_hash(tc); MVMObject *thread_gcs = new_array(tc); MVMuint32 i; /* Add time. */ MVM_repr_bind_key_o(tc, thread_hash, pds->total_time, box_i(tc, (ptd->end_time - ptd->start_time) / 1000)); /* Add call graph. */ if (ptd->call_graph) MVM_repr_bind_key_o(tc, thread_hash, pds->call_graph, dump_call_graph_node(tc, pds, ptd->call_graph)); /* Add GCs. */ for (i = 0; i < ptd->num_gcs; i++) { MVMObject *gc_hash = new_hash(tc); MVM_repr_bind_key_o(tc, gc_hash, pds->time, box_i(tc, ptd->gcs[i].time / 1000)); MVM_repr_bind_key_o(tc, gc_hash, pds->full, box_i(tc, ptd->gcs[i].full)); MVM_repr_bind_key_o(tc, gc_hash, pds->cleared_bytes, box_i(tc, ptd->gcs[i].cleared_bytes)); MVM_repr_bind_key_o(tc, gc_hash, pds->retained_bytes, box_i(tc, ptd->gcs[i].retained_bytes)); MVM_repr_bind_key_o(tc, gc_hash, pds->promoted_bytes, box_i(tc, ptd->gcs[i].promoted_bytes)); MVM_repr_bind_key_o(tc, gc_hash, pds->gen2_roots, box_i(tc, ptd->gcs[i].num_gen2roots)); MVM_repr_push_o(tc, thread_gcs, gc_hash); } MVM_repr_bind_key_o(tc, thread_hash, pds->gcs, thread_gcs); /* Add spesh time. */ MVM_repr_bind_key_o(tc, thread_hash, pds->spesh_time, box_i(tc, ptd->spesh_time / 1000)); return thread_hash; }
/* Returns the lines (backtrace) of an exception-object as an array. */ MVMObject * MVM_exception_backtrace_strings(MVMThreadContext *tc, MVMObject *ex_obj) { MVMException *ex; MVMFrame *cur_frame; MVMObject *arr; if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) ex = (MVMException *)ex_obj; else MVM_exception_throw_adhoc(tc, "Op 'backtracestrings' needs an exception object"); cur_frame = ex->body.origin; arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); MVMROOT(tc, arr, { MVMuint32 count = 0; while (cur_frame != NULL) { char *line = MVM_exception_backtrace_line(tc, cur_frame, count++); MVMString *line_str = MVM_string_utf8_decode(tc, tc->instance->VMString, line, strlen(line)); MVMObject *line_obj = MVM_repr_box_str(tc, tc->instance->boot_types.BOOTStr, line_str); MVM_repr_push_o(tc, arr, line_obj); cur_frame = cur_frame->caller; free(line); } });
/* Adds an method. */ static void add_attribute(MVMThreadContext *tc, MVMCallsite *callsite, MVMRegister *args) { MVMObject *self, *attr, *attributes; /* Get arguments. */ MVMArgProcContext arg_ctx; arg_ctx.named_used = NULL; MVM_args_proc_init(tc, &arg_ctx, callsite, args); MVM_args_checkarity(tc, &arg_ctx, 3, 3); self = MVM_args_get_pos_obj(tc, &arg_ctx, 0, MVM_ARG_REQUIRED).arg.o; attr = MVM_args_get_pos_obj(tc, &arg_ctx, 2, MVM_ARG_REQUIRED).arg.o; MVM_args_proc_cleanup(tc, &arg_ctx); /* Ensure we have the required representations. */ if (!self || !IS_CONCRETE(self) || REPR(self)->ID != MVM_REPR_ID_KnowHOWREPR) MVM_exception_throw_adhoc(tc, "KnowHOW methods must be called on object instance with REPR KnowHOWREPR"); if (REPR(attr)->ID != MVM_REPR_ID_KnowHOWAttributeREPR) MVM_exception_throw_adhoc(tc, "KnowHOW attributes must use KnowHOWAttributeREPR"); /* Add to method table. */ attributes = ((MVMKnowHOWREPR *)self)->body.attributes; MVM_repr_push_o(tc, attributes, attr); /* Return added attribute as result. */ MVM_args_set_result_obj(tc, attr, MVM_RETURN_CURRENT_FRAME); }
/* Locates all of the attributes. Puts them onto a flattened, ordered * list of attributes (populating the passed flat_list). Also builds * the index mapping for doing named lookups. Note index is not related * to the storage position. */ static MVMObject * index_mapping_and_flat_list(MVMThreadContext *tc, MVMObject *mro, MVMCPPStructREPRData *repr_data) { MVMInstance *instance = tc->instance; MVMObject *flat_list, *class_list, *attr_map_list; MVMint32 num_classes, i, current_slot = 0; MVMCPPStructNameMap *result; MVMint32 mro_idx = MVM_repr_elems(tc, mro); MVM_gc_root_temp_push(tc, (MVMCollectable **)&mro); flat_list = MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_array_type); MVM_gc_root_temp_push(tc, (MVMCollectable **)&flat_list); class_list = MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_array_type); MVM_gc_root_temp_push(tc, (MVMCollectable **)&class_list); attr_map_list = MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_array_type); MVM_gc_root_temp_push(tc, (MVMCollectable **)&attr_map_list); /* Walk through the parents list. */ while (mro_idx) { /* Get current class in MRO. */ MVMObject *type_info = MVM_repr_at_pos_o(tc, mro, --mro_idx); MVMObject *current_class = MVM_repr_at_pos_o(tc, type_info, 0); /* Get its local parents; make sure we're not doing MI. */ MVMObject *parents = MVM_repr_at_pos_o(tc, type_info, 2); MVMint32 num_parents = MVM_repr_elems(tc, parents); if (num_parents <= 1) { /* Get attributes and iterate over them. */ MVMObject *attributes = MVM_repr_at_pos_o(tc, type_info, 1); MVMIter * const attr_iter = (MVMIter *)MVM_iter(tc, attributes); MVMObject *attr_map = NULL; if (MVM_iter_istrue(tc, attr_iter)) { MVM_gc_root_temp_push(tc, (MVMCollectable **)&attr_iter); attr_map = MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_hash_type); MVM_gc_root_temp_push(tc, (MVMCollectable **)&attr_map); } while (MVM_iter_istrue(tc, attr_iter)) { MVMObject *current_slot_obj = MVM_repr_box_int(tc, MVM_hll_current(tc)->int_box_type, current_slot); MVMObject *attr, *name_obj; MVMString *name; MVM_repr_shift_o(tc, (MVMObject *)attr_iter); /* Get attribute. */ attr = MVM_iterval(tc, attr_iter); /* Get its name. */ name_obj = MVM_repr_at_key_o(tc, attr, instance->str_consts.name); name = MVM_repr_get_str(tc, name_obj); MVM_repr_bind_key_o(tc, attr_map, name, current_slot_obj); current_slot++; /* Push attr onto the flat list. */ MVM_repr_push_o(tc, flat_list, attr); } if (attr_map) { MVM_gc_root_temp_pop_n(tc, 2); } /* Add to class list and map list. */ MVM_repr_push_o(tc, class_list, current_class); MVM_repr_push_o(tc, attr_map_list, attr_map); } else { MVM_exception_throw_adhoc(tc, "CPPStruct representation does not support multiple inheritance"); } } MVM_gc_root_temp_pop_n(tc, 4); /* We can now form the name map. */ num_classes = MVM_repr_elems(tc, class_list); result = (MVMCPPStructNameMap *) MVM_malloc(sizeof(MVMCPPStructNameMap) * (1 + num_classes)); for (i = 0; i < num_classes; i++) { result[i].class_key = MVM_repr_at_pos_o(tc, class_list, i); result[i].name_map = MVM_repr_at_pos_o(tc, attr_map_list, i); } /* set the end to be NULL, it's useful for iteration. */ result[i].class_key = NULL; repr_data->name_to_index_mapping = result; return flat_list; }
/* Composes the meta-object. */ static void compose(MVMThreadContext *tc, MVMCallsite *callsite, MVMRegister *args) { MVMObject *self, *type_obj, *method_table, *attributes, *BOOTArray, *BOOTHash, *repr_info_hash, *repr_info, *type_info, *attr_info_list, *parent_info; MVMint64 num_attrs, i; /* Get arguments. */ MVMArgProcContext arg_ctx; arg_ctx.named_used = NULL; MVM_args_proc_init(tc, &arg_ctx, callsite, args); self = MVM_args_get_pos_obj(tc, &arg_ctx, 0, MVM_ARG_REQUIRED).arg.o; type_obj = MVM_args_get_pos_obj(tc, &arg_ctx, 1, MVM_ARG_REQUIRED).arg.o; MVM_args_proc_cleanup(tc, &arg_ctx); if (!self || !IS_CONCRETE(self) || REPR(self)->ID != MVM_REPR_ID_KnowHOWREPR) MVM_exception_throw_adhoc(tc, "KnowHOW methods must be called on object instance with REPR KnowHOWREPR"); /* Fill out STable. */ method_table = ((MVMKnowHOWREPR *)self)->body.methods; MVM_ASSIGN_REF(tc, STABLE(type_obj), STABLE(type_obj)->method_cache, method_table); STABLE(type_obj)->mode_flags = MVM_METHOD_CACHE_AUTHORITATIVE; STABLE(type_obj)->type_check_cache_length = 1; STABLE(type_obj)->type_check_cache = malloc(sizeof(MVMObject *)); MVM_ASSIGN_REF(tc, STABLE(type_obj), STABLE(type_obj)->type_check_cache[0], type_obj); /* Next steps will allocate, so make sure we keep hold of the type * object and ourself. */ MVM_gc_root_temp_push(tc, (MVMCollectable **)&self); MVM_gc_root_temp_push(tc, (MVMCollectable **)&type_obj); /* Use any attribute information to produce attribute protocol * data. The protocol consists of an array... */ BOOTArray = tc->instance->boot_types->BOOTArray; BOOTHash = tc->instance->boot_types->BOOTHash; MVM_gc_root_temp_push(tc, (MVMCollectable **)&BOOTArray); MVM_gc_root_temp_push(tc, (MVMCollectable **)&BOOTHash); repr_info = REPR(BOOTArray)->allocate(tc, STABLE(BOOTArray)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&repr_info); REPR(repr_info)->initialize(tc, STABLE(repr_info), repr_info, OBJECT_BODY(repr_info)); /* ...which contains an array per MRO entry (just us)... */ type_info = REPR(BOOTArray)->allocate(tc, STABLE(BOOTArray)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&type_info); REPR(type_info)->initialize(tc, STABLE(type_info), type_info, OBJECT_BODY(type_info)); MVM_repr_push_o(tc, repr_info, type_info); /* ...which in turn contains this type... */ MVM_repr_push_o(tc, type_info, type_obj); /* ...then an array of hashes per attribute... */ attr_info_list = REPR(BOOTArray)->allocate(tc, STABLE(BOOTArray)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&attr_info_list); REPR(attr_info_list)->initialize(tc, STABLE(attr_info_list), attr_info_list, OBJECT_BODY(attr_info_list)); MVM_repr_push_o(tc, type_info, attr_info_list); attributes = ((MVMKnowHOWREPR *)self)->body.attributes; MVM_gc_root_temp_push(tc, (MVMCollectable **)&attributes); num_attrs = REPR(attributes)->elems(tc, STABLE(attributes), attributes, OBJECT_BODY(attributes)); for (i = 0; i < num_attrs; i++) { MVMObject *attr_info = REPR(BOOTHash)->allocate(tc, STABLE(BOOTHash)); MVMKnowHOWAttributeREPR *attribute = (MVMKnowHOWAttributeREPR *) MVM_repr_at_pos_o(tc, attributes, i); MVM_gc_root_temp_push(tc, (MVMCollectable **)&attr_info); MVM_gc_root_temp_push(tc, (MVMCollectable **)&attribute); if (REPR((MVMObject *)attribute)->ID != MVM_REPR_ID_KnowHOWAttributeREPR) MVM_exception_throw_adhoc(tc, "KnowHOW attributes must use KnowHOWAttributeREPR"); REPR(attr_info)->initialize(tc, STABLE(attr_info), attr_info, OBJECT_BODY(attr_info)); REPR(attr_info)->ass_funcs->bind_key_boxed(tc, STABLE(attr_info), attr_info, OBJECT_BODY(attr_info), (MVMObject *)str_name, (MVMObject *)attribute->body.name); REPR(attr_info)->ass_funcs->bind_key_boxed(tc, STABLE(attr_info), attr_info, OBJECT_BODY(attr_info), (MVMObject *)str_type, attribute->body.type); if (attribute->body.box_target) { /* Merely having the key serves as a "yes". */ REPR(attr_info)->ass_funcs->bind_key_boxed(tc, STABLE(attr_info), attr_info, OBJECT_BODY(attr_info), (MVMObject *)str_box_target, attr_info); } MVM_repr_push_o(tc, attr_info_list, attr_info); MVM_gc_root_temp_pop_n(tc, 2); } /* ...followed by a list of parents (none). */ parent_info = REPR(BOOTArray)->allocate(tc, STABLE(BOOTArray)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&parent_info); REPR(parent_info)->initialize(tc, STABLE(parent_info), parent_info, OBJECT_BODY(parent_info)); MVM_repr_push_o(tc, type_info, parent_info); /* Finally, this all goes in a hash under the key 'attribute'. */ repr_info_hash = REPR(BOOTHash)->allocate(tc, STABLE(BOOTHash)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&repr_info_hash); REPR(repr_info_hash)->initialize(tc, STABLE(repr_info_hash), repr_info_hash, OBJECT_BODY(repr_info_hash)); REPR(repr_info_hash)->ass_funcs->bind_key_boxed(tc, STABLE(repr_info_hash), repr_info_hash, OBJECT_BODY(repr_info_hash), (MVMObject *)str_attribute, repr_info); /* Compose the representation using it. */ REPR(type_obj)->compose(tc, STABLE(type_obj), repr_info_hash); /* Clear temporary roots. */ MVM_gc_root_temp_pop_n(tc, 10); /* Return type object. */ MVM_args_set_result_obj(tc, type_obj, MVM_RETURN_CURRENT_FRAME); }
/* Returns a list of hashes containing file, line, sub and annotations. */ MVMObject * MVM_exception_backtrace(MVMThreadContext *tc, MVMObject *ex_obj) { MVMFrame *cur_frame; MVMObject *arr = NULL, *annotations = NULL, *row = NULL, *value = NULL; MVMuint32 count = 0; MVMString *k_file = NULL, *k_line = NULL, *k_sub = NULL, *k_anno = NULL; if (IS_CONCRETE(ex_obj) && REPR(ex_obj)->ID == MVM_REPR_ID_MVMException) cur_frame = ((MVMException *)ex_obj)->body.origin; else MVM_exception_throw_adhoc(tc, "Op 'backtrace' needs an exception object"); MVM_gc_root_temp_push(tc, (MVMCollectable **)&arr); MVM_gc_root_temp_push(tc, (MVMCollectable **)&annotations); MVM_gc_root_temp_push(tc, (MVMCollectable **)&row); MVM_gc_root_temp_push(tc, (MVMCollectable **)&value); MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_file); MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_line); MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_sub); MVM_gc_root_temp_push(tc, (MVMCollectable **)&k_anno); k_file = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "file"); k_line = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "line"); k_sub = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "sub"); k_anno = MVM_string_ascii_decode_nt(tc, tc->instance->VMString, "annotations"); arr = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); while (cur_frame != NULL) { MVMuint8 *cur_op = count ? cur_frame->return_address : cur_frame->throw_address; MVMuint32 offset = cur_op - cur_frame->effective_bytecode; MVMBytecodeAnnotation *annot = MVM_bytecode_resolve_annotation(tc, &cur_frame->static_info->body, offset > 0 ? offset - 1 : 0); MVMint32 fshi = annot ? (MVMint32)annot->filename_string_heap_index : -1; char *line_number = malloc(16); snprintf(line_number, 16, "%d", annot ? annot->line_number : 1); /* annotations hash will contain "file" and "line" */ annotations = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTHash); /* file */ if (fshi >= 0 && fshi < cur_frame->static_info->body.cu->body.num_strings) value = MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, cur_frame->static_info->body.cu->body.strings[fshi]); else value = MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, cur_frame->static_info->body.cu->body.filename); MVM_repr_bind_key_o(tc, annotations, k_file, value); /* line */ value = (MVMObject *)MVM_string_ascii_decode_nt(tc, tc->instance->VMString, line_number); value = MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, (MVMString *)value); MVM_repr_bind_key_o(tc, annotations, k_line, value); free(line_number); /* row will contain "sub" and "annotations" */ row = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTHash); MVM_repr_bind_key_o(tc, row, k_sub, cur_frame->code_ref); MVM_repr_bind_key_o(tc, row, k_anno, annotations); MVM_repr_push_o(tc, arr, row); cur_frame = cur_frame->caller; while (cur_frame && cur_frame->static_info->body.is_thunk) cur_frame = cur_frame->caller; count++; } MVM_gc_root_temp_pop_n(tc, 8); return arr; }
/* Enters the work loop. */ static void worker(MVMThreadContext *tc, MVMCallsite *callsite, MVMRegister *args) { MVMObject *updated_static_frames = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); MVMObject *previous_static_frames = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); tc->instance->speshworker_thread_id = tc->thread_obj->body.thread_id; MVMROOT2(tc, updated_static_frames, previous_static_frames, { while (1) { MVMObject *log_obj; MVMuint64 start_time; unsigned int interval_id; if (MVM_spesh_debug_enabled(tc)) start_time = uv_hrtime(); log_obj = MVM_repr_shift_o(tc, tc->instance->spesh_queue); if (MVM_spesh_debug_enabled(tc)) { MVM_spesh_debug_printf(tc, "Received Logs\n" "=============\n\n" "Was waiting %dus for logs on the log queue.\n\n", (int)((uv_hrtime() - start_time) / 1000)); } if (tc->instance->main_thread->prof_data) MVM_profiler_log_spesh_start(tc); interval_id = MVM_telemetry_interval_start(tc, "spesh worker consuming a log"); uv_mutex_lock(&(tc->instance->mutex_spesh_sync)); tc->instance->spesh_working = 1; uv_mutex_unlock(&(tc->instance->mutex_spesh_sync)); tc->instance->spesh_stats_version++; if (log_obj->st->REPR->ID == MVM_REPR_ID_MVMSpeshLog) { MVMSpeshLog *sl = (MVMSpeshLog *)log_obj; MVM_telemetry_interval_annotate((uintptr_t)sl->body.thread->body.tc, interval_id, "from this thread"); MVMROOT(tc, sl, { MVMThreadContext *stc; MVMuint32 i; MVMuint32 n; /* Update stats, and if we're logging dump each of them. */ tc->instance->spesh_stats_version++; if (MVM_spesh_debug_enabled(tc)) start_time = uv_hrtime(); MVM_spesh_stats_update(tc, sl, updated_static_frames); n = MVM_repr_elems(tc, updated_static_frames); if (MVM_spesh_debug_enabled(tc)) { MVM_spesh_debug_printf(tc, "Statistics Updated\n" "==================\n" "%d frames had their statistics updated in %dus.\n\n", (int)n, (int)((uv_hrtime() - start_time) / 1000)); for (i = 0; i < n; i++) { char *dump = MVM_spesh_dump_stats(tc, (MVMStaticFrame* ) MVM_repr_at_pos_o(tc, updated_static_frames, i)); MVM_spesh_debug_printf(tc, "%s==========\n\n", dump); MVM_free(dump); } } MVM_telemetry_interval_annotate((uintptr_t)n, interval_id, "stats for this many frames"); GC_SYNC_POINT(tc); /* Form a specialization plan. */ if (MVM_spesh_debug_enabled(tc)) start_time = uv_hrtime(); tc->instance->spesh_plan = MVM_spesh_plan(tc, updated_static_frames); if (MVM_spesh_debug_enabled(tc)) { n = tc->instance->spesh_plan->num_planned; MVM_spesh_debug_printf(tc, "Specialization Plan\n" "===================\n" "%u specialization(s) will be produced (planned in %dus).\n\n", n, (int)((uv_hrtime() - start_time) / 1000)); for (i = 0; i < n; i++) { char *dump = MVM_spesh_dump_planned(tc, &(tc->instance->spesh_plan->planned[i])); MVM_spesh_debug_printf(tc, "%s==========\n\n", dump); MVM_free(dump); } } MVM_telemetry_interval_annotate((uintptr_t)tc->instance->spesh_plan->num_planned, interval_id, "this many specializations planned"); GC_SYNC_POINT(tc); /* Implement the plan and then discard it. */ n = tc->instance->spesh_plan->num_planned; for (i = 0; i < n; i++) { MVM_spesh_candidate_add(tc, &(tc->instance->spesh_plan->planned[i])); GC_SYNC_POINT(tc); } MVM_spesh_plan_destroy(tc, tc->instance->spesh_plan); tc->instance->spesh_plan = NULL; /* Clear up stats that didn't get updated for a while, * then add frames updated this time into the previously * updated array. */ MVM_spesh_stats_cleanup(tc, previous_static_frames); n = MVM_repr_elems(tc, updated_static_frames); for (i = 0; i < n; i++) MVM_repr_push_o(tc, previous_static_frames, MVM_repr_at_pos_o(tc, updated_static_frames, i)); /* Clear updated static frames array. */ MVM_repr_pos_set_elems(tc, updated_static_frames, 0); /* Allow the sending thread to produce more logs again, * putting a new spesh log in place if needed. */ stc = sl->body.thread->body.tc; if (stc && !sl->body.was_compunit_bumped) if (MVM_incr(&(stc->spesh_log_quota)) == 0) { stc->spesh_log = MVM_spesh_log_create(tc, sl->body.thread); MVM_telemetry_timestamp(stc, "logging restored after quota had run out"); } /* If needed, signal sending thread that it can continue. */ if (sl->body.block_mutex) { uv_mutex_lock(sl->body.block_mutex); MVM_store(&(sl->body.completed), 1); uv_cond_signal(sl->body.block_condvar); uv_mutex_unlock(sl->body.block_mutex); } { MVMSpeshLogEntry *entries = sl->body.entries; sl->body.entries = NULL; MVM_free(entries); } }); } else if (MVM_is_null(tc, log_obj)) {
/* Composes the meta-object. */ static void compose(MVMThreadContext *tc, MVMCallsite *callsite, MVMRegister *args) { MVMObject *self, *type_obj, *method_table, *attributes, *BOOTArray, *BOOTHash, *repr_info_hash, *repr_info, *type_info, *attr_info_list, *parent_info; MVMuint64 num_attrs, i; MVMInstance *instance = tc->instance; /* Get arguments. */ MVMArgProcContext arg_ctx; arg_ctx.named_used = NULL; MVM_args_proc_init(tc, &arg_ctx, callsite, args); MVM_args_checkarity(tc, &arg_ctx, 2, 2); self = MVM_args_get_pos_obj(tc, &arg_ctx, 0, MVM_ARG_REQUIRED).arg.o; type_obj = MVM_args_get_pos_obj(tc, &arg_ctx, 1, MVM_ARG_REQUIRED).arg.o; MVM_args_proc_cleanup(tc, &arg_ctx); if (!self || !IS_CONCRETE(self) || REPR(self)->ID != MVM_REPR_ID_KnowHOWREPR) MVM_exception_throw_adhoc(tc, "KnowHOW methods must be called on object instance with REPR KnowHOWREPR"); /* Fill out STable. */ method_table = ((MVMKnowHOWREPR *)self)->body.methods; MVM_ASSIGN_REF(tc, &(STABLE(type_obj)->header), STABLE(type_obj)->method_cache, method_table); STABLE(type_obj)->mode_flags = MVM_METHOD_CACHE_AUTHORITATIVE; STABLE(type_obj)->type_check_cache_length = 1; STABLE(type_obj)->type_check_cache = MVM_malloc(sizeof(MVMObject *)); MVM_ASSIGN_REF(tc, &(STABLE(type_obj)->header), STABLE(type_obj)->type_check_cache[0], type_obj); attributes = ((MVMKnowHOWREPR *)self)->body.attributes; /* Next steps will allocate, so make sure we keep hold of the type * object and ourself. */ MVM_gc_root_temp_push(tc, (MVMCollectable **)&attributes); MVM_gc_root_temp_push(tc, (MVMCollectable **)&type_obj); /* Use any attribute information to produce attribute protocol * data. The protocol consists of an array... */ BOOTArray = instance->boot_types.BOOTArray; BOOTHash = instance->boot_types.BOOTHash; MVM_gc_root_temp_push(tc, (MVMCollectable **)&BOOTArray); MVM_gc_root_temp_push(tc, (MVMCollectable **)&BOOTHash); repr_info = REPR(BOOTArray)->allocate(tc, STABLE(BOOTArray)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&repr_info); /* ...which contains an array per MRO entry (just us)... */ type_info = REPR(BOOTArray)->allocate(tc, STABLE(BOOTArray)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&type_info); MVM_repr_push_o(tc, repr_info, type_info); /* ...which in turn contains this type... */ MVM_repr_push_o(tc, type_info, type_obj); /* ...then an array of hashes per attribute... */ attr_info_list = REPR(BOOTArray)->allocate(tc, STABLE(BOOTArray)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&attr_info_list); MVM_repr_push_o(tc, type_info, attr_info_list); num_attrs = REPR(attributes)->elems(tc, STABLE(attributes), attributes, OBJECT_BODY(attributes)); for (i = 0; i < num_attrs; i++) { MVMObject *attr_info = REPR(BOOTHash)->allocate(tc, STABLE(BOOTHash)); MVMKnowHOWAttributeREPR *attribute = (MVMKnowHOWAttributeREPR *) MVM_repr_at_pos_o(tc, attributes, i); MVMROOT(tc, attr_info, { MVMROOT(tc, attribute, { if (REPR((MVMObject *)attribute)->ID != MVM_REPR_ID_KnowHOWAttributeREPR) MVM_exception_throw_adhoc(tc, "KnowHOW attributes must use KnowHOWAttributeREPR"); MVM_repr_init(tc, attr_info); MVM_repr_bind_key_o(tc, attr_info, instance->str_consts.name, (MVMObject *)attribute->body.name); MVM_repr_bind_key_o(tc, attr_info, instance->str_consts.type, attribute->body.type); if (attribute->body.box_target) { /* Merely having the key serves as a "yes". */ MVM_repr_bind_key_o(tc, attr_info, instance->str_consts.box_target, attr_info); } MVM_repr_push_o(tc, attr_info_list, attr_info); }); });
/* Dumps a call graph node. */ static MVMObject * dump_call_graph_node(MVMThreadContext *tc, ProfDumpStrs *pds, const MVMProfileCallNode *pcn) { MVMObject *node_hash = new_hash(tc); MVMuint32 i; /* Let's see if we're dealing with a native call or a regular moar call */ if (pcn->sf) { /* Try to resolve the code filename and line number. */ MVMBytecodeAnnotation *annot = MVM_bytecode_resolve_annotation(tc, &(pcn->sf->body), 0); MVMint32 fshi = annot ? (MVMint32)annot->filename_string_heap_index : -1; /* Add name of code object. */ MVM_repr_bind_key_o(tc, node_hash, pds->name, box_s(tc, pcn->sf->body.name)); /* Add line number and file name. */ if (fshi >= 0 && fshi < pcn->sf->body.cu->body.num_strings) MVM_repr_bind_key_o(tc, node_hash, pds->file, box_s(tc, MVM_cu_string(tc, pcn->sf->body.cu, fshi))); else if (pcn->sf->body.cu->body.filename) MVM_repr_bind_key_o(tc, node_hash, pds->file, box_s(tc, pcn->sf->body.cu->body.filename)); else MVM_repr_bind_key_o(tc, node_hash, pds->file, box_s(tc, tc->instance->str_consts.empty)); MVM_repr_bind_key_o(tc, node_hash, pds->line, box_i(tc, annot ? (MVMint32)annot->line_number : -1)); MVM_free(annot); /* Use static frame memory address to get a unique ID. */ MVM_repr_bind_key_o(tc, node_hash, pds->id, box_i(tc, (MVMint64)pcn->sf)); } else { MVMString *function_name_string = MVM_string_utf8_c8_decode(tc, tc->instance->VMString, pcn->native_target_name, strlen(pcn->native_target_name)); MVM_repr_bind_key_o(tc, node_hash, pds->name, box_s(tc, function_name_string)); MVM_repr_bind_key_o(tc, node_hash, pds->file, box_s(tc, pds->native_lib)); MVM_repr_bind_key_o(tc, node_hash, pds->line, box_i(tc, -2)); /* Use the address of the name string as unique ID. a hack, but oh well. */ MVM_repr_bind_key_o(tc, node_hash, pds->id, box_i(tc, (MVMint64)pcn->native_target_name)); } /* Entry counts. */ if (pcn->total_entries) MVM_repr_bind_key_o(tc, node_hash, pds->entries, box_i(tc, pcn->total_entries)); if (pcn->specialized_entries) MVM_repr_bind_key_o(tc, node_hash, pds->spesh_entries, box_i(tc, pcn->specialized_entries)); if (pcn->jit_entries) MVM_repr_bind_key_o(tc, node_hash, pds->jit_entries, box_i(tc, pcn->jit_entries)); if (pcn->inlined_entries) MVM_repr_bind_key_o(tc, node_hash, pds->inlined_entries, box_i(tc, pcn->inlined_entries)); /* Total (inclusive) time. */ MVM_repr_bind_key_o(tc, node_hash, pds->inclusive_time, box_i(tc, pcn->total_time / 1000)); /* OSR and deopt counts. */ if (pcn->osr_count) MVM_repr_bind_key_o(tc, node_hash, pds->osr, box_i(tc, pcn->osr_count)); if (pcn->deopt_one_count) MVM_repr_bind_key_o(tc, node_hash, pds->deopt_one, box_i(tc, pcn->deopt_one_count)); if (pcn->deopt_all_count) MVM_repr_bind_key_o(tc, node_hash, pds->deopt_all, box_i(tc, pcn->deopt_all_count)); /* Visit successors in the call graph, dumping them and working out the * exclusive time. */ if (pcn->num_succ) { MVMObject *callees = new_array(tc); MVMuint64 exclusive_time = pcn->total_time; for (i = 0; i < pcn->num_succ; i++) { MVM_repr_push_o(tc, callees, dump_call_graph_node(tc, pds, pcn->succ[i])); exclusive_time -= pcn->succ[i]->total_time; } MVM_repr_bind_key_o(tc, node_hash, pds->exclusive_time, box_i(tc, exclusive_time / 1000)); MVM_repr_bind_key_o(tc, node_hash, pds->callees, callees); } else { MVM_repr_bind_key_o(tc, node_hash, pds->exclusive_time, box_i(tc, pcn->total_time / 1000)); } if (pcn->num_alloc) { /* Emit allocations. */ MVMObject *alloc_list = new_array(tc); MVM_repr_bind_key_o(tc, node_hash, pds->allocations, alloc_list); for (i = 0; i < pcn->num_alloc; i++) { MVMObject *alloc_info = new_hash(tc); MVMProfileAllocationCount *alloc = &pcn->alloc[i]; MVMObject *type = pcn->alloc[i].type; MVM_repr_bind_key_o(tc, alloc_info, pds->id, box_i(tc, (MVMint64)type)); MVM_repr_bind_key_o(tc, alloc_info, pds->type, type); if (alloc->allocations_spesh) MVM_repr_bind_key_o(tc, alloc_info, pds->spesh, box_i(tc, alloc->allocations_spesh)); if (alloc->allocations_jit) MVM_repr_bind_key_o(tc, alloc_info, pds->jit, box_i(tc, alloc->allocations_jit)); MVM_repr_bind_key_o(tc, alloc_info, pds->count, box_i(tc, alloc->allocations_interp + alloc->allocations_spesh + alloc->allocations_jit)); MVM_repr_push_o(tc, alloc_list, alloc_info); } } return node_hash; }
MVMObject * MVM_radix(MVMThreadContext *tc, MVMint64 radix, MVMString *str, MVMint64 offset, MVMint64 flag) { MVMObject *result; MVMint64 zvalue = 0; MVMint64 zbase = 1; MVMint64 chars = MVM_string_graphs(tc, str); MVMint64 value = zvalue; MVMint64 base = zbase; MVMint64 pos = -1; MVMuint16 neg = 0; MVMint64 ch; if (radix > 36) { MVM_exception_throw_adhoc(tc, "Cannot convert radix of %"PRId64" (max 36)", radix); } ch = (offset < chars) ? MVM_string_get_grapheme_at_nocheck(tc, str, offset) : 0; if ((flag & 0x02) && (ch == '+' || ch == '-')) { neg = (ch == '-'); offset++; ch = (offset < chars) ? MVM_string_get_grapheme_at_nocheck(tc, str, offset) : 0; } while (offset < chars) { if (ch >= '0' && ch <= '9') ch = ch - '0'; /* fast-path for ASCII 0..9 */ else if (ch >= 'a' && ch <= 'z') ch = ch - 'a' + 10; else if (ch >= 'A' && ch <= 'Z') ch = ch - 'A' + 10; else if (ch >= 0xFF21 && ch <= 0xFF3A) ch = ch - 0xFF21 + 10; /* uppercase fullwidth */ else if (ch >= 0xFF41 && ch <= 0xFF5A) ch = ch - 0xFF41 + 10; /* lowercase fullwidth */ else if (ch > 0 && MVM_unicode_codepoint_has_property_value(tc, ch, MVM_UNICODE_PROPERTY_GENERAL_CATEGORY, MVM_unicode_cname_to_property_value_code(tc, MVM_UNICODE_PROPERTY_GENERAL_CATEGORY, STR_WITH_LEN("Nd")))) { /* As of Unicode 6.0.0, we know that Nd category numerals are within * the range 0..9 */ /* the string returned for NUMERIC_VALUE contains a floating point * value, so atoi will stop on the . in the string. This is fine * though, since we'd have to truncate the float regardless. */ ch = atoi(MVM_unicode_codepoint_get_property_cstr(tc, ch, MVM_UNICODE_PROPERTY_NUMERIC_VALUE)); } else break; if (ch >= radix) break; zvalue = zvalue * radix + ch; zbase = zbase * radix; offset++; pos = offset; if (ch != 0 || !(flag & 0x04)) { value=zvalue; base=zbase; } if (offset >= chars) break; ch = MVM_string_get_grapheme_at_nocheck(tc, str, offset); if (ch != '_') continue; offset++; if (offset >= chars) break; ch = MVM_string_get_grapheme_at_nocheck(tc, str, offset); } if (neg || flag & 0x01) { value = -value; } /* initialize the object */ result = MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_array_type); MVMROOT(tc, result, { MVMObject *box_type = MVM_hll_current(tc)->int_box_type; MVMROOT(tc, box_type, { MVMObject *boxed = MVM_repr_box_int(tc, box_type, value); MVM_repr_push_o(tc, result, boxed); boxed = MVM_repr_box_int(tc, box_type, base); MVM_repr_push_o(tc, result, boxed); boxed = MVM_repr_box_int(tc, box_type, pos); MVM_repr_push_o(tc, result, boxed); }); });
/* Timer callback; dispatches schedulee to the queue. */ static void timer_cb(uv_timer_t *handle) { TimerInfo *ti = (TimerInfo *)handle->data; MVMThreadContext *tc = ti->tc; MVMAsyncTask *t = MVM_io_eventloop_get_active_work(tc, ti->work_idx); MVM_repr_push_o(tc, t->body.queue, t->body.schedulee); }