static void type_match(RzCore *core, char *fcn_name, ut64 addr, ut64 baddr, const char *cc,
	int prev_idx, bool userfnc, ut64 caddr, HtUP *op_cache) {
	RzAnalysisEsilTrace *etrace = core->analysis->esil->trace;
	RzTypeDB *typedb = core->analysis->typedb;
	RzAnalysis *analysis = core->analysis;
	RzList *types = NULL;

	int idx = rz_pvector_len(etrace->instructions) - 1;

	bool verbose = rz_config_get_i(core->config, "analysis.types.verbose");
	bool stack_rev = false, in_stack = false, format = false;

	if (!fcn_name || !cc) {
		return;
	}
	int i, j, pos = 0, size = 0, max = rz_type_func_args_count(typedb, fcn_name);
	const char *place = rz_analysis_cc_arg(analysis, cc, ST32_MAX);
	rz_cons_break_push(NULL, NULL);

	if (place && !strcmp(place, "stack_rev")) {
		stack_rev = true;
	}
	place = rz_analysis_cc_arg(analysis, cc, 0);
	if (place && rz_str_startswith("stack", place)) {
		in_stack = true;
	}
	if (verbose && !strncmp(fcn_name, "sym.imp.", 8)) {
		eprintf("%s missing function definition\n", fcn_name + 8);
	}
	if (!max || max == -1) {
		if (!in_stack) {
			max = rz_analysis_cc_max_arg(analysis, cc);
		} else {
			max = DEFAULT_MAX;
		}
	}
	// Because in the case of `rz_type_func_get()` we get the borrowed
	// pointer to the RzCallable and in the case of `rz_analysis_function_derive_type()`
	// we get the owned pointer, we should free the pointer in the one case but not another.
	bool owned = false;
	RzCallable *callable = function_type_derive(analysis, fcn_name, &owned);
	// We don't check if the callable is NULL because we not always need it, for example
	// in case of the `format` set it's unnecessary for the type derivation.
	for (i = 0; i < max; i++) {
		int arg_num = stack_rev ? (max - 1 - i) : i;
		RzType *type = NULL;
		char *name = NULL;
		if (format) {
			if (rz_list_empty(types)) {
				break;
			}
			const char *typestr = rz_str_new(rz_list_get_n(types, pos++));
			if (typestr) {
				type = rz_type_parse_string_single(typedb->parser, typestr, NULL);
			}
		} else {
			if (!function_argument_type_derive(callable, arg_num, &type, &name)) {
				continue;
			}
		}
		if (!type && !userfnc) {
			free(name);
			continue;
		}
		if (!in_stack) {
			//XXX: param arg_num must be fixed to support floating point register
			place = rz_analysis_cc_arg(analysis, cc, arg_num);
			if (place && rz_str_startswith("stack", place)) {
				in_stack = true;
			}
		}
		char regname[REGNAME_SIZE] = { 0 };
		ut64 xaddr = UT64_MAX;
		bool memref = false;
		bool cmt_set = false;
		bool res = false;
		// Backtrace instruction from source sink to prev source sink
		for (j = idx; j >= prev_idx; j--) {
			RzILTraceInstruction *instr_trace = rz_analysis_esil_get_instruction_trace(etrace, j);
			ut64 instr_addr = instr_trace->addr;
			if (instr_addr < baddr) {
				break;
			}
			RzAnalysisOp *op = op_cache_get(op_cache, core, instr_addr);
			if (!op) {
				break;
			}
			RzAnalysisOp *next_op = op_cache_get(op_cache, core, instr_addr + op->size);
			if (!next_op || (j != idx && (next_op->type == RZ_ANALYSIS_OP_TYPE_CALL || next_op->type == RZ_ANALYSIS_OP_TYPE_JMP))) {
				break;
			}
			RzAnalysisVar *var = rz_analysis_get_used_function_var(analysis, op->addr);

			// FIXME : It seems also assume only read memory once ?
			if (op->type == RZ_ANALYSIS_OP_TYPE_MOV && (instr_trace->stats & RZ_IL_TRACE_INS_HAS_MEM_R)) {
				memref = !(!memref && var && (var->kind != RZ_ANALYSIS_VAR_KIND_REG));
			}
			// Match type from function param to instr
			if (type_pos_hit(analysis, instr_trace, in_stack, size, place)) {
				if (!cmt_set && type && name) {
					char *typestr = rz_type_as_string(analysis->typedb, type);
					const char *maybe_space = type->kind == RZ_TYPE_KIND_POINTER ? "" : " ";
					rz_meta_set_string(analysis, RZ_META_TYPE_VARTYPE, instr_addr,
						sdb_fmt("%s%s%s", typestr, maybe_space, name));
					cmt_set = true;
					if ((op->ptr && op->ptr != UT64_MAX) && !strcmp(name, "format")) {
						RzFlagItem *f = rz_flag_get_by_spaces(core->flags, op->ptr, RZ_FLAGS_FS_STRINGS, NULL);
						if (f) {
							char formatstr[0x200];
							int read = rz_io_nread_at(core->io, f->offset, (ut8 *)formatstr, RZ_MIN(sizeof(formatstr) - 1, f->size));
							if (read > 0) {
								formatstr[read] = '\0';
								if ((types = parse_format(core, formatstr))) {
									max += rz_list_length(types);
								}
								format = true;
							}
						}
					}
					free(typestr);
				}
				if (var) {
					if (!userfnc) {
						// not a userfunction, propagate the callee's arg types into our function's vars
						var_type_set(analysis, var, type, memref);
						var_rename(analysis, var, name, addr);
					} else {
						// callee is a userfunction, propagate our variable's type into the callee's args
						retype_callee_arg(analysis, fcn_name, in_stack, place, size, var->type);
					}
					res = true;
				} else {
					get_src_regname(core, instr_addr, regname, sizeof(regname));
					xaddr = get_addr(analysis, regname, j);
				}
			}
			// Type propagate by following source reg
			if (!res && *regname && rz_analysis_il_get_reg_op_trace(instr_trace, regname, true)) {
				if (var) {
					if (!userfnc) {
						// not a userfunction, propagate the callee's arg types into our function's vars
						var_type_set(analysis, var, type, memref);
						var_rename(analysis, var, name, addr);
					} else {
						// callee is a userfunction, propagate our variable's type into the callee's args
						retype_callee_arg(analysis, fcn_name, in_stack, place, size, var->type);
					}
					res = true;
				} else {
					switch (op->type) {
					case RZ_ANALYSIS_OP_TYPE_MOV:
					case RZ_ANALYSIS_OP_TYPE_PUSH:
						get_src_regname(core, instr_addr, regname, sizeof(regname));
						break;
					case RZ_ANALYSIS_OP_TYPE_LEA:
					case RZ_ANALYSIS_OP_TYPE_LOAD:
					case RZ_ANALYSIS_OP_TYPE_STORE:
						res = true;
						break;
					}
				}
			} else if (var && res && xaddr && (xaddr != UT64_MAX)) { // Type progation using value
				char tmp[REGNAME_SIZE] = { 0 };
				get_src_regname(core, instr_addr, tmp, sizeof(tmp));
				ut64 ptr = get_addr(analysis, tmp, j);
				if (ptr == xaddr) {
					if (type) {
						var_type_set(analysis, var, type, memref);
					} else {
						var_type_set_str(analysis, var, "int", memref);
					}
				}
			}
		}
		size += analysis->bits / 8;
		rz_type_free(type);
		free(name);
	}
	if (owned) {
		rz_type_callable_free(callable);
	}
	rz_list_free(types);
	rz_cons_break_pop();
}