diff --git a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/algorithms.cpp b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/algorithms.cpp index 2ce135b92..ba44c4185 100644 --- a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/algorithms.cpp +++ b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/algorithms.cpp @@ -10,6 +10,8 @@ static PyMethodDef algorithms_PyMethodDef[] = { METH_VARARGS | METH_KEYWORDS, ""}, {"bubble_sort_llvm", (PyCFunction)bubble_sort_llvm, METH_VARARGS | METH_KEYWORDS, ""}, + {"is_ordered_llvm", (PyCFunction) is_ordered_llvm, + METH_VARARGS | METH_KEYWORDS, ""}, {"selection_sort", (PyCFunction) selection_sort, METH_VARARGS | METH_KEYWORDS, ""}, {"insertion_sort", (PyCFunction) insertion_sort, diff --git a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/llvm_algorithms.py b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/llvm_algorithms.py index 24b14609d..dd56a4833 100644 --- a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/llvm_algorithms.py +++ b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/llvm_algorithms.py @@ -41,6 +41,119 @@ def get_bubble_sort_ptr(dtype: str) -> int: return _materialize(dtype) + +def get_is_ordered_ptr(dtype: str) -> int: + dtype = dtype.lower().strip() + if dtype not in _SUPPORTED: + raise ValueError(f"Unsupported dtype '{dtype}'. Supported: {list(_SUPPORTED)}") + + return _materialize_is_ordered(dtype) + + +def _build_is_ordered_ir(dtype: str) -> str: + if dtype not in _SUPPORTED: + raise ValueError(f"Unsupported dtype '{dtype}'. Supported: {list(_SUPPORTED)}") + + T, _ = _SUPPORTED[dtype] + i32 = ir.IntType(32) + i64 = ir.IntType(64) + + mod = ir.Module(name=f"is_ordered_{dtype}_module") + fn_name = f"is_ordered_{dtype}" + + fn_ty = ir.FunctionType(i32, [T.as_pointer(), i32]) + fn = ir.Function(mod, fn_ty, name=fn_name) + + arr, n = fn.args + arr.name, n.name = "arr", "n" + + b_entry = fn.append_basic_block("entry") + b_loop = fn.append_basic_block("loop") + b_check = fn.append_basic_block("check") + b_ret_true = fn.append_basic_block("ret_true") + b_ret_false = fn.append_basic_block("ret_false") + b_exit = fn.append_basic_block("exit") + + b = ir.IRBuilder(b_entry) + cond_trivial = b.icmp_signed("<=", n, ir.Constant(i32, 1)) + b.cbranch(cond_trivial, b_ret_true, b_loop) + + b.position_at_end(b_loop) + i = b.phi(i32, name="i") + i.add_incoming(ir.Constant(i32, 1), b_entry) + + cond_loop = b.icmp_signed("<", i, n) + b.cbranch(cond_loop, b_check, b_ret_true) + + b.position_at_end(b_check) + i64_idx = b.sext(i, i64) + iprev = b.sub(i, ir.Constant(i32, 1)) + iprev64 = b.sext(iprev, i64) + + ptr_i = b.gep(arr, [i64_idx], inbounds=True) + ptr_iprev = b.gep(arr, [iprev64], inbounds=True) + + val_i = b.load(ptr_i) + val_iprev = b.load(ptr_iprev) + + if isinstance(T, ir.IntType): + cond = b.icmp_signed("<=", val_iprev, val_i) + else: + cond = b.fcmp_ordered("<=", val_iprev, val_i, fastmath=True) + + b.cbranch(cond, b.loop, b_ret_false) + + b.position_at_end(b_ret_false) + b.ret(ir.Constant(i32, 0)) + + b.position_at_end(b.loop) + i_next = b.add(i, ir.Constant(i32, 1)) + i.add_incoming(i_next, b.loop) + b.branch(b_loop) + + b.position_at_end(b_ret_true) + b.ret(ir.Constant(i32, 1)) + + return str(mod) + + +def _materialize_is_ordered(dtype: str) -> int: + _ensure_target_machine() + + if dtype in _fn_ptr_cache: + return _fn_ptr_cache[dtype] + + try: + llvm_ir = _build_is_ordered_ir(dtype) + mod = binding.parse_assembly(llvm_ir) + mod.verify() + + try: + pm = binding.ModulePassManager() + pm.add_instruction_combining_pass() + pm.add_reassociate_pass() + pm.add_gvn_pass() + pm.add_cfg_simplification_pass() + pm.run(mod) + except AttributeError: + pass + + engine = binding.create_mcjit_compiler(mod, _target_machine) + engine.finalize_object() + engine.run_static_constructors() + + addr = engine.get_function_address(f"is_ordered_{dtype}") + if not addr: + raise RuntimeError(f"Failed to get address for is_ordered_{dtype}") + + _fn_ptr_cache[dtype] = addr + _engines[dtype] = engine + + return addr + + except Exception as e: + raise RuntimeError(f"Failed to materialize function for dtype {dtype}: {e}") + def _build_bubble_sort_ir(dtype: str) -> str: if dtype not in _SUPPORTED: raise ValueError(f"Unsupported dtype '{dtype}'. Supported: {list(_SUPPORTED)}") diff --git a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/quadratic_time_sort.hpp b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/quadratic_time_sort.hpp index 210382455..e553743b9 100644 --- a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/quadratic_time_sort.hpp +++ b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/quadratic_time_sort.hpp @@ -612,6 +612,425 @@ static PyObject* bubble_sort_llvm(PyObject* self, PyObject* args, PyObject* kwds Py_INCREF(arr_obj); return arr_obj; } +// is_ordered LLVM backend +static PyObject* is_ordered_llvm(PyObject* self, PyObject* args, PyObject* kwds) { + static const char* kwlist[] = {"arr", "start", "end", "comp", "dtype", NULL}; + PyObject* arr_obj = NULL; + PyObject* start_obj = NULL; + PyObject* end_obj = NULL; + PyObject* comp_obj = NULL; + const char* dtype_cstr = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOOs", (char**)kwlist, + &arr_obj, &start_obj, &end_obj, &comp_obj, &dtype_cstr)) { + return NULL; + } + + Py_ssize_t arr_len_ssize = PyObject_Length(arr_obj); + size_t arr_len = (size_t)arr_len_ssize; + + bool is_dynamic_array = false; + PyObject* last_pos_attr = PyUnicode_FromString("_last_pos_filled"); + PyObject* num_attr = PyUnicode_FromString("_num"); + + if (last_pos_attr && num_attr && PyObject_HasAttr(arr_obj, last_pos_attr) && PyObject_HasAttr(arr_obj, num_attr)) { + is_dynamic_array = true; + } + + Py_XDECREF(last_pos_attr); + Py_XDECREF(num_attr); + + if (is_dynamic_array) { + PyObject* size_attr = PyUnicode_FromString("_size"); + if (size_attr && PyObject_HasAttr(arr_obj, size_attr)) { + PyObject* size_obj = PyObject_GetAttr(arr_obj, size_attr); + if (size_obj && PyLong_Check(size_obj)) { + Py_ssize_t size_val = PyLong_AsSsize_t(size_obj); + if (size_val >= 0) { + arr_len = (size_t)size_val; + } + } + Py_XDECREF(size_obj); + } + Py_XDECREF(size_attr); + } + + if (arr_len == 0) { + Py_INCREF(Py_True); + return Py_True; + } + + size_t lower = 0; + size_t upper = arr_len - 1; + + if (start_obj && start_obj != Py_None) { + Py_ssize_t start_val = PyLong_AsSsize_t(start_obj); + if (PyErr_Occurred()) return NULL; + lower = (size_t)start_val; + } + + if (end_obj && end_obj != Py_None) { + Py_ssize_t end_val = PyLong_AsSsize_t(end_obj); + if (PyErr_Occurred()) return NULL; + upper = (size_t)end_val; + } + + if (upper < lower || lower >= arr_len) { + Py_INCREF(Py_True); + return Py_True; + } + + if (upper >= arr_len) { + upper = arr_len - 1; + } + + if (comp_obj && comp_obj != Py_None) { + PyErr_SetString(PyExc_NotImplementedError, "LLVM backend does not support custom 'comp'."); + return NULL; + } + + std::string dtype = (dtype_cstr && *dtype_cstr) ? std::string(dtype_cstr) : std::string(); + auto infer_dtype = [&](PyObject* x) -> std::string { + if (x == Py_None) return ""; + if (PyFloat_Check(x)) return "float64"; + if (PyLong_Check(x)) return "int64"; + return "float64"; + }; + + std::vector non_none_values; + size_t none_count = 0; + const size_t N = upper - lower + 1; + + non_none_values.reserve(N); + + for (size_t i = 0; i < N; i++) { + size_t actual_index = lower + i; + + if (actual_index >= arr_len) { + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + PyErr_Format(PyExc_IndexError, "Index %zu out of bounds (array length: %zu)", + actual_index, arr_len); + return NULL; + } + + PyObject* item = NULL; + + if (PySequence_Check(arr_obj)) { + item = PySequence_GetItem(arr_obj, (Py_ssize_t)actual_index); + } else { + PyObject* index_obj = PyLong_FromSize_t(actual_index); + if (index_obj) { + item = PyObject_GetItem(arr_obj, index_obj); + Py_DECREF(index_obj); + } + } + + if (!item) { + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + + if (PyErr_ExceptionMatches(PyExc_IndexError)) { + PyErr_Format(PyExc_IndexError, "Cannot access index %zu in array", actual_index); + } + return NULL; + } + + if (item == Py_None) { + none_count++; + Py_DECREF(item); + } else { + non_none_values.push_back(item); + } + } + + if (dtype.empty() && !non_none_values.empty()) { + dtype = infer_dtype(non_none_values[0]); + } + + if (non_none_values.empty() || dtype.empty()) { + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + Py_INCREF(Py_True); + return Py_True; + } + + auto get_addr = [&](const char* dtype_str) -> PyObject* { + PyObject* sys = PyImport_ImportModule("sys"); + PyObject* sys_path = PyObject_GetAttrString(sys, "path"); + Py_DECREF(sys); + + Py_ssize_t original_len = PyList_Size(sys_path); + if (original_len == -1) { + Py_DECREF(sys_path); + return NULL; + } + + PyObject* path = PyUnicode_FromString("pydatastructs/linear_data_structures/_backend/cpp/algorithms"); + if (!path) { + Py_DECREF(sys_path); + return NULL; + } + + int append_result = PyList_Append(sys_path, path); + Py_DECREF(path); + + if (append_result != 0) { + Py_DECREF(sys_path); + return NULL; + } + + PyObject* mod = PyImport_ImportModule("llvm_algorithms"); + + if (PyList_SetSlice(sys_path, original_len, original_len + 1, NULL) != 0) { + PyErr_Clear(); + } + Py_DECREF(sys_path); + if (!mod) { + return NULL; + } + + PyObject* fn = PyObject_GetAttrString(mod, "get_is_ordered_ptr"); + Py_DECREF(mod); + if (!fn) { + return NULL; + } + + PyObject* arg = PyUnicode_FromString(dtype_str); + if (!arg) { + Py_DECREF(fn); + return NULL; + } + + PyObject* addr_obj = PyObject_CallFunctionObjArgs(fn, arg, NULL); + Py_DECREF(fn); + Py_DECREF(arg); + if (!addr_obj) { + return NULL; + } + return addr_obj; + }; + + if (N > INT32_MAX) { + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + PyErr_SetString(PyExc_OverflowError, "Slice length exceeds 32-bit limit for JIT function signature."); + return NULL; + } + + if (dtype == "int32" || dtype == "int64") { + bool is32 = (dtype == "int32"); + PyObject* addr_obj = get_addr(dtype.c_str()); + if (!addr_obj) { + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + return NULL; + } + + long long addr = PyLong_AsLongLong(addr_obj); + Py_DECREF(addr_obj); + if (addr == -1 && PyErr_Occurred()) { + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + return NULL; + } + + if (is32) { + std::vector buf; + buf.reserve(non_none_values.size()); + + for (size_t i = 0; i < non_none_values.size(); i++) { + PyObject* obj = non_none_values[i]; + + long v = PyLong_AsLong(obj); + if (PyErr_Occurred()) { + for (PyObject* cleanup_obj : non_none_values) { + Py_DECREF(cleanup_obj); + } + return NULL; + } + + if (v < INT32_MIN || v > INT32_MAX) { + for (PyObject* cleanup_obj : non_none_values) { + Py_DECREF(cleanup_obj); + } + PyErr_Format(PyExc_OverflowError, "Value %ld at index %zu out of int32 range", v, i); + return NULL; + } + buf.push_back((int32_t)v); + } + + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + non_none_values.clear(); + + if (buf.empty()) { + Py_INCREF(Py_True); + return Py_True; + } + + bool result = false; + try { + auto fn = reinterpret_cast(addr); + int res = fn(buf.data(), (int32_t)buf.size()); + result = (res != 0); + } catch (...) { + PyErr_SetString(PyExc_RuntimeError, "LLVM function call failed"); + return NULL; + } + PyObject* out = result ? Py_True : Py_False; + Py_INCREF(out); + return out; + } else { + std::vector buf; + buf.reserve(non_none_values.size()); + + for (size_t i = 0; i < non_none_values.size(); i++) { + PyObject* obj = non_none_values[i]; + long long v = PyLong_AsLongLong(obj); + if (PyErr_Occurred()) { + for (PyObject* cleanup_obj : non_none_values) { + Py_DECREF(cleanup_obj); + } + return NULL; + } + buf.push_back((int64_t)v); + } + + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + non_none_values.clear(); + + if (buf.empty()) { + Py_INCREF(Py_True); + return Py_True; + } + + bool result = false; + try { + auto fn = reinterpret_cast(addr); + int res = fn(buf.data(), (int32_t)buf.size()); + result = (res != 0); + } catch (...) { + PyErr_SetString(PyExc_RuntimeError, "LLVM function call failed"); + return NULL; + } + PyObject* out = result ? Py_True : Py_False; + Py_INCREF(out); + return out; + } + } + else if (dtype == "float32" || dtype == "float64") { + bool is32 = (dtype == "float32"); + PyObject* addr_obj = get_addr(dtype.c_str()); + if (!addr_obj) { + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + return NULL; + } + long long addr = PyLong_AsLongLong(addr_obj); + Py_DECREF(addr_obj); + if (addr == -1 && PyErr_Occurred()) { + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + return NULL; + } + if (is32) { + std::vector buf; + buf.reserve(non_none_values.size()); + + for (size_t i = 0; i < non_none_values.size(); i++) { + PyObject* obj = non_none_values[i]; + double v = PyFloat_AsDouble(obj); + if (PyErr_Occurred()) { + for (PyObject* cleanup_obj : non_none_values) { + Py_DECREF(cleanup_obj); + } + return NULL; + } + buf.push_back((float)v); + } + + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + non_none_values.clear(); + if (buf.empty()) { + Py_INCREF(Py_True); + return Py_True; + } + + bool result = false; + try { + auto fn = reinterpret_cast(addr); + int res = fn(buf.data(), (int32_t)buf.size()); + result = (res != 0); + } catch (...) { + PyErr_SetString(PyExc_RuntimeError, "LLVM function call failed"); + return NULL; + } + PyObject* out = result ? Py_True : Py_False; + Py_INCREF(out); + return out; + } + else { + std::vector buf; + buf.reserve(non_none_values.size()); + + for (size_t i = 0; i < non_none_values.size(); i++) { + PyObject* obj = non_none_values[i]; + double v = PyFloat_AsDouble(obj); + if (PyErr_Occurred()) { + for (PyObject* cleanup_obj : non_none_values) { + Py_DECREF(cleanup_obj); + } + return NULL; + } + buf.push_back(v); + } + + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + non_none_values.clear(); + + if (buf.empty()) { + Py_INCREF(Py_True); + return Py_True; + } + + bool result = false; + try { + auto fn = reinterpret_cast(addr); + int res = fn(buf.data(), (int32_t)buf.size()); + result = (res != 0); + } catch (...) { + PyErr_SetString(PyExc_RuntimeError, "LLVM function call failed"); + return NULL; + } + PyObject* out = result ? Py_True : Py_False; + Py_INCREF(out); + return out; + } + + } else { + for (PyObject* obj : non_none_values) { + Py_DECREF(obj); + } + PyErr_SetString(PyExc_ValueError, "dtype must be one of: int32,int64,float32,float64"); + return NULL; + } +} // Selection Sort static PyObject* selection_sort_impl(PyObject* array, size_t lower, size_t upper, PyObject* comp) { diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index 6d383fdca..f1980599b 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -913,6 +913,8 @@ def is_ordered(array, **kwargs): backend = kwargs.pop("backend", Backend.PYTHON) if backend == Backend.CPP: return _algorithms.is_ordered(array, **kwargs) + if backend == Backend.LLVM: + return _algorithms.is_ordered_llvm(array, **kwargs) lower = kwargs.get('start', 0) upper = kwargs.get('end', len(array) - 1) comp = kwargs.get("comp", lambda u, v: u <= v) diff --git a/pydatastructs/linear_data_structures/tests/test_algorithms.py b/pydatastructs/linear_data_structures/tests/test_algorithms.py index 3e287bb74..28bbc1402 100644 --- a/pydatastructs/linear_data_structures/tests/test_algorithms.py +++ b/pydatastructs/linear_data_structures/tests/test_algorithms.py @@ -214,6 +214,8 @@ def _test_inner_ordered(*args, **kwargs): _test_inner_ordered() _test_inner_ordered(backend=Backend.CPP) + # Also test LLVM backend (if available) + _test_inner_ordered(backend=Backend.LLVM) def test_upper_bound():