bool is_concrete(ast_t* type) { switch(ast_id(type)) { case TK_UNIONTYPE: case TK_TUPLETYPE: return false; case TK_ISECTTYPE: { ast_t* child = ast_child(type); while(child != NULL) { if(is_concrete(child)) return true; child = ast_sibling(child); } return false; } case TK_NOMINAL: { ast_t* def = (ast_t*)ast_data(type); switch(ast_id(def)) { case TK_INTERFACE: case TK_TRAIT: return false; case TK_PRIMITIVE: case TK_CLASS: case TK_ACTOR: return true; default: {} } break; } case TK_TYPEPARAMREF: { ast_t* def = (ast_t*)ast_data(type); ast_t* constraint = ast_childidx(def, 1); return is_constructable(constraint); } case TK_ARROW: return is_concrete(ast_childidx(type, 1)); default: {} } assert(0); return false; }
void change_visibility(DexMethod* method) { auto code = method->get_code(); always_assert(code != nullptr); editable_cfg_adapter::iterate(code, [](MethodItemEntry& mie) { auto insn = mie.insn; if (insn->has_field()) { auto cls = type_class(insn->get_field()->get_class()); if (cls != nullptr && !cls->is_external()) { set_public(cls); } auto field = resolve_field(insn->get_field(), is_sfield_op(insn->opcode()) ? FieldSearch::Static : FieldSearch::Instance); if (field != nullptr && field->is_concrete()) { set_public(field); set_public(type_class(field->get_class())); // FIXME no point in rewriting opcodes in the method insn->set_field(field); } } else if (insn->has_method()) { auto cls = type_class(insn->get_method()->get_class()); if (cls != nullptr && !cls->is_external()) { set_public(cls); } auto current_method = resolve_method( insn->get_method(), opcode_to_search(insn)); if (current_method != nullptr && current_method->is_concrete()) { set_public(current_method); set_public(type_class(current_method->get_class())); // FIXME no point in rewriting opcodes in the method insn->set_method(current_method); } } else if (insn->has_type()) { auto type = insn->get_type(); auto cls = type_class(type); if (cls != nullptr && !cls->is_external()) { set_public(cls); } } return editable_cfg_adapter::LOOP_CONTINUE; }); std::vector<DexType*> types; if (code->editable_cfg_built()) { code->cfg().gather_catch_types(types); } else { code->gather_catch_types(types); } for (auto type : types) { auto cls = type_class(type); if (cls != nullptr && !cls->is_external()) { set_public(cls); } } }
static bool validate_sget(DexMethod* context, DexOpcodeField* opfield) { auto opcode = opfield->opcode(); switch (opcode) { case OPCODE_SGET_WIDE: unhandled_inline++; return false; case OPCODE_SGET: case OPCODE_SGET_BOOLEAN: case OPCODE_SGET_BYTE: case OPCODE_SGET_CHAR: case OPCODE_SGET_SHORT: return true; default: auto field = resolve_field(opfield->field(), FieldSearch::Static); always_assert_log(field->is_concrete(), "Must be a concrete field"); auto value = field->get_static_value(); always_assert_log( false, "Unexpected field type in inline_*sget %s for field %s value %s in " "method %s\n", SHOW(opfield), SHOW(field), value != nullptr ? value->show().c_str() : "('nullptr')", SHOW(context)); } return false; }
void inline_field_values(Scope& fullscope) { std::unordered_set<DexField*> inline_field; std::unordered_set<DexField*> cheap_inline_field; std::vector<DexClass*> scope; uint32_t aflags = ACC_STATIC | ACC_FINAL; for (auto clazz : fullscope) { std::unordered_map<DexField*, bool> blank_statics; get_sput_in_clinit(clazz, blank_statics); auto sfields = clazz->get_sfields(); for (auto sfield : sfields) { if ((sfield->get_access() & aflags) != aflags) continue; if (blank_statics[sfield]) continue; auto value = sfield->get_static_value(); if (value == nullptr && !is_primitive(sfield->get_type())) { continue; } if (value != nullptr && !value->is_evtype_primitive()) { continue; } uint64_t v = value != nullptr ? value->value() : 0; if ((v & 0xffff) == v || (v & 0xffff0000) == v) { cheap_inline_field.insert(sfield); } inline_field.insert(sfield); scope.push_back(clazz); } } std::vector<std::pair<DexMethod*, DexOpcodeField*>> cheap_rewrites; std::vector<std::pair<DexMethod*, DexOpcodeField*>> simple_rewrites; walk_opcodes( fullscope, [](DexMethod* method) { return true; }, [&](DexMethod* method, DexInstruction* insn) { if (insn->has_fields() && is_sfield_op(insn->opcode())) { auto fieldop = static_cast<DexOpcodeField*>(insn); auto field = resolve_field(fieldop->field(), FieldSearch::Static); if (field == nullptr || !field->is_concrete()) return; if (inline_field.count(field) == 0) return; if (cheap_inline_field.count(field) > 0) { cheap_rewrites.push_back(std::make_pair(method, fieldop)); return; } simple_rewrites.push_back(std::make_pair(method, fieldop)); } }); TRACE(FINALINLINE, 1, "Method Re-writes Cheap %lu Simple %lu\n", cheap_rewrites.size(), simple_rewrites.size()); for (auto cheapcase : cheap_rewrites) { inline_cheap_sget(cheapcase.first, cheapcase.second); } for (auto simplecase : simple_rewrites) { inline_sget(simplecase.first, simplecase.second); } MethodTransform::sync_all(); }
/** * Change the visibility of members accessed in a callee as they are moved * to the caller context. * We make everything public but we could be more precise and only * relax visibility as needed. */ void MultiMethodInliner::change_visibility(DexMethod* callee) { TRACE(MMINL, 6, "checking visibility usage of members in %s\n", SHOW(callee)); for (auto insn : callee->get_code()->get_instructions()) { if (insn->has_fields()) { auto fop = static_cast<DexOpcodeField*>(insn); auto field = fop->field(); field = resolve_field(field, is_sfield_op(insn->opcode()) ? FieldSearch::Static : FieldSearch::Instance); if (field != nullptr && field->is_concrete()) { TRACE(MMINL, 6, "changing visibility of %s.%s %s\n", SHOW(field->get_class()), SHOW(field->get_name()), SHOW(field->get_type())); set_public(field); set_public(type_class(field->get_class())); fop->rewrite_field(field); } continue; } if (insn->has_methods()) { auto mop = static_cast<DexOpcodeMethod*>(insn); auto method = mop->get_method(); method = resolver(method, opcode_to_search(insn)); if (method != nullptr && method->is_concrete()) { TRACE(MMINL, 6, "changing visibility of %s.%s: %s\n", SHOW(method->get_class()), SHOW(method->get_name()), SHOW(method->get_proto())); set_public(method); set_public(type_class(method->get_class())); mop->rewrite_method(method); } continue; } if (insn->has_types()) { auto type = static_cast<DexOpcodeType*>(insn)->get_type(); auto cls = type_class(type); if (cls != nullptr && !cls->is_external()) { TRACE(MMINL, 6, "changing visibility of %s\n", SHOW(type)); set_public(cls); } continue; } } }
void inline_sget(DexMethod* method, DexOpcodeField* opfield) { if (!validate_sget(method, opfield)) return; auto opcode = OPCODE_CONST; auto dest = opfield->dest(); auto field = resolve_field(opfield->field(), FieldSearch::Static); always_assert_log(field->is_concrete(), "Must be a concrete field"); auto value = field->get_static_value(); /* FIXME for sget_wide case */ uint32_t v = value != nullptr ? (uint32_t)value->value() : 0; auto newopcode = (new DexInstruction(opcode))->set_dest(dest)->set_literal(v); replace_opcode(method, opfield, newopcode); }
void MultiMethodInliner::inline_callees( InlineContext& inline_context, std::vector<DexMethod*>& callees) { size_t found = 0; auto caller = inline_context.caller; auto insns = caller->get_code()->get_instructions(); // walk the caller opcodes collecting all candidates to inline // Build a callee to opcode map std::vector<std::pair<DexMethod*, DexOpcodeMethod*>> inlinables; for (auto insn = insns.begin(); insn != insns.end(); ++insn) { if (!is_invoke((*insn)->opcode())) continue; auto mop = static_cast<DexOpcodeMethod*>(*insn); auto callee = resolver(mop->get_method(), opcode_to_search(*insn)); if (callee == nullptr) continue; if (std::find(callees.begin(), callees.end(), callee) == callees.end()) { continue; } always_assert(callee->is_concrete()); found++; inlinables.push_back(std::make_pair(callee, mop)); if (found == callees.size()) break; } if (found != callees.size()) { always_assert(found <= callees.size()); info.not_found += callees.size() - found; } // attempt to inline all inlinable candidates for (auto inlinable : inlinables) { auto callee = inlinable.first; auto mop = inlinable.second; if (!is_inlinable(callee, caller)) continue; auto op = mop->opcode(); if (is_invoke_range(op)) { info.invoke_range++; continue; } TRACE(MMINL, 4, "inline %s (%d) in %s (%d)\n", SHOW(callee), caller->get_code()->get_registers_size(), SHOW(caller), callee->get_code()->get_registers_size() - callee->get_code()->get_ins_size()); change_visibility(callee); MethodTransform::inline_16regs(inline_context, callee, mop); info.calls_inlined++; inlined.insert(callee); } }
std::unordered_set<DexField*> get_called_field_defs(Scope& scope) { std::vector<DexField*> field_refs; walk_methods(scope, [&](DexMethod* method) { method->gather_fields(field_refs); }); sort_unique(field_refs); /* Okay, now we have a complete list of field refs * for this particular dex. Map to the def actually invoked. */ std::unordered_set<DexField*> field_defs; for (auto field_ref : field_refs) { auto field_def = resolve_field(field_ref); if (field_def == nullptr || !field_def->is_concrete()) continue; field_defs.insert(field_def); } return field_defs; }
static void setup_dwarf(dwarf_t* dwarf, dwarf_meta_t* meta, gentype_t* g, bool opaque, bool field) { memset(meta, 0, sizeof(dwarf_meta_t)); ast_t* ast = g->ast; LLVMTypeRef type = g->primitive; if(is_machine_word(ast)) { if(is_float(ast)) meta->flags |= DWARF_FLOAT; else if(is_signed(dwarf->opt, ast)) meta->flags |= DWARF_SIGNED; else if(is_bool(ast)) meta->flags |= DWARF_BOOLEAN; } else if(is_pointer(ast) || is_maybe(ast) || !is_concrete(ast) || (is_constructable(ast) && field)) { type = g->use_type; } else if(is_constructable(ast)) { type = g->structure; } bool defined_type = g->underlying != TK_TUPLETYPE && g->underlying != TK_UNIONTYPE && g->underlying != TK_ISECTTYPE; source_t* source; if(defined_type) ast = (ast_t*)ast_data(ast); source = ast_source(ast); meta->file = source->file; meta->name = g->type_name; meta->line = ast_line(ast); meta->pos = ast_pos(ast); if(!opaque) { meta->size = LLVMABISizeOfType(dwarf->target_data, type) << 3; meta->align = LLVMABIAlignmentOfType(dwarf->target_data, type) << 3; } }
/** * The callee contains a *get/put instruction to an unknown field. * Given the caller may not be in the same hierarchy/package we cannot inline * it unless we make the field public. * But we need to make all fields public across the hierarchy and for fields * we don't know we have no idea whether the field was public or not anyway. */ bool MultiMethodInliner::unknown_field(DexInstruction* insn, DexMethod* context) { if (is_ifield_op(insn->opcode()) || is_sfield_op(insn->opcode())) { auto fop = static_cast<DexOpcodeField*>(insn); auto field = fop->field(); field = resolve_field(field, is_sfield_op(insn->opcode()) ? FieldSearch::Static : FieldSearch::Instance); if (field == nullptr) { info.escaped_field++; return true; } if (!field->is_concrete() && !is_public(field)) { info.non_pub_field++; return true; } } return false; }
/* * There's no "good way" to differentiate blank vs. non-blank * finals. So, we just scan the code in the CL-init. If * it's sput there, then it's a blank. Lame, agreed, but functional. * */ void get_sput_in_clinit(DexClass* clazz, std::unordered_map<DexField*, bool>& blank_statics) { auto methods = clazz->get_dmethods(); for (auto method : methods) { if (is_clinit(method)) { always_assert_log(is_static(method) && is_constructor(method), "static constructor doesn't have the proper access bits set\n"); auto& code = method->get_code(); auto opcodes = code->get_instructions(); for (auto opcode : opcodes) { if (opcode->has_fields() && is_sput(opcode->opcode())) { auto fieldop = static_cast<DexOpcodeField*>(opcode); auto field = resolve_field(fieldop->field(), FieldSearch::Static); if (field == nullptr || !field->is_concrete()) continue; if (field->get_class() != clazz->get_type()) continue; blank_statics[field] = true; } } } } }
void inline_cheap_sget(DexMethod* method, DexOpcodeField* opfield) { if (!validate_sget(method, opfield)) return; auto dest = opfield->dest(); auto field = resolve_field(opfield->field(), FieldSearch::Static); always_assert_log(field->is_concrete(), "Must be a concrete field"); auto value = field->get_static_value(); /* FIXME for sget_wide case */ uint32_t v = value != nullptr ? (uint32_t)value->value() : 0; auto opcode = [&] { if ((v & 0xffff) == v) { return OPCODE_CONST_16; } else if ((v & 0xffff0000) == v) { return OPCODE_CONST_HIGH16; } always_assert_log(false, "Bad inline_cheap_sget queued up, can't fit to" " CONST_16 or CONST_HIGH16, bailing\n"); }(); auto newopcode = (new DexInstruction(opcode, 0))->set_dest(dest)->set_literal(v); replace_opcode(method, opfield, newopcode); }
/** * Check if a visibility/accessibility change would turn a method referenced * in a callee to virtual methods as they are inlined into the caller. * That is, once a callee is inlined we need to ensure that everything that was * referenced by a callee is visible and accessible in the caller context. * This step would not be needed if we changed all private instance to static. */ bool MultiMethodInliner::create_vmethod(DexInstruction* insn) { auto opcode = insn->opcode(); if (opcode == OPCODE_INVOKE_DIRECT || opcode == OPCODE_INVOKE_DIRECT_RANGE) { auto method = static_cast<DexOpcodeMethod*>(insn)->get_method(); method = resolver(method, MethodSearch::Direct); if (method == nullptr) { info.need_vmethod++; return true; } always_assert(method->is_def()); if (is_init(method)) { if (!method->is_concrete() && !is_public(method)) { info.non_pub_ctor++; return true; } // concrete ctors we can handle because they stay invoke_direct return false; } info.need_vmethod++; return true; } return false; }
MultiMethodInliner::MultiMethodInliner( std::vector<DexClass*>& scope, DexClasses& primary_dex, std::unordered_set<DexMethod*>& candidates, std::function<DexMethod*(DexMethod*, MethodSearch)> resolver) : resolver(resolver) { for (const auto& cls : primary_dex) { primary.insert(cls->get_type()); } // walk every opcode in scope looking for calls to inlinable candidates // and build a map of callers to callees and the reverse callees to callers walk_opcodes(scope, [](DexMethod* meth) { return true; }, [&](DexMethod* meth, DexInstruction* opcode) { if (is_invoke(opcode->opcode())) { auto mop = static_cast<DexOpcodeMethod*>(opcode); auto callee = resolver(mop->get_method(), opcode_to_search(opcode)); if (callee != nullptr && callee->is_concrete() && candidates.find(callee) != candidates.end()) { callee_caller[callee].push_back(meth); caller_callee[meth].push_back(callee); } } }); }
/** * Add to the list the single called. */ void SimpleInlinePass::select_single_called( Scope& scope, std::unordered_set<DexMethod*>& methods) { std::unordered_map<DexMethod*, int> calls; for (const auto& method : methods) { calls[method] = 0; } // count call sites for each method walk_opcodes(scope, [](DexMethod* meth) { return true; }, [&](DexMethod* meth, DexInstruction* insn) { if (is_invoke(insn->opcode())) { auto mop = static_cast<DexOpcodeMethod*>(insn); auto callee = resolve_method( mop->get_method(), opcode_to_search(insn), resolved_refs); if (callee != nullptr && callee->is_concrete() && methods.count(callee) > 0) { calls[callee]++; } } }); // pick methods with a single call site and add to candidates. // This vector usage is only because of logging we should remove it // once the optimization is "closed" std::vector<std::vector<DexMethod*>> calls_group(MAX_COUNT); for (auto call_it : calls) { if (call_it.second >= MAX_COUNT) { calls_group[MAX_COUNT - 1].push_back(call_it.first); continue; } calls_group[call_it.second].push_back(call_it.first); } assert(method_breakup(calls_group)); for (auto callee : calls_group[1]) { inlinable.insert(callee); } }
bool check_constraints(ast_t* orig, ast_t* typeparams, ast_t* typeargs, bool report_errors, pass_opt_t* opt) { ast_t* typeparam = ast_child(typeparams); ast_t* typearg = ast_child(typeargs); while(typeparam != NULL) { if(is_bare(typearg)) { if(report_errors) { ast_error(opt->check.errors, typearg, "a bare type cannot be used as a type argument"); } return false; } switch(ast_id(typearg)) { case TK_NOMINAL: { ast_t* def = (ast_t*)ast_data(typearg); if(ast_id(def) == TK_STRUCT) { if(report_errors) { ast_error(opt->check.errors, typearg, "a struct cannot be used as a type argument"); } return false; } break; } case TK_TYPEPARAMREF: { ast_t* def = (ast_t*)ast_data(typearg); if(def == typeparam) { typeparam = ast_sibling(typeparam); typearg = ast_sibling(typearg); continue; } break; } default: {} } // Reify the constraint. ast_t* constraint = ast_childidx(typeparam, 1); ast_t* r_constraint = reify(constraint, typeparams, typeargs, opt, true); // A bound type must be a subtype of the constraint. errorframe_t info = NULL; errorframe_t* infop = (report_errors ? &info : NULL); if(!is_subtype_constraint(typearg, r_constraint, infop, opt)) { if(report_errors) { errorframe_t frame = NULL; ast_error_frame(&frame, orig, "type argument is outside its constraint"); ast_error_frame(&frame, typearg, "argument: %s", ast_print_type(typearg)); ast_error_frame(&frame, typeparam, "constraint: %s", ast_print_type(r_constraint)); errorframe_append(&frame, &info); errorframe_report(&frame, opt->check.errors); } ast_free_unattached(r_constraint); return false; } ast_free_unattached(r_constraint); // A constructable constraint can only be fulfilled by a concrete typearg. if(is_constructable(constraint) && !is_concrete(typearg)) { if(report_errors) { ast_error(opt->check.errors, orig, "a constructable constraint can " "only be fulfilled by a concrete type argument"); ast_error_continue(opt->check.errors, typearg, "argument: %s", ast_print_type(typearg)); ast_error_continue(opt->check.errors, typeparam, "constraint: %s", ast_print_type(constraint)); } return false; } typeparam = ast_sibling(typeparam); typearg = ast_sibling(typearg); } pony_assert(typeparam == NULL); pony_assert(typearg == NULL); return true; }
bool check_constraints(ast_t* orig, ast_t* typeparams, ast_t* typeargs, bool report_errors, pass_opt_t* opt) { ast_t* typeparam = ast_child(typeparams); ast_t* typearg = ast_child(typeargs); while(typeparam != NULL) { if(ast_id(typearg) == TK_TYPEPARAMREF) { ast_t* def = (ast_t*)ast_data(typearg); if(def == typeparam) { typeparam = ast_sibling(typeparam); typearg = ast_sibling(typearg); continue; } } // Reify the constraint. ast_t* constraint = ast_childidx(typeparam, 1); ast_t* bind_constraint = bind_type(constraint); ast_t* r_constraint = reify(bind_constraint, typeparams, typeargs, opt); if(bind_constraint != r_constraint) ast_free_unattached(bind_constraint); // A bound type must be a subtype of the constraint. errorframe_t info = NULL; if(!is_subtype(typearg, r_constraint, report_errors ? &info : NULL, opt)) { if(report_errors) { ast_error(opt->check.errors, orig, "type argument is outside its constraint"); ast_error_continue(opt->check.errors, typearg, "argument: %s", ast_print_type(typearg)); ast_error_continue(opt->check.errors, typeparam, "constraint: %s", ast_print_type(r_constraint)); } ast_free_unattached(r_constraint); return false; } ast_free_unattached(r_constraint); // A constructable constraint can only be fulfilled by a concrete typearg. if(is_constructable(constraint) && !is_concrete(typearg)) { if(report_errors) { ast_error(opt->check.errors, orig, "a constructable constraint can " "only be fulfilled by a concrete type argument"); ast_error_continue(opt->check.errors, typearg, "argument: %s", ast_print_type(typearg)); ast_error_continue(opt->check.errors, typeparam, "constraint: %s", ast_print_type(constraint)); } return false; } typeparam = ast_sibling(typeparam); typearg = ast_sibling(typearg); } assert(typeparam == NULL); assert(typearg == NULL); return true; }
bool check_constraints(ast_t* orig, ast_t* typeparams, ast_t* typeargs, bool report_errors) { ast_t* typeparam = ast_child(typeparams); ast_t* typearg = ast_child(typeargs); while(typeparam != NULL) { // Reify the constraint. ast_t* constraint = ast_childidx(typeparam, 1); ast_t* bind_constraint = bind_type(constraint); ast_t* r_constraint = reify(bind_constraint, typeparams, typeargs); if(bind_constraint != r_constraint) ast_free_unattached(bind_constraint); // A bound type must be a subtype of the constraint. errorframe_t info = NULL; if(!is_subtype(typearg, r_constraint, report_errors ? &info : NULL)) { if(report_errors) { errorframe_t frame = NULL; ast_error_frame(&frame, orig, "type argument is outside its constraint"); ast_error_frame(&frame, typearg, "argument: %s", ast_print_type(typearg)); ast_error_frame(&frame, typeparam, "constraint: %s", ast_print_type(r_constraint)); errorframe_append(&frame, &info); errorframe_report(&frame); } ast_free_unattached(r_constraint); return false; } ast_free_unattached(r_constraint); // A constructable constraint can only be fulfilled by a concrete typearg. if(is_constructable(constraint) && !is_concrete(typearg)) { if(report_errors) { ast_error(orig, "a constructable constraint can only be fulfilled " "by a concrete type argument"); ast_error(typearg, "argument: %s", ast_print_type(typearg)); ast_error(typeparam, "constraint: %s", ast_print_type(constraint)); } return false; } typeparam = ast_sibling(typeparam); typearg = ast_sibling(typearg); } assert(typeparam == NULL); assert(typearg == NULL); return true; }
bool check_constraints(ast_t* orig, ast_t* typeparams, ast_t* typeargs, bool report_errors) { // Reify the type parameters with the typeargs. ast_t* r_typeparams = reify(orig, typeparams, typeparams, typeargs); if(r_typeparams == NULL) return false; ast_t* r_typeparam = ast_child(r_typeparams); ast_t* typeparam = ast_child(typeparams); ast_t* typearg = ast_child(typeargs); while(r_typeparam != NULL) { // Use the reified constraint. ast_t* r_constraint = ast_childidx(r_typeparam, 1); r_constraint = bind_type(r_constraint); // A bound type must be a subtype of the constraint. if(!is_subtype(typearg, r_constraint)) { if(report_errors) { ast_error(orig, "type argument is outside its constraint"); ast_error(typearg, "argument: %s", ast_print_type(typearg)); ast_error(typeparam, "constraint: %s", ast_print_type(r_constraint)); } ast_free_unattached(r_typeparams); ast_free_unattached(r_constraint); return false; } ast_free_unattached(r_constraint); // A constructable constraint can only be fulfilled by a concrete typearg. ast_t* constraint = ast_childidx(typeparam, 1); if(is_constructable(constraint) && !is_concrete(typearg)) { if(report_errors) { ast_error(orig, "a constructable constraint can only be fulfilled " "by a concrete type argument"); ast_error(typearg, "argument: %s", ast_print_type(typearg)); ast_error(typeparam, "constraint: %s", ast_print_type(constraint)); } ast_free_unattached(r_typeparams); return false; } r_typeparam = ast_sibling(r_typeparam); typeparam = ast_sibling(typeparam); typearg = ast_sibling(typearg); } assert(r_typeparam == NULL); assert(typearg == NULL); ast_free_unattached(r_typeparams); return true; }