#include "compiler/Switch.h" #include "compiler/CError.h" #include "compiler/CFunc.h" #include "compiler/CInt64.h" #include "compiler/CParser.h" #include "compiler/InstrSelection.h" #include "compiler/ObjGenMachO.h" #include "compiler/Operands.h" #include "compiler/PCode.h" #include "compiler/PCodeUtilities.h" #include "compiler/RegisterInfo.h" #include "compiler/TOC.h" #include "compiler/CompilerTools.h" #include "compiler/objects.h" ObjectList *switchtables; static SwitchCase **caselabels; static CaseRange *caseranges; static SInt32 ncases; static SInt32 nranges_minus1; static CInt64 min; static CInt64 max; static CInt64 first; static short selector_gpr; static short selector_gprHi; static Type *selector_type; static PCodeLabel *defaultlabel; static CInt64 range; static int compare_cases(const void *a, const void *b) { const SwitchCase **casea = (const SwitchCase **) a; const SwitchCase **caseb = (const SwitchCase **) b; if (CInt64_Less((*casea)->min, (*caseb)->min)) return -1; if (CInt64_Greater((*casea)->min, (*caseb)->min)) return 1; return 0; } static void build_case_ranges(Type *type, SwitchCase *cases, CLabel *label) { SwitchCase **caseptr; SInt32 i; SwitchCase *curcase; CaseRange *currange; if (type->size == 8) { min.lo = 0; min.hi = 0x80000000; max.lo = 0xFFFFFFFF; max.hi = 0x7FFFFFFF; } else if (type->size == 4) { CInt64_SetLong(&min, 0x80000000); CInt64_SetLong(&max, 0x7FFFFFFF); } else if (is_unsigned(type)) { min.hi = 0; min.lo = 0; max.hi = 0; max.lo = 0xFFFF; } else { CInt64_SetLong(&min, -0x8000); CInt64_SetLong(&max, 0x7FFF); } caselabels = lalloc(sizeof(SwitchCase *) * ncases); caseptr = caselabels; while (cases) { *caseptr = cases; cases = cases->next; ++caseptr; } caseranges = lalloc(((ncases * 2) + 2) * sizeof(CaseRange)); if (type->size < 8) { for (i = 0; i < ncases; i++) CInt64_SetLong(&caselabels[i]->min, caselabels[i]->min.lo); } qsort(caselabels, ncases, sizeof(SwitchCase *), &compare_cases); currange = caseranges; currange->min = min; currange->range = CInt64_Sub(max, min); currange->label = label->pclabel; for (i = 0; i < ncases; i++) { curcase = caselabels[i]; if (CInt64_GreaterEqual(curcase->min, min) && CInt64_LessEqual(curcase->min, max)) { if (CInt64_Equal(currange->min, min)) first = curcase->min; range = CInt64_Sub(curcase->min, first); if (CInt64_Greater(curcase->min, currange->min)) { currange->range = CInt64_Sub(CInt64_Sub(curcase->min, currange->min), cint64_one); (++currange)->min = curcase->min; } else if (CInt64_Greater(currange->min, min) && curcase->label->pclabel == currange[-1].label) { currange[-1].range = CInt64_Add(currange[-1].range, cint64_one); if (CInt64_Equal(currange->range, cint64_zero)) { currange--; } else { currange->min = CInt64_Add(currange->min, cint64_one); currange->range = CInt64_Sub(currange->range, cint64_one); } continue; } currange->range = cint64_zero; currange->label = curcase->label->pclabel; if (CInt64_Less(curcase->min, max)) { currange++; currange->min = CInt64_Add(curcase->min, cint64_one); currange->range = CInt64_Sub(max, currange->min); currange->label = label->pclabel; } } } nranges_minus1 = currange - caseranges; } static void treecompare(SInt32 start, SInt32 end) { SInt32 r30; SInt32 r29; CaseRange *currange; int count; count = end - start; CError_ASSERT(175, selector_type->size <= 4); r29 = start + (count >> 1) + 1; currange = caseranges + r29; if (CInt64_Equal(currange[-1].range, cint64_zero) && (!(count & 1) || (CInt64_NotEqual(currange->range, cint64_zero) && count > 1))) { currange--; r29--; } r30 = r29 - 1; if (selector_type->size < 4 && is_unsigned(selector_type)) { emitpcode(PC_CMPLI, 0, selector_gpr, CInt64_GetULong(&currange->min)); } else if (FITS_IN_SHORT((SInt32) CInt64_GetULong(&currange->min))) { emitpcode(PC_CMPI, 0, selector_gpr, CInt64_GetULong(&currange->min)); } else { SInt32 value = CInt64_GetULong(&currange->min); int reg = ALLOC_GPR(); load_immediate(reg, value); emitpcode(PC_CMP, 0, selector_gpr, reg); } if (CInt64_Equal(currange->range, cint64_zero) && r29 < end) { branch_conditional(0, EEQU, 1, currange->label); r29++; } if (r29 == end) { if (start == r30) { if (caseranges[start].label == caseranges[end].label) { branch_always(caseranges[start].label); } else { branch_conditional(0, EGREATEREQU, 1, caseranges[end].label); branch_always(caseranges[start].label); } } else { branch_conditional(0, EGREATEREQU, 1, caseranges[end].label); treecompare(start, r30); } } else { if (start == r30) { branch_conditional(0, ELESS, 1, caseranges[start].label); treecompare(r29, end); } else { PCodeLabel *label = makepclabel(); branch_conditional(0, EGREATEREQU, 1, label); treecompare(start, r30); branch_label(label); treecompare(r29, end); } } } static void I8_treecompare(SInt32 start, SInt32 end) { SInt32 r30; SInt32 r29; CaseRange *currange; int count; count = end - start; r29 = start + (count >> 1) + 1; currange = caseranges + r29; if (CInt64_Equal(currange[-1].range, cint64_zero) && (!(count & 1) || (CInt64_NotEqual(currange->range, cint64_zero) && count > 1))) { currange--; r29--; } r30 = r29 - 1; if (CInt64_Equal(currange->range, cint64_zero) && r29 < end) { short a = ALLOC_GPR(); short b = ALLOC_GPR(); load_immediate(a, currange->min.lo); load_immediate(b, currange->min.hi); emitpcode(PC_XOR, a, selector_gpr, a); emitpcode(PC_XOR, b, selector_gprHi, b); emitpcode(PC_OR, b, a, b); emitpcode(PC_CMPI, 0, b, 0); branch_conditional(0, EEQU, 1, currange->label); r29++; } if (r29 == end) { if (start == r30) { if (caseranges[start].label == caseranges[end].label) { branch_always(caseranges[start].label); } else { short a = ALLOC_GPR(); short b = ALLOC_GPR(); short c = ALLOC_GPR(); short d = ALLOC_GPR(); load_immediate(a, currange->min.lo); load_immediate(b, currange->min.hi); if (TYPE_INTEGRAL(selector_type)->integral != IT_ULONGLONG && TYPE_INTEGRAL(selector_type)->integral != IT_ULONGLONG) { emitpcode(PC_XORIS, c, selector_gprHi, 0x8000); emitpcode(PC_XORIS, d, b, 0x8000); } else { c = selector_gprHi; d = b; } emitpcode(PC_SUBFC, a, a, selector_gpr); emitpcode(PC_SUBFE, b, d, c); emitpcode(PC_SUBFE, b, a, a); emitpcode(PC_NEG, b, b); emitpcode(PC_CMPI, 0, b, 0); branch_conditional(0, EEQU, 1, caseranges[end].label); branch_always(caseranges[start].label); } } else { short a = ALLOC_GPR(); short b = ALLOC_GPR(); short c = ALLOC_GPR(); short d = ALLOC_GPR(); load_immediate(a, currange->min.lo); load_immediate(b, currange->min.hi); if (TYPE_INTEGRAL(selector_type)->integral != IT_ULONGLONG && TYPE_INTEGRAL(selector_type)->integral != IT_ULONGLONG) { emitpcode(PC_XORIS, c, selector_gprHi, 0x8000); emitpcode(PC_XORIS, d, b, 0x8000); } else { c = selector_gprHi; d = b; } emitpcode(PC_SUBFC, a, a, selector_gpr); emitpcode(PC_SUBFE, b, d, c); emitpcode(PC_SUBFE, b, a, a); emitpcode(PC_NEG, b, b); emitpcode(PC_CMPI, 0, b, 0); branch_conditional(0, EEQU, 1, caseranges[end].label); I8_treecompare(start, r30); } } else { if (start == r30) { short a = ALLOC_GPR(); short b = ALLOC_GPR(); short c = ALLOC_GPR(); short d = ALLOC_GPR(); load_immediate(a, currange->min.lo); load_immediate(b, currange->min.hi); if (TYPE_INTEGRAL(selector_type)->integral != IT_ULONGLONG && TYPE_INTEGRAL(selector_type)->integral != IT_ULONGLONG) { emitpcode(PC_XORIS, c, selector_gprHi, 0x8000); emitpcode(PC_XORIS, d, b, 0x8000); } else { c = selector_gprHi; d = b; } emitpcode(PC_SUBFC, a, selector_gpr, a); emitpcode(PC_SUBFE, b, c, d); emitpcode(PC_SUBFE, b, a, a); emitpcode(PC_NEG, b, b); emitpcode(PC_CMPI, 0, b, 0); branch_conditional(0, ENOTEQU, 1, caseranges[end].label); I8_treecompare(r29, end); } else { PCodeLabel *label; short a = ALLOC_GPR(); short b = ALLOC_GPR(); short c = ALLOC_GPR(); short d = ALLOC_GPR(); load_immediate(a, currange->min.lo); load_immediate(b, currange->min.hi); if (TYPE_INTEGRAL(selector_type)->integral != IT_ULONGLONG && TYPE_INTEGRAL(selector_type)->integral != IT_ULONGLONG) { emitpcode(PC_XORIS, c, selector_gprHi, 0x8000); emitpcode(PC_XORIS, d, b, 0x8000); } else { c = selector_gprHi; d = b; } emitpcode(PC_SUBFC, a, a, selector_gpr); emitpcode(PC_SUBFE, b, d, b); emitpcode(PC_SUBFE, b, a, a); emitpcode(PC_NEG, b, b); emitpcode(PC_CMPI, 0, b, 0); label = makepclabel(); branch_conditional(0, EEQU, 1, label); I8_treecompare(start, r30); branch_label(label); I8_treecompare(r29, end); } } } static void generate_tree(ENode *expr) { Operand op; memclrw(&op, sizeof(Operand)); if (TYPE_IS_8BYTES(expr->rtype)) { GEN_NODE(expr, &op); coerce_to_register_pair(&op, expr->rtype, 0, 0); selector_type = expr->rtype; selector_gpr = op.reg; selector_gprHi = op.regHi; I8_treecompare(0, nranges_minus1); } else { GEN_NODE(expr, &op); if (expr->rtype->size < 4) extend32(&op, expr->rtype, 0); ENSURE_GPR(&op, expr->rtype, 0); selector_type = expr->rtype; selector_gpr = op.reg; treecompare(0, nranges_minus1); } } static Object *create_switch_table(void) { Object *obj; ObjectList *list; CaseRange *currange; UInt32 *outptr; CInt64 value; obj = galloc(sizeof(Object)); list = galloc(sizeof(ObjectList)); memclrw(obj, sizeof(Object)); memclrw(list, sizeof(ObjectList)); obj->otype = OT_OBJECT; obj->access = ACCESSPUBLIC; obj->datatype = DDATA; obj->name = CParser_GetUniqueName(); obj->toc = NULL; obj->sclass = TK_STATIC; obj->qual = Q_CONST; obj->flags |= OBJECT_FLAGS_2 | OBJECT_FLAGS_4; obj->u.data.linkname = obj->name; obj->type = NULL; createIndirect(obj, 0, 0); obj->type = TYPE(&void_ptr); obj->u.data.u.switchtable.size = CInt64_GetULong(&range) + 1; obj->u.data.u.switchtable.data = lalloc(4 * obj->u.data.u.switchtable.size); currange = caseranges; outptr = (UInt32 *) obj->u.data.u.switchtable.data; value = cint64_zero; while (CInt64_LessEqual(value, range)) { while (CInt64_Greater(CInt64_Add(first, value), CInt64_Add(currange->min, currange->range))) currange++; *outptr = CTool_CreateIndexFromPointer(currange->label); value = CInt64_Add(value, cint64_one); outptr++; } list->object = obj; list->next = switchtables; switchtables = list; return list->object; } static void generate_table(ENode *expr, SwitchInfo *info) { Object *table; short reg; short reg2; short reg3; SwitchCase *curcase; Operand op1; Operand op2; CInt64 val3 = {0, 3}; memclrw(&op1, sizeof(Operand)); memclrw(&op2, sizeof(Operand)); if (CInt64_Greater(first, cint64_zero) && CInt64_Less(first, val3)) { range = CInt64_Add(range, first); first = cint64_zero; } table = create_switch_table(); CError_ASSERT(553, !TYPE_IS_8BYTES(expr->rtype)); GEN_NODE(expr, &op1); if (expr->rtype->size < 4) extend32(&op1, expr->rtype, 0); ENSURE_GPR(&op1, expr->rtype, 0); reg = op1.reg; if (CInt64_NotEqual(first, cint64_zero)) { SInt32 value; SInt32 valueext; reg = ALLOC_GPR(); value = -CInt64_GetULong(&first); valueext = (SInt16) value; if (value != valueext) { emitpcode(PC_ADDIS, reg, op1.reg, 0, HIGH_PART(value)); if (value) emitpcode(PC_ADDI, reg, reg, 0, valueext); } else { emitpcode(PC_ADDI, reg, op1.reg, 0, value); } } if (!FITS_IN_SHORT(CInt64_GetULong(&range))) { short tmp = ALLOC_GPR(); load_immediate(tmp, CInt64_GetULong(&range)); emitpcode(PC_CMPL, 0, reg, tmp); } else { emitpcode(PC_CMPLI, 0, reg, CInt64_GetULong(&range)); } branch_conditional(0, EGREATER, 0, defaultlabel); if (table->toc) { op2.optype = OpndType_Symbol; op2.object = table->toc; indirect(&op2, NULL); } else { op2.optype = OpndType_Symbol; op2.object = table; } if (op2.optype != OpndType_GPR) { reg2 = ALLOC_GPR(); Coerce_to_register(&op2, TYPE(&void_ptr), reg2); } if (op2.optype != OpndType_GPR) { CError_FATAL(599); } else { if (op2.reg != reg2) emitpcode(PC_MR, reg2, op2.reg); } if (CInt64_Equal(first, cint64_zero)) { reg = ALLOC_GPR(); emitpcode(PC_RLWINM, reg, op1.reg, 2, 0, 29); } else { emitpcode(PC_RLWINM, reg, reg, 2, 0, 29); } reg3 = reg2; emitpcode(PC_LWZX, reg3, reg3, reg); for (curcase = info->cases; curcase; curcase = curcase->next) pcbranch(pclastblock, curcase->label->pclabel); pcbranch(pclastblock, info->defaultlabel->pclabel); emitpcode(PC_MTCTR, reg3); branch_indirect(table); } void switchstatement(ENode *expr, SwitchInfo *info) { Boolean use_table; SwitchCase *swcase; use_table = copts.switch_tables; ncases = 0; for (swcase = info->cases; swcase; swcase = swcase->next) { if (!swcase->label->pclabel) swcase->label->pclabel = makepclabel(); ncases++; } CError_ASSERT(656, ncases >= 0 && ncases <= 0x3333332U); if (!info->defaultlabel->pclabel) info->defaultlabel->pclabel = makepclabel(); defaultlabel = info->defaultlabel->pclabel; build_case_ranges(expr->rtype, info->cases, info->defaultlabel); if (TYPE_IS_8BYTES(expr->rtype)) { generate_tree(expr); return; } if (!use_table || nranges_minus1 < 8 || (nranges_minus1 * 2) < ((range.lo / 2) + 4)) generate_tree(expr); else generate_table(expr, info); } void dumpswitchtables(Object *funcobj) { Object *table; ObjectList *list; SInt32 size; UInt32 *array; for (list = switchtables; list; list = list->next) { table = list->object; CError_ASSERT(694, table->otype == OT_OBJECT && table->access == ACCESSPUBLIC && table->datatype == DDATA); size = table->u.data.u.switchtable.size; array = (UInt32 *) table->u.data.u.switchtable.data; while (size--) { *array = CTool_EndianConvertWord32(((PCodeLabel *) CTool_ResolveIndexToPointer(*array))->block->codeOffset); array++; } ObjGen_DeclareSwitchTable(table, funcobj); } }