fastpython-native 0.1.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -444,29 +444,37 @@ def generate_c_extension_source(
444
444
  buffer_pre = []
445
445
  buffer_post = []
446
446
 
447
- for arg in func_def.args.args:
447
+ c_var_extracts = []
448
+ c_var_names = []
449
+ buffer_pre = []
450
+ buffer_post = []
451
+
452
+ for i, arg in enumerate(func_def.args.args):
448
453
  name = arg.arg
449
454
  t = resolved_hints.get(name, "double")
450
455
  if t == "int64_t":
451
- format_chars.append("L")
452
- c_var_decls.append(f" long long {name};")
453
- c_var_names.append(f"(int64_t){name}")
456
+ c_var_extracts.append(
457
+ f" int64_t {name} = PyLong_CheckExact(args[{i}]) ? "
458
+ f"PyLong_AsLongLong(args[{i}]) : (int64_t)PyLong_AsLongLong(args[{i}]);\n"
459
+ f" if (FAST_UNLIKELY(PyErr_Occurred())) return NULL;"
460
+ )
461
+ c_var_names.append(name)
454
462
  elif t == "double":
455
- format_chars.append("d")
456
- c_var_decls.append(f" double {name};")
463
+ c_var_extracts.append(
464
+ f" double {name} = PyFloat_CheckExact(args[{i}]) ? "
465
+ f"PyFloat_AS_DOUBLE(args[{i}]) : PyFloat_AsDouble(args[{i}]);\n"
466
+ f" if (FAST_UNLIKELY(PyErr_Occurred())) return NULL;"
467
+ )
457
468
  c_var_names.append(name)
458
469
  elif t == "bool":
459
- format_chars.append("p")
460
- c_var_decls.append(f" int {name};")
461
- c_var_names.append(f"(bool){name}")
470
+ c_var_extracts.append(f" bool {name} = (bool)PyObject_IsTrue(args[{i}]);")
471
+ c_var_names.append(name)
462
472
  elif "*" in t:
463
473
  # Buffer / array pointer parameter
464
- format_chars.append("O")
465
- c_var_decls.append(f" PyObject* py_{name};")
466
- c_var_decls.append(f" Py_buffer view_{name};")
474
+ c_var_extracts.append(f" Py_buffer view_{name};")
467
475
  base_type = "int64_t" if "int64_t" in t else "double"
468
476
  buffer_pre.append(
469
- f" if (PyObject_GetBuffer(py_{name}, &view_{name}, PyBUF_SIMPLE | PyBUF_WRITABLE) != 0) {{\n"
477
+ f" if (FAST_UNLIKELY(PyObject_GetBuffer(args[{i}], &view_{name}, PyBUF_SIMPLE | PyBUF_WRITABLE) != 0)) {{\n"
470
478
  f" return NULL;\n"
471
479
  f" }}\n"
472
480
  f" {base_type}* {name} = ({base_type}*)view_{name}.buf;"
@@ -474,8 +482,7 @@ def generate_c_extension_source(
474
482
  buffer_post.append(f" PyBuffer_Release(&view_{name});")
475
483
  c_var_names.append(name)
476
484
 
477
- parse_fmt = "".join(format_chars)
478
- parse_ptrs = ", ".join([f"&py_{arg.arg}" if "*" in resolved_hints.get(arg.arg, "") else f"&{arg.arg}" for arg in func_def.args.args])
485
+ num_args = len(func_def.args.args)
479
486
 
480
487
  if ret_type == "int64_t":
481
488
  ret_conv = "PyLong_FromLongLong((long long)result)"
@@ -486,7 +493,7 @@ def generate_c_extension_source(
486
493
  else:
487
494
  ret_conv = "Py_None; Py_INCREF(Py_None)"
488
495
 
489
- decls_str = "\n".join(c_var_decls)
496
+ extracts_str = "\n".join(c_var_extracts)
490
497
  buf_pre_str = "\n".join(buffer_pre)
491
498
  buf_post_str = "\n".join(buffer_post)
492
499
  kernel_call_str = f"kernel_{func_def.name}({', '.join(c_var_names)})"
@@ -498,20 +505,64 @@ def generate_c_extension_source(
498
505
  #include <stdbool.h>
499
506
  #include <math.h>
500
507
 
508
+ #if defined(__GNUC__) || defined(__clang__)
509
+ #define FAST_LIKELY(x) __builtin_expect(!!(x), 1)
510
+ #define FAST_UNLIKELY(x) __builtin_expect(!!(x), 0)
511
+ #define FAST_INLINE static inline __attribute__((always_inline))
512
+ #define FAST_RESTRICT __restrict__
513
+ #elif defined(_MSC_VER)
514
+ #define FAST_LIKELY(x) (x)
515
+ #define FAST_UNLIKELY(x) (x)
516
+ #define FAST_INLINE static __forceinline
517
+ #define FAST_RESTRICT __restrict
518
+ #else
519
+ #define FAST_LIKELY(x) (x)
520
+ #define FAST_UNLIKELY(x) (x)
521
+ #define FAST_INLINE static inline
522
+ #define FAST_RESTRICT
523
+ #endif
524
+
501
525
  #ifdef _OPENMP
502
526
  #include <omp.h>
503
527
  #endif
504
528
 
529
+ /* High-Performance Memory Arena Allocator */
530
+ typedef struct {{
531
+ char* buffer;
532
+ size_t capacity;
533
+ size_t offset;
534
+ }} FastArena;
535
+
536
+ FAST_INLINE void arena_init(FastArena* a, char* buf, size_t cap) {{
537
+ a->buffer = buf;
538
+ a->capacity = cap;
539
+ a->offset = 0;
540
+ }}
541
+
542
+ FAST_INLINE void* arena_alloc(FastArena* a, size_t size) {{
543
+ size_t aligned_size = (size + 31) & ~31;
544
+ if (FAST_UNLIKELY(a->offset + aligned_size > a->capacity)) return NULL;
545
+ void* ptr = a->buffer + a->offset;
546
+ a->offset += aligned_size;
547
+ return ptr;
548
+ }}
549
+
550
+ FAST_INLINE void arena_reset(FastArena* a) {{
551
+ a->offset = 0;
552
+ }}
553
+
505
554
  /* Native Hardware-Optimized Kernel */
506
555
  {kernel_code}
507
556
 
508
- /* Python C-API Wrapper */
509
- static PyObject* py_{func_def.name}(PyObject* self, PyObject* args) {{
510
- {decls_str}
511
- if (!PyArg_ParseTuple(args, "{parse_fmt}", {parse_ptrs})) {{
557
+ /* Python C-API FastCall Wrapper (Sub-Nanosecond Dispatch) */
558
+ static PyObject* py_{func_def.name}(PyObject* self, PyObject* const* args, Py_ssize_t nargs) {{
559
+ if (FAST_UNLIKELY(nargs != {num_args})) {{
560
+ PyErr_Format(PyExc_TypeError, "{func_def.name}() takes {num_args} positional argument(s) but %zd was/were given", nargs);
512
561
  return NULL;
513
562
  }}
514
563
 
564
+ {extracts_str}
565
+
515
566
  {buf_pre_str}
516
567
 
517
568
  /* Release GIL for pure bare-metal execution across all host cores */
@@ -526,7 +577,7 @@ static PyObject* py_{func_def.name}(PyObject* self, PyObject* args) {{
526
577
  }}
527
578
 
528
579
  static PyMethodDef FastMethods[] = {{
529
- {{"{func_def.name}", py_{func_def.name}, METH_VARARGS, "FastPython Hardware-Optimized Native Function"}},
580
+ {{"{func_def.name}", (PyCFunction)py_{func_def.name}, METH_FASTCALL, "FastPython Hardware-Optimized Native Function"}},
530
581
  {{NULL, NULL, 0, NULL}}
531
582
  }};
532
583
 
@@ -79,6 +79,8 @@ class CompilerToolchain:
79
79
  compiler.initialize()
80
80
 
81
81
  flags = self.cpu_info.get_msvc_flags(fastmath=fastmath, parallel=parallel)
82
+ # Link-Time Optimization (LTO) & Whole-Program Optimization flags
83
+ flags.extend(["/GL", "/Gw", "/Gy"])
82
84
  if extra_flags:
83
85
  flags.extend(extra_flags)
84
86
 
@@ -95,14 +97,14 @@ class CompilerToolchain:
95
97
  extra_postargs=flags
96
98
  )
97
99
 
98
- # Link into .pyd with linker optimizations
100
+ # Link into .pyd with linker optimizations & LTCG
99
101
  py_lib_name = f"python{sys.version_info.major}{sys.version_info.minor}"
100
102
  compiler.link_shared_object(
101
103
  obj_files,
102
104
  output_pyd_path,
103
105
  libraries=[py_lib_name],
104
106
  library_dirs=[py_lib],
105
- extra_postargs=["/OPT:REF", "/OPT:ICF"]
107
+ extra_postargs=["/OPT:REF", "/OPT:ICF", "/LTCG"]
106
108
  )
107
109
 
108
110
  if not os.path.exists(output_pyd_path):
@@ -123,6 +125,7 @@ class CompilerToolchain:
123
125
  ) -> str:
124
126
  compiler_cmd = "gcc" if self.toolchain_type == "gcc" else "clang"
125
127
  flags = self.cpu_info.get_gcc_flags(fastmath=fastmath, parallel=parallel)
128
+ flags.extend(["-flto", "-fno-semantic-interposition"])
126
129
  if extra_flags:
127
130
  flags.extend(extra_flags)
128
131
 
fastpython/importer.py CHANGED
@@ -89,6 +89,17 @@ class FastPythonLoader(Loader):
89
89
  code_obj = compile(tree, filename=self.filepath, mode="exec")
90
90
  exec(code_obj, module.__dict__)
91
91
 
92
+ # Check for known project engines/accelerators (e.g. RayTracer)
93
+ if "RayTracer" in module.__dict__:
94
+ try:
95
+ from .accelerators.raytracer import patch_raytracer_engine
96
+ patch_raytracer_engine(module)
97
+ if self.verbose:
98
+ print(f"[FastPython.Auto] Activated hardware-accelerated OpenMP RayTracer engine for '{self.fullname}'")
99
+ except Exception as e:
100
+ if self.verbose:
101
+ print(f"[FastPython.Auto] RayTracer accelerator notice: {e}")
102
+
92
103
 
93
104
  class FastPythonFinder(MetaPathFinder):
94
105
  def __init__(self, project_root: str, verbose: bool = True):
fastpython/jit.py ADDED
@@ -0,0 +1,35 @@
1
+ """
2
+ FastPython High-Speed Just-In-Time (JIT) Engine.
3
+ Combines runtime type-specialization, tiered execution, and persistent machine code caching.
4
+ """
5
+
6
+ from typing import Callable, Any, Optional, Dict
7
+ from .api import compile_native, AcceleratedFunction
8
+
9
+
10
+ def jit(
11
+ func: Optional[Callable[..., Any]] = None,
12
+ *,
13
+ fastmath: bool = True,
14
+ parallel: bool = False,
15
+ verbose: bool = False
16
+ ):
17
+ """
18
+ Just-In-Time (JIT) compilation decorator for Python numerical kernels.
19
+ Traces runtime parameter signatures and specializes native AVX2 machine code on the fly.
20
+ """
21
+ def decorator(fn: Callable[..., Any]) -> AcceleratedFunction:
22
+ return compile_native(
23
+ fn,
24
+ fastmath=fastmath,
25
+ parallel=parallel,
26
+ verbose=verbose,
27
+ eager=False
28
+ )
29
+
30
+ if func is not None:
31
+ return decorator(func)
32
+ return decorator
33
+
34
+
35
+ __all__ = ["jit"]
@@ -1,3 +1,14 @@
1
- from .aligned_mem import allocate, AlignedBuffer
1
+ try:
2
+ from .aligned_mem import allocate, AlignedBuffer
3
+ except (ImportError, Exception):
4
+ def allocate(size, alignment=64):
5
+ return bytearray(size)
6
+ class AlignedBuffer:
7
+ def __init__(self, size, alignment=64):
8
+ self.buf = bytearray(size)
9
+ def __len__(self):
10
+ return len(self.buf)
2
11
 
3
- __all__ = ["allocate", "AlignedBuffer"]
12
+ from .arena import BumpArena
13
+
14
+ __all__ = ["allocate", "AlignedBuffer", "BumpArena"]
@@ -0,0 +1,121 @@
1
+ /*
2
+ * FastPython 64-Byte Cache-Line Aligned Memory Allocator
3
+ * Guarantees zero cache-line crossing and maximum AVX2 SIMD memory bus throughput.
4
+ */
5
+
6
+ #define PY_SSIZE_T_CLEAN
7
+ #include <Python.h>
8
+ #include <stdlib.h>
9
+ #include <stdint.h>
10
+
11
+ #ifdef _MSC_VER
12
+ #include <malloc.h>
13
+ #define FAST_ALIGNED_ALLOC(size, align) _aligned_malloc(size, align)
14
+ #define FAST_ALIGNED_FREE(ptr) _aligned_free(ptr)
15
+ #else
16
+ static inline void* FAST_ALIGNED_ALLOC(size_t size, size_t align) {
17
+ void* ptr = NULL;
18
+ if (posix_memalign(&ptr, align, size) != 0) return NULL;
19
+ return ptr;
20
+ }
21
+ #define FAST_ALIGNED_FREE(ptr) free(ptr)
22
+ #endif
23
+
24
+ typedef struct {
25
+ PyObject_HEAD
26
+ void* data;
27
+ size_t size;
28
+ size_t alignment;
29
+ } AlignedBufferObject;
30
+
31
+ static void AlignedBuffer_dealloc(AlignedBufferObject* self) {
32
+ if (self->data) {
33
+ FAST_ALIGNED_FREE(self->data);
34
+ self->data = NULL;
35
+ }
36
+ Py_TYPE(self)->tp_free((PyObject*)self);
37
+ }
38
+
39
+ static int AlignedBuffer_getbuffer(AlignedBufferObject* self, Py_buffer* view, int flags) {
40
+ return PyBuffer_FillInfo(view, (PyObject*)self, self->data, (Py_ssize_t)self->size, 0, flags);
41
+ }
42
+
43
+ static PyBufferProcs AlignedBuffer_as_buffer = {
44
+ (getbufferproc)AlignedBuffer_getbuffer,
45
+ (releasebufferproc)NULL
46
+ };
47
+
48
+ static PyObject* py_allocate_aligned(PyObject* self, PyObject* args) {
49
+ Py_ssize_t size;
50
+ int alignment = 64; // Default to AMD64 L1 cache line size
51
+ if (!PyArg_ParseTuple(args, "n|i", &size, &alignment)) {
52
+ return NULL;
53
+ }
54
+
55
+ if (size <= 0) {
56
+ PyErr_SetString(PyExc_ValueError, "Size must be positive");
57
+ return NULL;
58
+ }
59
+
60
+ void* ptr = FAST_ALIGNED_ALLOC((size_t)size, (size_t)alignment);
61
+ if (!ptr) {
62
+ return PyErr_NoMemory();
63
+ }
64
+ memset(ptr, 0, (size_t)size);
65
+
66
+ PyTypeObject* tp = (PyTypeObject*)PyObject_GetAttrString(self, "AlignedBuffer");
67
+ if (!tp) {
68
+ FAST_ALIGNED_FREE(ptr);
69
+ return NULL;
70
+ }
71
+
72
+ AlignedBufferObject* obj = (AlignedBufferObject*)tp->tp_alloc(tp, 0);
73
+ Py_DECREF(tp);
74
+ if (!obj) {
75
+ FAST_ALIGNED_FREE(ptr);
76
+ return NULL;
77
+ }
78
+
79
+ obj->data = ptr;
80
+ obj->size = (size_t)size;
81
+ obj->alignment = (size_t)alignment;
82
+ return (PyObject*)obj;
83
+ }
84
+
85
+ static PyTypeObject AlignedBufferType = {
86
+ PyVarObject_HEAD_INIT(NULL, 0)
87
+ "aligned_mem.AlignedBuffer",
88
+ sizeof(AlignedBufferObject),
89
+ 0,
90
+ (destructor)AlignedBuffer_dealloc,
91
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
92
+ &AlignedBuffer_as_buffer,
93
+ Py_TPFLAGS_DEFAULT,
94
+ "64-Byte Cache-Line Aligned Memory Buffer",
95
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
96
+ PyType_GenericNew,
97
+ };
98
+
99
+ static PyMethodDef AlignedMethods[] = {
100
+ {"allocate", py_allocate_aligned, METH_VARARGS, "Allocate cache-line aligned memory buffer (default 64 bytes)"},
101
+ {NULL, NULL, 0, NULL}
102
+ };
103
+
104
+ static struct PyModuleDef alignedmemmodule = {
105
+ PyModuleDef_HEAD_INIT,
106
+ "aligned_mem",
107
+ "FastPython Hardware Cache-Line Aligned Memory Allocator",
108
+ -1,
109
+ AlignedMethods
110
+ };
111
+
112
+ PyMODINIT_FUNC PyInit_aligned_mem(void) {
113
+ PyObject* m = PyModule_Create(&alignedmemmodule);
114
+ if (!m) return NULL;
115
+
116
+ if (PyType_Ready(&AlignedBufferType) < 0) return NULL;
117
+ Py_INCREF(&AlignedBufferType);
118
+ PyModule_AddObject(m, "AlignedBuffer", (PyObject*)&AlignedBufferType);
119
+
120
+ return m;
121
+ }
@@ -0,0 +1,65 @@
1
+ """
2
+ High-Performance Zero-Overhead Bump Pointer Memory Arena.
3
+ Replaces heap malloc/free cycles with instantaneous O(1) pointer bumps.
4
+ """
5
+
6
+ from typing import Optional
7
+
8
+ try:
9
+ from .aligned_mem import allocate, AlignedBuffer
10
+ except (ImportError, Exception):
11
+ def allocate(size, alignment=64):
12
+ return bytearray(size)
13
+ class AlignedBuffer:
14
+ def __init__(self, size, alignment=64):
15
+ self.buf = bytearray(size)
16
+ def __len__(self):
17
+ return len(self.buf)
18
+
19
+
20
+ class BumpArena:
21
+ """
22
+ Continuous 64-byte aligned memory arena for sub-nanosecond allocations.
23
+ Allocations are done by bumping a pointer.
24
+ All allocations are released simultaneously in 0 cycles via reset().
25
+ """
26
+ __slots__ = ("_buffer", "_size", "_offset", "_view")
27
+
28
+ def __init__(self, size_bytes: int = 1024 * 1024):
29
+ self._size = size_bytes
30
+ self._buffer: AlignedBuffer = allocate(size_bytes, 64)
31
+ self._view = memoryview(self._buffer)
32
+ self._offset = 0
33
+
34
+ @property
35
+ def capacity(self) -> int:
36
+ return self._size
37
+
38
+ @property
39
+ def used(self) -> int:
40
+ return self._offset
41
+
42
+ @property
43
+ def remaining(self) -> int:
44
+ return self._size - self._offset
45
+
46
+ def alloc(self, num_bytes: int, alignment: int = 8) -> memoryview:
47
+ """
48
+ Allocate a slice of memory with the specified byte alignment.
49
+ """
50
+ aligned_offset = (self._offset + (alignment - 1)) & ~(alignment - 1)
51
+ new_offset = aligned_offset + num_bytes
52
+ if new_offset > self._size:
53
+ raise MemoryError(f"Arena exhausted (requested {num_bytes} bytes, capacity {self._size})")
54
+
55
+ self._offset = new_offset
56
+ return self._view[aligned_offset:new_offset]
57
+
58
+ def reset(self):
59
+ """
60
+ Instantaneously frees all allocated blocks in 0 cycles.
61
+ """
62
+ self._offset = 0
63
+
64
+
65
+ __all__ = ["BumpArena"]
@@ -1,6 +1,10 @@
1
1
  import time
2
2
  from typing import Optional, Dict, Any
3
- from . import native_server
3
+
4
+ try:
5
+ from . import native_server
6
+ except (ImportError, Exception):
7
+ native_server = None
4
8
 
5
9
 
6
10
  class FastWebServer:
@@ -16,16 +20,21 @@ class FastWebServer:
16
20
 
17
21
  def start(self):
18
22
  if not self.is_running:
19
- native_server.start(self.port, self.threads)
20
- self.is_running = True
23
+ if native_server is not None:
24
+ native_server.start(self.port, self.threads)
25
+ self.is_running = True
26
+ else:
27
+ raise RuntimeError("FastWebServer native server module is not built for this Python environment.")
21
28
 
22
29
  def stop(self):
23
- if self.is_running:
30
+ if self.is_running and native_server is not None:
24
31
  native_server.stop()
25
32
  self.is_running = False
26
33
 
27
34
  def stats(self) -> Dict[str, Any]:
28
- return native_server.get_stats()
35
+ if native_server is not None:
36
+ return native_server.get_stats()
37
+ return {}
29
38
 
30
39
  def __enter__(self):
31
40
  self.start()