def codegen_compile(func, datatype: str):
    """
    :param func:
    :param datatype: either 'float', 'double' or 'int'
    :return:
    """
    func_name = func.__name__
    sig = signature(func)

    if datatype.startswith('int'):
        llvm_type = ll.IntType(64)
        type_dummy_instance = LLVMInt64
        c_type = c_int64
    elif datatype.startswith('uint'):
        llvm_type = ll.IntType(64)
        type_dummy_instance = LLVMUInt64
        c_type = c_uint64
    elif datatype in ['float', 'double']:
        llvm_type = ll.DoubleType()
        type_dummy_instance = LLVMDouble
        c_type = c_double
    else:
        return None

    llvm_param_types = [llvm_type for c in param_names[:len(sig.parameters)]]
    fntype = ll.FunctionType(llvm_type, llvm_param_types)
    module = ll.Module()
    llvm_func = ll.Function(module, fntype, name=func_name)
    bb_entry = llvm_func.append_basic_block()
    builder = ll.IRBuilder()
    builder.position_at_end(bb_entry)

    context = Context(builder)
    params = [type_dummy_instance(context, arg) for arg in llvm_func.args]

    ret = func(*params)
    context.builder.ret(ret.instruction)

    code = str(module)

    llmod = llvm.parse_assembly(code)

    pmb = llvm.create_pass_manager_builder()
    pmb.opt_level = 2
    pm = llvm.create_module_pass_manager()
    pmb.populate(pm)

    pm.run(llmod)

    ee = llvm.create_mcjit_compiler(llmod, target_machine)
    ee.finalize_object()
    cfptr = ee.get_function_address(func_name)

    cfunc = CFUNCTYPE(c_type, *[c_type for c in llvm_param_types])(cfptr)
    # keep the reference alive
    # (this is probably an ugly hack? but whatever)
    cfunc.execution_engine = ee
    cfunc.target_asm = target_machine.emit_assembly(llmod)
    cfunc.llvm_code = code
    return cfunc