/* datastruct.c -- Functions for creating CSBE data structures Copyright © 2022-2024 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include "include/csbe.h" #include "csbe_internal.h" static struct CsbeType *real_typedef(struct Csbe *csbe, struct CsbeType *type, unsigned *arrlen_out); static void defer_named_type(struct CsbeType *type, struct CsbeTypedef *td); static enum CsbeErr copy_named_type(struct Csbe *csbe, struct CsbeType *dest, struct CsbeType *src, unsigned arrlen); static void ebb_end(struct Csbe *csbe); static void finalize_previous_op(struct Csbe *csbe); static void flush_prev_op(struct Csbe *csbe); static void mark_var_usage(struct Csbe *csbe, unsigned var_num); static void mark_live_vars(struct Csbe *csbe, unsigned flag); static void clear_live_vars(struct Csbe *csbe); static void type_init(struct Csbe *csbe) { csbe->type->is_unbound = 0; csbe->type->is_defined = 0; csbe->type->is_array = 0; csbe->type->array_length = 1; } static void type_added(struct Csbe *csbe) { csbe->type++; csbe->type_slots_left--; if (csbe->type_slots_left) { type_init(csbe); } } void csbe_type_void(struct Csbe *csbe) { assert(csbe->type); assert(csbe->type_slots_left > 0); assert(!csbe->type->is_array); assert(!csbe->type->is_defined); csbe->type->is_defined = 1; csbe->type->is_struct = 0; csbe->type->num_fields = 0; csbe->type->is_array = 0; csbe->type->array_length = 1; type_added(csbe); } void csbe_type_simple(struct Csbe *csbe, enum CsbeTypeKind kind) { assert(csbe->type); assert(csbe->type_slots_left > 0); assert(!csbe->type->is_defined); csbe->type->is_defined = 1; csbe->type->simple_kind = kind; csbe->type->is_struct = 0; csbe->type->num_fields = 1; if (!csbe->type->is_array) { csbe->type->array_length = 1; } type_added(csbe); } enum CsbeErr csbe_type_named(struct Csbe *csbe, unsigned typedef_id) { enum CsbeErr status = CSBEE_OK; struct CsbeTypedef *td; assert(typedef_id < csbe->max_typedef_count); td = &csbe->typedefs[typedef_id]; assert(csbe->type); assert(!csbe->type->is_defined); assert(csbe->type_slots_left > 0); if (!td->type.is_defined) { assert(!csbe->defs_done); defer_named_type(csbe->type, td); } else { unsigned arrlen; struct CsbeType *realdef = real_typedef(csbe, &td->type, &arrlen); if (!realdef) return CSBEE_TYPE_TOO_LARGE; status = copy_named_type(csbe, csbe->type, &td->type, arrlen); } type_added(csbe); return status; } enum CsbeErr csbe_type_array(struct Csbe *csbe, unsigned array_length) { enum CsbeErr status = CSBEE_OK; assert(csbe->type); assert(csbe->type_slots_left > 0); if (!csbe->type->is_array) { csbe->type->is_array = 1; csbe->type->array_length = array_length; } else { /* Multi-dimensional array */ status = csbe_multiply_arraylengths(csbe, &csbe->type->array_length, csbe->type->array_length, array_length); } /* array length is added to next type, so no call to type_added here */ return status; } enum CsbeErr csbe_multiply_arraylengths(struct Csbe *csbe, unsigned *prod_out, unsigned l1, unsigned l2) { unsigned prod = l1 * l2; if (prod >= CSBE_ARRAY_MAX || (l2 != 0 && prod/l2 != l1)) { return SET_ERROR(CSBEE_TYPE_TOO_LARGE); } *prod_out = prod; return CSBEE_OK; } enum CsbeErr csbe_type_struct_start(struct Csbe *csbe, int num_fields, enum CsbePackMode packmode) { struct CsbeType *t, *elems; assert(csbe->type); assert(csbe->type_slots_left > 0); if (num_fields >= MAX_FIELDS) { return SET_ERROR(CSBEE_TYPE_TOO_LARGE); } t = csbe->type; t->packmode = packmode; t->is_struct = 1; t->num_fields = num_fields; if (num_fields) { elems = allocpa(csbe, sizeof(struct CsbeType), num_fields); if (!elems) goto outofmem; } else { elems = NULL; } t->elemtypes = elems; t->containing_type = csbe->containing_type; t->saved_slots_left = csbe->type_slots_left; csbe->containing_type = t; csbe->type_slots_left = num_fields; csbe->type = elems; /* place next type in elems[0] */ if (num_fields) { type_init(csbe); } return CSBEE_OK; outofmem: return CSBEE_OUT_OF_MEM; } enum CsbeErr csbe_type_struct_end(struct Csbe *csbe) { assert(csbe->type_slots_left == 0); csbe->type = csbe->containing_type; assert(!csbe->type->is_defined); csbe->type->is_defined = 1; csbe->containing_type = csbe->type->containing_type; csbe->type_slots_left = csbe->type->saved_slots_left; type_added(csbe); return CSBEE_OK; } enum CsbeErr csbe_set_num_typedefs(struct Csbe *csbe, unsigned count) { struct CsbeTypedef *arr; assert(csbe->typedefs == NULL); csbe->max_typedef_count = count; if (count != 0) { unsigned i; struct CsbeTypedef *td; arr = allocpa(csbe, sizeof(struct CsbeTypedef), count); if (!arr) return CSBEE_OUT_OF_MEM; csbe->typedefs = arr; td = &csbe->typedefs[0]; for (i = csbe->max_typedef_count; i-- > 0; td++) { td->type.is_defined = 0; td->refs = NULL; } } return CSBEE_OK; } enum CsbeErr csbe_set_num_datadefs(struct Csbe *csbe, unsigned count) { struct CsbeDatadef *arr; assert(csbe->datadefs == NULL); csbe->max_datadef_count = count; if (count != 0) { arr = allocpa(csbe, sizeof(struct CsbeDatadef), count); if (!arr) return CSBEE_OUT_OF_MEM; csbe->datadefs = arr; } return CSBEE_OK; } enum CsbeErr csbe_set_num_funcdefs(struct Csbe *csbe, unsigned count) { struct CsbeFuncdef *arr; assert(csbe->funcdefs == NULL); csbe->max_funcdef_count = count; if (count != 0) { arr = allocpa(csbe, sizeof(struct CsbeFuncdef), count); if (!arr) return CSBEE_OUT_OF_MEM; csbe->funcdefs = arr; } return CSBEE_OK; } void csbe_typedef_start(struct Csbe *csbe, unsigned id) { struct CsbeTypedef *td; assert(!csbe->defs_done); assert(!csbe->td); assert(!csbe->type); assert(csbe->typedefs != NULL); assert(id < csbe->max_typedef_count); td = &csbe->typedefs[id]; td->next = csbe->td; assert(!td->type.is_defined); csbe->td = td; csbe->type_slots_left = 1; csbe->containing_type = NULL; csbe->type = &td->type; type_init(csbe); } void csbe_typedef_end(struct Csbe *csbe) { assert(csbe->type_slots_left == 0); assert(csbe->containing_type == NULL); csbe->type = NULL; csbe->td = NULL; } enum CsbeErr csbe_funcdef_start(struct Csbe *csbe, unsigned id, int num_params, enum CsbeAbi abi, unsigned flags) { struct CsbeFuncdef *fd; struct CsbeType *params; assert(csbe->funcdefs != NULL); assert(id < csbe->max_funcdef_count); fd = &csbe->funcdefs[id]; fd->func_id = id; assert(!csbe->defs_done); assert(!csbe->type); fd->next = csbe->fd; csbe->fd = fd; if (num_params >= MAX_PARAMS) { return SET_ERROR(CSBEE_TOO_MANY_ITEMS); } params = allocpa(csbe, sizeof(struct CsbeType), num_params+1); if (!params) goto outofmem; fd->num_params = num_params; fd->paramtypes = params; fd->abi = abi; fd->flags = flags; fd->funcbody = NULL; fd->name = NULL; fd->namelen = 0; if ((flags & CSBEFD_MAIN) != 0) { assert(csbe->main_func == NULL); csbe->main_func = fd; } csbe->type_slots_left = 1 + num_params; csbe->type = params; /* place next type (return type) in paramtypes[0]. the pointers are later changed, see funcdef_ready */ type_init(csbe); csbe->containing_type = NULL; return CSBEE_OK; outofmem: return CSBEE_OUT_OF_MEM; } void csbe_funcdef_set_name(struct Csbe *csbe, const char *name, size_t len) { csbe->fd->name = name; csbe->fd->namelen = len; } void csbe_funcdef_end(struct Csbe *csbe) { struct CsbeFuncdef *fd; assert(csbe->type_slots_left == 0); assert(csbe->containing_type == NULL); fd = csbe->fd; fd->returntype = &fd->paramtypes[0]; fd->paramtypes++; /* TODO create a CsbeType for the function definition? */ csbe->type = NULL; csbe->num_funcs++; } void csbe_datadef_start(struct Csbe *csbe, unsigned id, unsigned flags) { struct CsbeDatadef *dd; assert(csbe->datadefs != NULL); assert(id < csbe->max_datadef_count); dd = &csbe->datadefs[id]; dd->data_id = id; assert(!csbe->type); dd->next = csbe->dd; dd->flags = flags; dd->name = NULL; dd->namelen = 0; dd->valueslots = NULL; csbe->dd = dd; csbe->type = &dd->type; csbe->type_slots_left = 1; csbe->containing_type = NULL; type_init(csbe); } void csbe_datadef_set_name(struct Csbe *csbe, const char *name, size_t len) { csbe->dd->name = name; csbe->dd->namelen = len; } static unsigned init_value_slots(const struct CsbeType *type, struct DatadefValueSlot **slots) { unsigned num_slots = 0; if (type->is_struct) { unsigned arraylen = (type->is_array ? type->array_length : 1); while (arraylen--) { unsigned structlen = type->num_fields; const struct CsbeType *fieldtype = type->elemtypes; for (; structlen--; fieldtype++) { num_slots += init_value_slots(fieldtype, slots); if (type->packmode == CSBEPM_UNION) break; } } } else { if (slots) { struct DatadefValueSlot *slot = (*slots)++; enum CsbeSlotType t; if (!type->is_array) { t = CSBEST_UINT64; } else if (type->simple_kind == CSBET_U8 || type->simple_kind == CSBET_I8 || type->simple_kind == CSBET_BOOL) { t = CSBEST_BYTES; } else { t = CSBEST_UINT64_ARRAY; } slot->slot_type = t; slot->array_length = type->array_length; } num_slots++; } return num_slots; } enum CsbeErr csbe_datadef_initval_start(struct Csbe *csbe) { struct DatadefValueSlot *slots; const struct CsbeType *type; unsigned num_slots; assert(csbe->dd != NULL); assert((csbe->dd->flags & CSBEDD_EXTERNAL) == 0); assert(csbe->dd->valueslots == NULL); assert(csbe->type_slots_left == 0); assert(csbe->containing_type == NULL); /* Allocate a "value slot" for each field/element */ type = &csbe->type[-1]; num_slots = init_value_slots(type, NULL); assert(num_slots >= 1); slots = allocpa(csbe, sizeof(struct DatadefValueSlot), num_slots); if (!slots) goto outofmem; csbe->value_slots_left = num_slots; csbe->valueslot = slots; csbe->dd->valueslots = slots; csbe->dd->num_valueslots = num_slots; init_value_slots(type, &slots); csbe->type = NULL; return CSBEE_OK; outofmem: return CSBEE_OUT_OF_MEM; } static struct DatadefValueSlot *next_value_slot(struct Csbe *csbe) { assert(csbe->dd != NULL); assert(csbe->value_slots_left > 0); assert(csbe->valueslot != NULL); csbe->value_slots_left--; return csbe->valueslot++; } void csbe_datadef_value_int(struct Csbe *csbe, CsbeUint64 value) { struct DatadefValueSlot *slot = next_value_slot(csbe); assert(slot->slot_type == CSBEST_UINT64); slot->u.uint64_value = value; } void csbe_datadef_value_bytearray(struct Csbe *csbe, const unsigned char *values, unsigned length) { struct DatadefValueSlot *slot = next_value_slot(csbe); assert(slot->slot_type == CSBEST_BYTES); assert(slot->array_length == length); slot->u.bytes = values; } void csbe_datadef_value_intarray(struct Csbe *csbe, const CsbeUint64 *values, unsigned length) { struct DatadefValueSlot *slot = next_value_slot(csbe); assert(slot->slot_type == CSBEST_UINT64_ARRAY); assert(slot->array_length == length); slot->u.uint64_array = values; } void csbe_datadef_initval_end(struct Csbe *csbe) { assert(csbe->dd != NULL); assert(csbe->dd->valueslots != NULL); assert(csbe->value_slots_left == 0); assert(csbe->type == NULL); } void csbe_datadef_end(struct Csbe *csbe) { assert(csbe->dd != NULL); csbe->type = NULL; csbe->num_data++; } struct CsbeLibrary *csbe_add_libimport(struct Csbe *csbe, const char *libname, unsigned libnamelen) { struct CsbeLibrary *lib = allocp(csbe, sizeof(struct CsbeLibrary)); if (!lib) return NULL; assert(libname); lib->name = libname; assert(libnamelen > 0); lib->namelen = libnamelen; lib->versions = NULL; lib->next = csbe->imported_libs; csbe->imported_libs = lib; return lib; } struct CsbeSymVer *csbe_define_symver(struct Csbe *csbe, struct CsbeLibrary *lib, const char *verprefix, unsigned verprefixlen, const char *vername, unsigned vernamelen) { struct CsbeSymVer *symver = allocp(csbe, sizeof(struct CsbeSymVer)); if (!symver) return NULL; assert(verprefix || verprefixlen == 0); symver->prefix = verprefix; symver->prefixlen = verprefixlen; assert(vername); symver->ver = vername; assert(vernamelen > 0); symver->verlen = vernamelen; /* TODO check that the symver comes after the last symver (in version order) */ if (lib) { /* Import */ symver->next = lib->versions; lib->versions = symver; } else { /* Export */ symver->next = csbe->exported_symvers; csbe->exported_symvers = symver; } return symver; } static enum CsbeErr importexport_decl(struct Csbe *csbe, enum CsbeSymbolType symtype, struct CsbeLibrary *lib, struct CsbeSymVer *symver, const char *name, unsigned namelen) { struct LibrarySymbol **inspoint; struct LibrarySymbol *sym; if (symver) { inspoint = &symver->syms; } else if (lib) { inspoint = &lib->unversioned_syms; } else { inspoint = &csbe->exported_unversioned_syms; } sym = allocp(csbe, sizeof(struct LibrarySymbol)); if (!sym) return CSBEE_OUT_OF_MEM; sym->symtype = symtype; assert(name); sym->name = name; assert(namelen > 0); sym->namelen = namelen; if (symtype == CSBEST_LAST_FUNC) { assert(csbe->fd != NULL); sym->decl.func = csbe->fd; if (!lib) { sym->decl.func->flags |= CSBEFD_INTERN_EXPORTED; } } else if (symtype == CSBEST_LAST_DATA) { assert(csbe->dd != NULL); sym->decl.data = csbe->dd; if (!lib) { sym->decl.data->flags |= CSBEDD_INTERN_EXPORTED; } } else if (symtype == CSBEST_MARKER) { /* Nothing more to do */ } sym->next = *inspoint; *inspoint = sym; return CSBEE_OK; } enum CsbeErr csbe_export_decl(struct Csbe *csbe, enum CsbeSymbolType symtype, struct CsbeSymVer *symver, const char *name, unsigned namelen) { return importexport_decl(csbe, symtype, NULL, symver, name, namelen); } enum CsbeErr csbe_import_decl(struct Csbe *csbe, enum CsbeSymbolType symtype, struct CsbeLibrary *lib, struct CsbeSymVer *symver, const char *name, unsigned namelen) { if (!lib) return SET_ERROR(CSBEE_INVALID_PARAM); return importexport_decl(csbe, symtype, lib, symver, name, namelen); } static struct CsbeType *real_typedef(struct Csbe *csbe, struct CsbeType *type, unsigned *arrlen_out) { struct CsbeType *realdef; int is_array = 0; unsigned arrlen = 1; assert(type); realdef = type; for (;;) { if (realdef->is_array) { is_array = 1; if (csbe_multiply_arraylengths(csbe, &arrlen, type->array_length, arrlen) != CSBEE_OK) { return NULL; } } if (!realdef->is_unbound) break; realdef = realdef->elemtypes; assert(realdef); } if (is_array) { *arrlen_out = arrlen; } else { *arrlen_out = 0; } return realdef; } static void defer_named_type(struct CsbeType *type, struct CsbeTypedef *td) { assert(!td->type.is_defined); assert(!type->is_unbound); assert(type != &td->type); type->is_unbound = 1; type->elemtypes = td->refs; td->refs = type; } static enum CsbeErr copy_named_type(struct Csbe *csbe, struct CsbeType *dest, struct CsbeType *src, unsigned arrlen) { int is_array = dest->is_array; unsigned total_arrlen = dest->array_length; assert(!src->is_unbound); assert(src != dest); memcpy(dest, src, sizeof(struct CsbeType)); if (arrlen >= 1) { if (is_array) { enum CsbeErr status = csbe_multiply_arraylengths(csbe, &total_arrlen, total_arrlen, arrlen); if (status != CSBEE_OK) return status; } else { is_array = 1; total_arrlen = arrlen; } } dest->is_array = is_array; dest->array_length = total_arrlen; assert(!dest->is_unbound); return CSBEE_OK; } static enum CsbeErr bind_typedefs(struct Csbe *csbe) { unsigned i; struct CsbeTypedef *td = csbe->typedefs; /* Worst case performance is O(n^2). Improve this. */ for (i = csbe->max_typedef_count; i-- > 0; td++) { struct CsbeType *realdef, *ref; unsigned arrlen; if (!td->refs) continue; /* Normalize/bind the type in the typedef */ realdef = real_typedef(csbe, &td->type, &arrlen); if (!realdef) return CSBEE_TYPE_TOO_LARGE; /* Bind all references to it */ for (ref = td->refs; ref; ) { enum CsbeErr status; struct CsbeType *next_ref = ref->elemtypes; assert(ref->is_unbound); assert(next_ref != ref); status = copy_named_type(csbe, ref, realdef, arrlen); if (status != CSBEE_OK) return status; ref = next_ref; } } return CSBEE_OK; } enum CsbeErr csbe_defs_done(struct Csbe *csbe) { assert(!csbe->defs_done); csbe->defs_done = 1; return bind_typedefs(csbe); } static void op_initstate(struct Csbe *csbe) { csbe->next_opnd = &csbe->current_op->opnd[0]; csbe->next_opnd_large = &csbe->current_op->opnd_large[0]; csbe->current_operand_pos = 0; } static enum CsbeErr alloc_op_chunk(struct Csbe *csbe, enum CsbeOp op) { struct OpChunk *chunk; assert(csbe->funcbody); chunk = allocp(csbe, sizeof(struct OpChunk)); if (!chunk) return CSBEE_OUT_OF_MEM; chunk->ops[0].op = op; csbe->current_op = &chunk->ops[0]; csbe->opchunk_end = &chunk->ops[OPS_PER_CHUNK]; chunk->next = NULL; if (csbe->funcbody->last_opchunk) { csbe->funcbody->last_opchunk->next = chunk; csbe->funcbody->last_opchunk->num_ops = OPS_PER_CHUNK; } else { csbe->funcbody->first_opchunk = chunk; } csbe->funcbody->last_opchunk = chunk; csbe->funcbody->num_opchunks++; op_initstate(csbe); return CSBEE_OK; } enum CsbeErr csbe_funcbody_start(struct Csbe *csbe, unsigned func_id, CsbeAllocFunc *temp_alloc, void *userctx, unsigned num_ebb, unsigned num_vars) /* note: total number of vars = num_args+num_vars */ { struct CsbeFuncdef *func; struct FuncIR *fb; struct Ebb *ebbs; struct Var *vars; assert(csbe->defs_done); assert(num_ebb != 0); if (num_ebb > MAX_EBBS || num_vars > MAX_VARDEFS) { return SET_ERROR(CSBEE_TOO_MANY_ITEMS); } func = &csbe->funcdefs[func_id]; if (num_vars < func->num_params) { return SET_ERROR(CSBEE_INVALID_PARAM); } fb = allocp(csbe, sizeof(struct FuncIR)); if (!fb) return CSBEE_OUT_OF_MEM; fb->funcdef = func; fb->contains_likely_calls = 0; fb->likely_no_return = 0; fb->max_out_params = 0; func->funcbody = fb; csbe->saved_allocator = csbe->cfg.funcptrs.fptr_alloc; csbe->saved_userctx = csbe->cfg.userctx; if (temp_alloc) { csbe->cfg.funcptrs.fptr_alloc = temp_alloc; } if (userctx) { csbe->cfg.userctx = userctx; } fb->num_ebb = num_ebb; ebbs = allocpa(csbe, sizeof(struct Ebb), num_ebb); if (!ebbs) return CSBEE_OUT_OF_MEM; memset(ebbs, 0, sizeof(struct Ebb)*num_ebb); csbe->current_ebb = NULL; fb->ebbs = ebbs; csbe->current_ebb_id = 0; fb->num_vars = num_vars; if (num_vars) { vars = allocpa(csbe, sizeof(struct Var), num_vars); if (!vars) return CSBEE_OUT_OF_MEM; memset(vars, 0, sizeof(struct Var)*num_vars); } else { vars = NULL; } csbe->current_var = fb->vars = vars; csbe->current_op = NULL; csbe->opchunk_end = NULL; csbe->pending_prev_op = NULL; fb->num_opchunks = 0; fb->first_opchunk = NULL; fb->last_opchunk = NULL; fb->opchunk_by_id = NULL; fb->first_non_temporary_varlane = 1; csbe->current_opnum = 0; csbe->current_paramtype = func->paramtypes; csbe->params_left = func->num_params; csbe->call_state = CSBE_CS_NO; csbe->has_unfinalized_op = 0; csbe->lowest_unused_var_id = func->num_params; fb->next = csbe->funcbody; csbe->funcbody = fb; return alloc_op_chunk(csbe, 0); } static int make_opchunk_list(struct Csbe *csbe) { struct OpChunk **arr, *chunk, **elem; arr = allocpa(csbe, sizeof(struct OpChunk **), csbe->funcbody->num_opchunks); if (!arr) return 0; elem = arr; for (chunk = csbe->funcbody->first_opchunk; chunk; chunk = chunk->next) { *(elem++) = chunk; } csbe->funcbody->opchunk_by_id = arr; return 1; } enum CsbeErr csbe_funcbody_end(struct Csbe *csbe) { enum CsbeErr e; struct FuncIR *fb = csbe->funcbody; assert(fb != NULL); ebb_end(csbe); fb->num_vars = csbe->lowest_unused_var_id; if (fb->last_opchunk) { if (!make_opchunk_list(csbe)) return CSBEE_OUT_OF_MEM; fb->last_opchunk->num_ops = csbe->current_opnum % OPS_PER_CHUNK; } else { fb->opchunk_by_id = NULL; } csbe->cfg.funcptrs.fptr_alloc = csbe->saved_allocator; csbe->cfg.userctx = csbe->saved_userctx; e = analyze_ir(csbe, fb); if (e != CSBEE_OK) return e; csbe->funcbody = NULL; return CSBEE_OK; } void csbe_funcbody_paramdef(struct Csbe *csbe, unsigned flags, unsigned var_id) { assert(csbe->params_left); assert(var_id == csbe->funcbody->funcdef->num_params - csbe->params_left); /*assert((flags & CSBEVD_HAVE_ADDRESS) == 0);*/ csbe_funcbody_vardef_start(csbe, flags|CSBEVD_INTERN_FUNCPARAM, var_id, CSBE_INTERN_VARLANE_FIRSTPARAM+var_id); csbe->current_var->type = *(csbe->current_paramtype++); csbe->type_slots_left--; csbe_funcbody_vardef_end(csbe); csbe->params_left--; } void csbe_funcbody_vardef_start(struct Csbe *csbe, unsigned flags, unsigned var_id, unsigned var_lane) { csbe->current_var = &csbe->funcbody->vars[var_id]; assert(!csbe->current_var->flags); csbe->current_var->flags = flags|CSBEVD_INTERN_DEFINED; csbe->current_var->lane = var_lane; if ((flags & CSBEVD_INTERN_FUNCPARAM) == 0 && var_lane >= csbe->funcbody->first_non_temporary_varlane) { csbe->funcbody->first_non_temporary_varlane = var_lane+1; } csbe->type = &csbe->current_var->type; csbe->type_slots_left = 1; type_init(csbe); } void csbe_funcbody_vardef_end(struct Csbe *csbe) { assert(csbe->type_slots_left == 0); assert(csbe->containing_type == NULL); csbe->type = NULL; } void csbe_funcbody_set_trap_ebb(struct Csbe *csbe, unsigned trap_ebb) { csbe->funcbody->has_trap_ebb = 1; csbe->funcbody->trap_ebb = trap_ebb; } enum CsbeErr csbe_ebb_start(struct Csbe *csbe, unsigned ebb_id, unsigned flags) { struct Ebb *ebb; assert(ebb_id < csbe->funcbody->num_ebb); if (csbe->current_ebb) { assert(ebb_id != csbe->current_ebb_id); if ((flags & CSBEEF_FLOWINTO) != 0 && ebb_id != csbe->current_ebb_id+1 && (csbe->current_ebb->flags & CSBEEF_INTERN_UNCOND_JUMP) == 0) { enum CsbeErr e = csbe_op_start(csbe, CSBEO_JUMP); if (e != CSBEE_OK) return e; csbe_operand_ebb(csbe, ebb_id); csbe_op_end(csbe); } ebb_end(csbe); } csbe->current_ebb_id = ebb_id; ebb = csbe->current_ebb = &csbe->funcbody->ebbs[ebb_id]; assert((ebb->flags & CSBEEF_INTERN_DEFINED) == 0); ebb->flags = flags|CSBEEF_INTERN_DEFINED; ebb->first_op = csbe->current_opnum; ebb->next_varlane = 0; clear_live_vars(csbe); return CSBEE_OK; } static void ebb_end(struct Csbe *csbe) { finalize_previous_op(csbe); flush_prev_op(csbe); assert(csbe->call_state == CSBE_CS_NO); csbe->pending_prev_op = NULL; /* even if last op was NOP */ if (csbe->current_ebb) { csbe->current_ebb->end_op = csbe->current_opnum; } } enum CsbeErr csbe_ebb_implicit_return(struct Csbe *csbe) { enum CsbeErr e; struct FuncIR *fb = csbe->funcbody; assert(fb != NULL); if (CSBE_TYPE_IS_VOID(fb->funcdef->returntype) && csbe->current_ebb_id != fb->num_ebb-1) { e = csbe_op_start(csbe, CSBEO_RETURN_VOID); if (e != CSBEE_OK) return e; csbe_op_end(csbe); } return CSBEE_OK; } static void check_op_applicability(struct Csbe *csbe, enum CsbeOp op) { struct FuncIR *fb = csbe->funcbody; assert(fb != NULL); switch ((int)op) { case CSBEO_RETURN_VOID: assert(csbe->call_state == CSBE_CS_NO); assert(CSBE_TYPE_IS_VOID(fb->funcdef->returntype)); break; case CSBEO_RETURN_ARG: assert(csbe->call_state == CSBE_CS_NO); assert(!CSBE_TYPE_IS_VOID(fb->funcdef->returntype)); break; case CSBEO_CALL_START: /* Note: Use csbe_call_start instead of using CSBEO_CALL_START directly */ assert(csbe->call_state == CSBE_CS_IN_START); csbe->call_state = CSBE_CS_ARGS_OR_FUNC; break; case CSBEO_CALL_ARG: /* Note: Use csbe_call_arg_start/end instead of using CSBEO_CALL_ARG directly */ assert(csbe->call_state == CSBE_CS_IN_ARG); assert(csbe->call_op_start); csbe->call_state = CSBE_CS_ARGS_OR_FUNC; break; case CSBEO_CALL_FUNCDEF: case CSBEO_CALL_FUNCVAR: assert(csbe->call_state == CSBE_CS_ARGS_OR_FUNC); assert(csbe->call_op_start); assert(csbe->call_op_argsleft == 0); csbe->call_state = CSBE_CS_RETURN; break; case CSBEO_CALL_GET_RETURN: case CSBEO_CALL_DISCARD_RETURN: assert(csbe->call_state == CSBE_CS_RETURN); assert(csbe->call_op_start); assert(csbe->call_op_argsleft == 0); csbe->call_state = CSBE_CS_NO; break; default: assert(csbe->call_state == CSBE_CS_NO); break; } } enum CsbeErr csbe_op_start(struct Csbe *csbe, enum CsbeOp op) { assert(op >= 0 && op < CSBEO_LAST); finalize_previous_op(csbe); check_op_applicability(csbe, op); csbe->has_unfinalized_op = 1; if (UNLIKELY(csbe->current_op == csbe->opchunk_end)) { /* also initially */ return alloc_op_chunk(csbe, op); } assert(csbe->current_operand_pos == 0); csbe->current_op->op = op; op_initstate(csbe); if (!csbe->pending_prev_op || csbe->pending_prev_op->op != CSBEO_NOP) { csbe->prev_var_out = csbe->last_var_out; } csbe->last_var_out = NULL; return CSBEE_OK; } static int constant_cond_is_true(const struct Op *op, enum CsbeBranchType branchtype) { switch (branchtype) { case CSBEBT_Z: return op->opnd_large[0].u64 == 0; case CSBEBT_NZ: return op->opnd_large[0].u64 != 0; case CSBEBT_LZ: case CSBEBT_LEZ: case CSBEBT_GZ: case CSBEBT_GEZ: assert(0); /* Signedness is not known! */ break; default: assert(0); /* Invalid parameter to csbe_operand_enum() */ } return -1; /* unreachable */ } struct TypeRange { unsigned min_bits : 8; unsigned max_bits : 8; unsigned is_sys : 1; unsigned is_int : 1; unsigned is_signed : 1; }; /** * Compares two types * * \param type1 First type * \param type2 Second type * \param allow_widening Allow type2 to be wider than type1 * \return 1 if same/wider, 0 if not */ static int types_same(const struct CsbeType *type1, const struct CsbeType *type2, int allow_widening) { if (!!type1->is_array != !!type2->is_array) return 0; if (type1->is_array) { if (type1->array_length != type2->array_length) return 0; } if (!!type1->is_struct != !!type2->is_struct) return 0; if (type1->is_struct) { const struct CsbeType *elem1, *elem2; unsigned remaining; if (type1->num_fields != type2->num_fields) return 0; if (type1->packmode != type2->packmode) return 0; remaining = type1->num_fields; elem1 = type1->elemtypes; elem2 = type2->elemtypes; while (remaining--) { if (!types_same(elem1, elem2, 0)) return 0; elem1++; elem2++; } return 1; } else { const enum CsbeTypeKind tk1 = type1->simple_kind; const enum CsbeTypeKind tk2 = type2->simple_kind; if (tk1 == tk2) return 1; else if (!allow_widening) return 0; else { static const struct TypeRange t_infos[NUM_TYPEKINDS] = { /* min max sys int sgn */ /* CSBET_BOOL */ { 0,0,0,0,0 }, /* CSBET_INT */ { 16,255, 1, 1, 1 }, /* CSBET_UINT */ { 16,255, 1, 1, 0 }, /* CSBET_LONG */ { 32,255, 1, 1, 1 }, /* CSBET_ULONG */ { 32,255, 1, 1, 0 }, /* CSBET_I8 */ { 8, 8, 0, 1, 1 }, /* CSBET_U8 */ { 8, 8, 0, 1, 0 }, /* CSBET_I16 */ { 16, 16, 1, 1, 1 }, /* CSBET_U16 */ { 16, 16, 1, 1, 0 }, /* CSBET_I32 */ { 32, 32, 1, 1, 1 }, /* CSBET_U32 */ { 32, 32, 1, 1, 0 }, /* CSBET_I64 */ { 64, 64, 1, 1, 1 }, /* CSBET_U64 */ { 64, 64, 1, 1, 0 }, /* CSBET_F32 */ { 0,0,0,0,0 }, /* CSBET_F64 */ { 0,0,0,0,0 }, /* CSBET_SSIZE */ { 16,255, 1, 1, 1 }, /* CSBET_USIZE */ { 16,255, 1, 1, 0 }, /* CSBET_DPTR */ { 0,0,0,0,0 }, /* CSBET_DPTR_ALIASED */ { 0,0,0,0,0 }, /* CSBET_DPTR_THREADED */ { 0,0,0,0,0 }, /* CSBET_FPTR */ { 0,0,0,0,0 } }; const struct TypeRange tr1 = t_infos[tk1]; const struct TypeRange tr2 = t_infos[tk2]; if (!tr1.is_int || !tr2.is_int) return 0; /* System-dependent types could have any size */ if (tr1.is_sys && tr2.is_sys) return 0; if (tr1.is_signed == tr2.is_signed) { return tr1.max_bits <= tr2.min_bits; } else if (tr2.is_signed) { return tr1.max_bits < tr2.min_bits; /* -1 bit for sign */ } else { return 0; /* signed to unsigned is not allowed */ } } } } static int var_type_non_narrowing(const struct Csbe *csbe, unsigned var1_id, unsigned var2_id) { const struct Var *vars = csbe->funcbody->vars; return types_same(&vars[var1_id].type, &vars[var2_id].type, /*allow_widening=*/1); } static int is_live(const struct Csbe *csbe, unsigned var_id) { return (csbe->funcbody->vars[var_id].flags & CSBEVD_INTERN_CURRENTLY_LIVE) != 0; } struct BinOpInfo { unsigned invar_flags, invar; unsigned outvar; unsigned left_is_var : 1; unsigned right_is_var : 1; unsigned has_immed : 1; unsigned only_immeds : 1; }; static struct BinOpInfo get_binop_info(const struct Op *op) { struct BinOpInfo b; b.outvar = op->opnd[4+1]; b.left_is_var = IS_VAR(op->opnd[0+0]); b.right_is_var = IS_VAR(op->opnd[2+0]); if (b.left_is_var) { b.invar_flags = op->opnd[0+0]; b.invar = op->opnd[0+1]; b.has_immed = !b.right_is_var; b.only_immeds = 0; } else if (b.right_is_var) { b.invar_flags = op->opnd[2+0]; b.invar = op->opnd[2+1]; b.has_immed = !b.left_is_var; b.only_immeds = 0; } else { b.only_immeds = 1; } return b; } /** * Merges comparisons (EQ,NEQ,LT,LE,GT,GE) with CONDJUMP/CONDTRAP. * * \param csbe CSBE context * \param op CONDJUMP or CONDTRAP operation. * \param branchtype_index Index of branchtype operand in COND* operation. */ static void merge_previous_comparison(struct Csbe *csbe, struct Op *op, unsigned invar_index, unsigned branchtype_index) { enum CsbeBranchType cmptype, branchtype; struct Op *prev = csbe->pending_prev_op; unsigned prev_optype; struct BinOpInfo prevb; assert(IS_VAR(op->opnd[invar_index])); if (IS_NON_DISCARD(op->opnd[invar_index])) return; if (!prev || !csbe->prev_var_out) return; prev_optype = prev->op; switch (prev_optype) { case CSBEO_EQ: cmptype = CSBEBT_Z; break; case CSBEO_NEQ: cmptype = CSBEBT_NZ; break; case CSBEO_LT: cmptype = CSBEBT_LZ; break; case CSBEO_LE: cmptype = CSBEBT_LEZ; break; case CSBEO_GT: cmptype = CSBEBT_GZ; break; case CSBEO_GE: cmptype = CSBEBT_GEZ; break; default: return; } prevb = get_binop_info(prev); /* We can only transform the following kind of sequence: r, ~, 0 CONDJUMP , r~, Into the optimized one: DISCARD r~ CONDJUMP , ~, */ /* TODO optimize EQ/NEQ/LT/... with constants */ if (prevb.only_immeds) return; /* TODO optimize EQ/NEQ/LT/... with same variable for both operands */ /* TODO for immed!=0, optimize into COMPAREJUMP with immed */ if (!prevb.has_immed || prev->opnd_large[0].u64 != 0) return; if (prevb.outvar != op->opnd[invar_index+1]) return; branchtype = op->opnd[branchtype_index]; if (branchtype == CSBEBT_NZ) { /* Use cmptype as-is */ } else if (branchtype == CSBEBT_Z) { cmptype = INVERT_BRANCHTYPE(cmptype); } else return; if (is_live(csbe, prevb.outvar)) { prev->op = CSBEO_DISCARD; prev->opnd[0+0] = CSBE_OPV_VARIABLE|CSBE_OPV_DISCARD; prev->opnd[0+1] = prevb.outvar; } else { prev->op = CSBEO_NOP; } op->opnd[branchtype_index] = cmptype; op->opnd[invar_index+0] = prevb.invar_flags; op->opnd[invar_index+1] = prevb.invar; } static void optimize_ir_op(struct Csbe *csbe, struct Op *op) { enum { ADD, SUB, MUL, DIV } subop; int is_trap = 1; optimize_more: switch ((int)op->op) { case CSBEO_JUMP: case CSBEO_TRAP: case CSBEO_RETURN_VOID: case CSBEO_RETURN_ARG: csbe->current_ebb->flags |= CSBEEF_INTERN_UNCOND_JUMP; break; case CSBEO_CONDJUMP: if (IS_IMMED(op->opnd[1+0])) { /* Constant comparison -> unconditional jump or no-op. These are silly, but this simplifies the frontend */ int go = constant_cond_is_true(op, /*branchtype=*/op->opnd[3]); assert(IS_NON_DISCARD(op->opnd[1+0])); /* not a variable */ if (go) { csbe->current_ebb->flags |= CSBEEF_INTERN_UNCOND_JUMP; op->op = CSBEO_JUMP; } else { op->op = CSBEO_NOP; } } else { merge_previous_comparison(csbe, op, /*invar_index*/1, /*branchtype_index=*/3); } break; case CSBEO_CONDTRAP: if (IS_IMMED(op->opnd[0+0])) { int go = constant_cond_is_true(op, /*branchtype=*/op->opnd[2]); assert(IS_NON_DISCARD(op->opnd[1+0])); /* not a variable */ if (go) { csbe->current_ebb->flags |= CSBEEF_INTERN_UNCOND_JUMP; op->op = CSBEO_TRAP; } else { op->op = CSBEO_NOP; } } else { merge_previous_comparison(csbe, op, /*invar_index*/0, /*branchtype_index=*/2); } break; case CSBEO_MOVE: case CSBEO_MOVE_TRAP: { assert(IS_VAR(op->opnd[2+0])); if (IS_VAR(op->opnd[0+0])) { unsigned invar = op->opnd[0+1]; unsigned outvar = op->opnd[2+1]; if (invar == outvar) { /* a = a */ op->op = CSBEO_NOP; } else if (csbe->pending_prev_op && /* A narrowing operation might cause a trap, so those can't be optimized out. */ var_type_non_narrowing(csbe, outvar, invar) && csbe->prev_var_out) { unsigned invar_discarded = IS_DISCARD(op->opnd[0+0]); unsigned prev_outvar = csbe->prev_var_out[1]; if (prev_outvar == invar && invar_discarded) { /* Previous output goes *only* to the input of this operation. So this MOVE operation is redundant. */ if (is_live(csbe, invar)) { op->op = CSBEO_DISCARD; op->opnd[0] = CSBE_OPV_VARIABLE|CSBE_OPV_DISCARD; op->opnd[1] = invar; } else { op->op = CSBEO_NOP; } csbe->prev_var_out[1] = outvar; } } } break; } case CSBEO_ADD: is_trap = 0; /* Fall through */ case CSBEO_ADD_TRAP: subop = ADD; goto arithmetic_op; case CSBEO_SUB: is_trap = 0; /* Fall through */ case CSBEO_SUB_TRAP: subop = SUB; goto arithmetic_op; case CSBEO_MUL: is_trap = 0; /* Fall through */ case CSBEO_MUL_TRAP: subop = MUL; goto arithmetic_op; case CSBEO_DIV: is_trap = 0; /* Fall through */ case CSBEO_DIV_TRAP: subop = DIV; arithmetic_op: { const struct BinOpInfo b = get_binop_info(op); if (is_trap && var_type_non_narrowing(csbe, b.invar, b.outvar)) { is_trap = 0; } if (b.only_immeds) { /* Compute and turn into a MOVE */ /* TODO detect overflows (and assert() or generate a TRAP?). note that if any variable is discarded, then it must remain discarded after optimization too. */ if (subop == ADD) { op->opnd_large[0].u64 = op->opnd_large[0].u64 + op->opnd_large[1].u64; } else if (subop == SUB) { op->opnd_large[0].u64 = op->opnd_large[0].u64 - op->opnd_large[1].u64; } else if (subop == MUL) { op->opnd_large[0].u64 = op->opnd_large[0].u64 * op->opnd_large[1].u64; } else { uint64 q; assert(subop == DIV); q = op->opnd_large[1].u64; if (q == 0) break; op->opnd_large[0].u64 = op->opnd_large[0].u64 / q; } op->op = (is_trap ? CSBEO_MOVE_TRAP : CSBEO_MOVE); op->opnd[0] = 0; op->opnd[2] = b.outvar; break; } if (b.has_immed) { uint64 immed = op->opnd_large[0].u64; if (subop == MUL) { if (immed == 0) { /* If the input is NOT discarded, then this is equivalent to a MOVE out,0 */ if (IS_NON_DISCARD(b.invar_flags) && var_type_non_narrowing(csbe, b.invar, b.outvar)) { op->op = (is_trap ? CSBEO_MOVE_TRAP : CSBEO_MOVE); op->opnd[0+0] = 0; op->opnd[2+0] = CSBE_OPV_VARIABLE; op->opnd[2+1] = b.outvar; op->opnd_large[0].u64 = 0; csbe->last_var_out = &op->opnd[2]; goto optimize_more; } } else if (immed == 1) { goto into_move; } } else if (subop == DIV) { if (immed == 1 && b.left_is_var) { goto into_move; } } else if (immed == 0) { assert(subop == ADD || subop == SUB); into_move: if (b.invar == b.outvar) { op->op = CSBEO_NOP; } else { op->op = (is_trap ? CSBEO_MOVE_TRAP : CSBEO_MOVE); op->opnd[0+0] = b.invar_flags; op->opnd[0+1] = b.invar; op->opnd[2+0] = CSBE_OPV_VARIABLE; op->opnd[2+1] = b.outvar; csbe->last_var_out = &op->opnd[2]; goto optimize_more; } } } break; } default: ; } } /** * Tracks variable liveness after an operation. * * Note that this function is called AFTER csbe_op_end * (in the next csbe_op_start or in csbe_ebb_end), * so it can take calls to csbe_divert_result() into account. */ static void mark_variable_liveness(struct Csbe *csbe, struct Op *op) { const struct OpArgInfo *arginfo = &op_argdefs[op->op]; struct Var *vars = csbe->funcbody->vars; unsigned *opnd = op->opnd; int i; for (i = 0; i < arginfo->num_args; i++) { int state; unsigned argtype = OPERAND_TYPE(arginfo->args[i]); unsigned var_id; unsigned flags; switch (argtype) { case OPERAND_VAR_OUT: /* Mark as live */ flags = *(opnd++); state = CSBEVD_INTERN_CURRENTLY_LIVE; goto set_status; case OPERAND_VAR_IN: case OPERAND_VAR_IN_OR_IMMED: flags = *(opnd++); if (IS_DISCARD(flags)) { var_id = *opnd; if ((vars[var_id].flags & CSBEVD_INTERN_FUNCPARAM) != 0) { /* Unlike normal variables, function params are "assigned" by the caller and never appear in VAR_OUT operands, So It's possible that all usages have the DISCARD flag. */ mark_var_usage(csbe, var_id); } /* Mark as dead */ state = 0; goto set_status; } else if (IS_VAR(flags)) { assert(op->op != CSBEO_DISCARD); mark_var_usage(csbe, *opnd); } goto next; case OPERAND_INDEX: case OPERAND_EBB: case OPERAND_FUNCDEF: case OPERAND_DATADEF: case OPERAND_FLAGS: case OPERAND_ENUM: goto next; case OPERAND_PTR: case OPERAND_TYPE: /* Argument is not stored in args[] */ continue; default: assert(0); } set_status: /* Mark variable */ assert(IS_VAR(flags)); var_id = *opnd; vars[var_id].flags = (vars[var_id].flags & ~CSBEVD_INTERN_CURRENTLY_LIVE) | state; mark_var_usage(csbe, var_id); next: opnd++; } } /** "Flushes" the previous op. After this has been done, no optimizations/changes may be performed on it. */ static void flush_prev_op(struct Csbe *csbe) { if (csbe->pending_prev_op) { mark_variable_liveness(csbe, csbe->pending_prev_op); switch ((int)csbe->pending_prev_op->op) { case CSBEO_NOP: return; /* don't clear pending_prev_op */ case CSBEO_JUMP: case CSBEO_CONDJUMP: case CSBEO_COMPAREJUMP: mark_live_vars(csbe, CSBEVD_INTERN_LIVE_ACROSS_JUMP); break; case CSBEO_CALL_FUNCDEF: case CSBEO_CALL_FUNCVAR: mark_live_vars(csbe, CSBEVD_INTERN_LIVE_ACROSS_CALL); break; } csbe->pending_prev_op = NULL; } } static void finalize_previous_op(struct Csbe *csbe) { if (csbe->has_unfinalized_op) { struct Op *op; csbe->has_unfinalized_op = 0; assert(csbe->current_opnum != 0); op = &csbe->current_op[-1]; optimize_ir_op(csbe, op); flush_prev_op(csbe); csbe->pending_prev_op = op; } } void csbe_op_end(struct Csbe *csbe) { unsigned op_pos = csbe->current_operand_pos; const struct OpArgInfo *arginfo = &op_argdefs[csbe->current_op->op]; int reachable = (csbe->current_ebb->flags&CSBEEF_INTERN_UNCOND_JUMP) == 0; assert(op_pos == arginfo->num_args); assert(csbe->current_op != NULL); if (reachable) { csbe->current_op++; csbe->current_opnum++; } if (LIKELY(csbe->current_op != csbe->opchunk_end)) { op_initstate(csbe); } } static void check_operand_pos(struct Csbe *csbe, unsigned operand_type) { unsigned op_pos = csbe->current_operand_pos; const struct OpArgInfo *arginfo = &op_argdefs[csbe->current_op->op]; assert(op_pos < arginfo->num_args); assert(OPERAND_TYPE(arginfo->args[op_pos]) == operand_type); csbe->current_operand_pos = op_pos + 1; } /** Checks that the current operand can be of type ot1 or ot2 */ static void check_operand_pos2(struct Csbe *csbe, unsigned ot1, unsigned ot2) { unsigned op_pos = csbe->current_operand_pos; const struct OpArgInfo *arginfo = &op_argdefs[csbe->current_op->op]; assert(op_pos < arginfo->num_args); assert(OPERAND_TYPE(arginfo->args[op_pos]) == ot1 || OPERAND_TYPE(arginfo->args[op_pos]) == ot2); csbe->current_operand_pos = op_pos + 1; } /** Checks that variables in operands have the correct type */ static void check_operand_var_type(struct Csbe *csbe, unsigned var_num) { enum CsbeOp op = csbe->current_op->op; unsigned op_pos = csbe->current_operand_pos - 1; /* already incremented */ const struct CsbeType *type = &csbe->funcbody->vars[var_num].type; if ((op == CSBEO_MOVETOPTR && op_pos == 1) || (op == CSBEO_DEREF && op_pos == 0) || (op == CSBEO_ADDRLOCAL && op_pos == 1) || (op == CSBEO_ADDRELEM && op_pos == 4) || (op == CSBEO_ADDRGLOBAL && op_pos == 1)) { /* Operations that only accept pointers (to data) */ assert(!type->is_array); assert(!type->is_struct); assert(IS_DPTR_TYPE(*type)); } else if ((op == CSBEO_ADDRELEM || op == CSBEO_LOADELEM) && op_pos == 2) { /* Index variable of ADDRELEM/LOADELEM */ const struct CsbeType *container_type = csbe->current_op->opnd_large[0].ptr; assert(container_type->is_array); assert(!type->is_array); assert(!type->is_struct); assert(IS_INTEGRAL_TYPE(*type)); } else if ((op >= CSBEO_ADD && op <= CSBEO_SHRA) || (op >= CSBEO_LT && op <= CSBEO_GE)) { /* Arithmetic, bitwise and less/greater operations */ assert(!type->is_array); assert(!type->is_struct); assert(IS_INTEGRAL_TYPE(*type)); } else if (op >= CSBEO_LAND && op <= CSBEO_LNOT) { /* Boolean operations */ assert(!type->is_array); assert(!type->is_struct); assert(type->simple_kind == CSBET_BOOL); } else if (op == CSBEO_ADDRFUNC && op_pos == 1) { /* Operations that only accept function pointers */ assert(!type->is_array); assert(!type->is_struct); assert(type->simple_kind == CSBET_FPTR); } else if ((op == CSBEO_EQ || op == CSBEO_NEQ) && op_pos == 2) { /* Function pointers may not be compared (that would require unique function entry point adresses, and that has a performance impact on some platforms) */ if (!type->is_struct) { assert(type->simple_kind != CSBET_FPTR); } else { /* TODO disallow comparison of structs containing function pointers */ } } } static void check_operand_immed(struct Csbe *csbe, uint64 value) { enum CsbeOp op = csbe->current_op->op; unsigned op_pos = csbe->current_operand_pos - 1; /* already incremented */ if ((op == CSBEO_ADDRELEM || op == CSBEO_LOADELEM) && op_pos == 2 && value != 0) { /* Index immediate of ADDRELEM/LOADELEM */ const struct CsbeType *container_type = csbe->current_op->opnd_large[0].ptr; assert(container_type->is_array); } } static void add_operand_unsigned(struct Csbe *csbe, unsigned x) { *(csbe->next_opnd++) = x; } static void add_operand_uint64(struct Csbe *csbe, uint64 x) { (csbe->next_opnd_large++)->u64 = x; } static void add_operand_ptr(struct Csbe *csbe, void *p) { (csbe->next_opnd_large++)->ptr = p; } static void mark_var_usage(struct Csbe *csbe, unsigned var_num) { struct Var *var = &csbe->funcbody->vars[var_num]; unsigned ebb_id = csbe->current_ebb_id; if ((var->flags & CSBEVD_INTERN_USED) == 0) { /* First use */ var->flags |= CSBEVD_INTERN_USED|CSBEVD_INTERN_SINGLE_EBB; var->ebb_id = ebb_id; } else if (var->ebb_id != ebb_id) { var->flags &= ~CSBEVD_INTERN_SINGLE_EBB; } if (var_num >= csbe->lowest_unused_var_id) { csbe->lowest_unused_var_id = var_num+1; } } static void mark_var_address_taken(struct Csbe *csbe, unsigned var_num) { struct Var *var = &csbe->funcbody->vars[var_num]; var->flags |= CSBEVD_INTERN_HAS_ADDRESS; } void csbe_operand_var_in(struct Csbe *csbe, unsigned var_num, unsigned flags) { assert(var_num < csbe->funcbody->num_vars); assert(csbe->funcbody->vars[var_num].flags); check_operand_pos2(csbe, OPERAND_VAR_IN, OPERAND_VAR_IN_OR_IMMED); check_operand_var_type(csbe, var_num); if (csbe->current_op->op == CSBEO_ADDRLOCAL && csbe->current_operand_pos-1 == 0) { mark_var_address_taken(csbe, var_num); } else if (csbe->current_op->op == CSBEO_DISCARD) { assert((flags & CSBE_OPV_DISCARD) != 0); } add_operand_unsigned(csbe, flags|CSBE_OPV_VARIABLE); add_operand_unsigned(csbe, var_num); } void csbe_operand_var_out(struct Csbe *csbe, unsigned var_num, unsigned flags) { assert(var_num < csbe->funcbody->num_vars); assert(csbe->funcbody->vars[var_num].flags); check_operand_pos(csbe, OPERAND_VAR_OUT); csbe->last_var_out = csbe->next_opnd; assert((flags & CSBE_OPV_DISCARD) == 0); add_operand_unsigned(csbe, flags|CSBE_OPV_VARIABLE); add_operand_unsigned(csbe, var_num); } /* TODO consider removing this and relying on MOVE's being optimized out. but currently, that does not give the same IR (besides the NOP diffs) */ void csbe_divert_result(struct Csbe *csbe, unsigned var_num, unsigned flags) { /* Assumption: The old variable is a temporary */ unsigned old_num; struct Var *oldvar; assert(csbe->last_var_out != NULL); old_num = csbe->last_var_out[1]; oldvar = &csbe->funcbody->vars[old_num]; assert((oldvar->flags & CSBEVD_INTERN_HAS_ADDRESS) == 0); csbe->last_var_out[0] = flags|CSBE_OPV_VARIABLE; csbe->last_var_out[1] = var_num; } void csbe_operand_immed(struct Csbe *csbe, enum CsbeTypeKind typekind, CsbeUint64 value) { check_operand_pos(csbe, OPERAND_VAR_IN_OR_IMMED); check_operand_immed(csbe, value); add_operand_unsigned(csbe, 0); add_operand_unsigned(csbe, typekind); add_operand_uint64(csbe, value); } void csbe_operand_index(struct Csbe *csbe, unsigned index) { check_operand_pos(csbe, OPERAND_INDEX); add_operand_unsigned(csbe, index); } void csbe_operand_ebb(struct Csbe *csbe, unsigned ebb_num) { assert(ebb_num < csbe->funcbody->num_ebb); check_operand_pos(csbe, OPERAND_EBB); add_operand_unsigned(csbe, ebb_num); } void csbe_operand_funcdef(struct Csbe *csbe, unsigned funcdef_num) { struct CsbeFuncdef *fd; assert(funcdef_num < csbe->max_funcdef_count); check_operand_pos(csbe, OPERAND_FUNCDEF); add_operand_unsigned(csbe, funcdef_num); fd = &csbe->funcdefs[funcdef_num]; fd->flags |= CSBEFD_INTERN_USED; if (csbe->current_op->op == CSBEO_CALL_FUNCDEF) { unsigned num_args = csbe->call_op_start->opnd[0]; assert(fd->num_params == num_args); } } void csbe_operand_datadef(struct Csbe *csbe, unsigned datadef_num) { assert(datadef_num < csbe->max_datadef_count); check_operand_pos(csbe, OPERAND_DATADEF); add_operand_unsigned(csbe, datadef_num); csbe->datadefs[datadef_num].flags |= CSBEDD_INTERN_USED; } void csbe_operand_flags(struct Csbe *csbe, unsigned flags) { check_operand_pos(csbe, OPERAND_FLAGS); add_operand_unsigned(csbe, flags); } void csbe_operand_enum(struct Csbe *csbe, int enumval) { check_operand_pos(csbe, OPERAND_ENUM); add_operand_unsigned(csbe, enumval); } void csbe_operand_ptr(struct Csbe *csbe, void *p) { check_operand_pos(csbe, OPERAND_PTR); add_operand_ptr(csbe, p); } enum CsbeErr csbe_operand_type_start(struct Csbe *csbe) { struct CsbeType *type; check_operand_pos(csbe, OPERAND_TYPE); assert(!csbe->type); assert(csbe->type_slots_left == 0); type = allocp(csbe, sizeof(struct CsbeType)); if (!type) return CSBEE_OUT_OF_MEM; csbe->type_slots_left = 1; csbe->containing_type = NULL; csbe->type = type; type_init(csbe); add_operand_ptr(csbe, type); return CSBEE_OK; } static void check_type_operand(const struct Csbe *csbe) { enum CsbeOp op = csbe->current_op->op; unsigned op_pos = csbe->current_operand_pos - 1; /* already incremented */ const struct CsbeType *type = &csbe->type[-1]; if (op == CSBEO_ADDRELEM || op == CSBEO_LOADELEM) { assert(op_pos == 0); assert(type->is_array || type->is_struct); } } void csbe_operand_type_end(struct Csbe *csbe) { assert(csbe->type_slots_left == 0); assert(csbe->containing_type == NULL); check_type_operand(csbe); csbe->type = NULL; } enum CsbeErr csbe_call_start(struct Csbe *csbe, unsigned num_args) { enum CsbeErr e; assert(csbe->call_state == CSBE_CS_NO); csbe->call_state = CSBE_CS_IN_START; e = csbe_op_start(csbe, CSBEO_CALL_START); if (e != CSBEE_OK) return e; csbe_operand_ptr(csbe, NULL); csbe_operand_index(csbe, num_args); csbe->call_op_lastarg = NULL; csbe->call_op_start = csbe->current_op; csbe->call_op_argsleft = num_args; csbe_op_end(csbe); /* TODO only set these if the call is in a likely code path */ csbe->funcbody->contains_likely_calls = 1; /* TODO csbe->funcbody->likely_no_return */ if (num_args > csbe->funcbody->max_out_params) { csbe->funcbody->max_out_params = num_args; } return CSBEE_OK; } enum CsbeErr csbe_call_arg_start(struct Csbe *csbe) { enum CsbeErr e; assert(csbe->call_op_argsleft > 0); csbe->call_op_argsleft--; assert(csbe->call_state == CSBE_CS_ARGS_OR_FUNC); csbe->call_state = CSBE_CS_IN_ARG; e = csbe_op_start(csbe, CSBEO_CALL_ARG); if (e != CSBEE_OK) return e; csbe_operand_ptr(csbe, csbe->call_op_lastarg); csbe->call_op_lastarg = csbe->current_op; return CSBEE_OK; } void csbe_call_arg_end(struct Csbe *csbe) { csbe_op_end(csbe); } void csbe_operand_callinfo(struct Csbe *csbe) { assert(csbe->call_op_argsleft == 0); /* Set pointer in CSBEO_CALL_START to this op (CSBEO_CALL_FUNC*) */ csbe->call_op_start->opnd_large[0].ptr = csbe->current_op; /* Add pointer to last argument, for reverse iteration */ csbe_operand_ptr(csbe, csbe->call_op_lastarg); } /** * Marks all currently live vars with the given flag. * * Used to mark variables as live across jumps/calls. */ static void mark_live_vars(struct Csbe *csbe, unsigned flag) { struct Var *fv = csbe->funcbody->vars; unsigned remaining = csbe->lowest_unused_var_id; for (; remaining--; fv++) { if ((fv->flags & CSBEVD_INTERN_CURRENTLY_LIVE) != 0) { fv->flags |= flag; } } } static void clear_live_vars(struct Csbe *csbe) { struct Var *fv = csbe->funcbody->vars; unsigned remaining = csbe->lowest_unused_var_id; assert(csbe->pending_prev_op == NULL); for (; remaining--; fv++) { fv->flags &= ~CSBEVD_INTERN_CURRENTLY_LIVE; } }