fastpython-native 0.1.0__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.
@@ -0,0 +1,147 @@
1
+ """
2
+ Compiler toolchain for FastPython.
3
+ Handles discovery, configuration, and execution of host C/C++ compilers (MSVC, GCC, Clang)
4
+ to compile C-extensions targeting the host CPU architecture.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import shutil
10
+ import subprocess
11
+ from pathlib import Path
12
+ from typing import List, Optional
13
+ from ..hardware.cpu import get_cpu_info, CPUFeatures
14
+
15
+
16
+ class CompilationError(Exception):
17
+ pass
18
+
19
+
20
+ class CompilerToolchain:
21
+ def __init__(self):
22
+ self.cpu_info: CPUFeatures = get_cpu_info()
23
+ self.toolchain_type = self._detect_toolchain()
24
+
25
+ def _detect_toolchain(self) -> str:
26
+ if sys.platform == "win32":
27
+ # On Windows, try MSVC first
28
+ try:
29
+ from setuptools._distutils._msvccompiler import MSVCCompiler
30
+ comp = MSVCCompiler()
31
+ comp.initialize()
32
+ if comp.cc and os.path.exists(comp.cc):
33
+ return "msvc"
34
+ except Exception:
35
+ pass
36
+ if shutil.which("gcc"):
37
+ return "gcc"
38
+ if shutil.which("clang"):
39
+ return "clang"
40
+ else:
41
+ if shutil.which("gcc"):
42
+ return "gcc"
43
+ if shutil.which("clang"):
44
+ return "clang"
45
+
46
+ raise RuntimeError("No suitable native C/C++ compiler found on the system. Please ensure MSVC, GCC, or Clang is installed.")
47
+
48
+ def compile_extension(
49
+ self,
50
+ c_source_path: str,
51
+ output_pyd_path: str,
52
+ fastmath: bool = True,
53
+ parallel: bool = True,
54
+ extra_flags: Optional[List[str]] = None
55
+ ) -> str:
56
+ """
57
+ Compile a C source file into a native Python extension (.pyd or .so).
58
+ """
59
+ out_dir = str(Path(output_pyd_path).parent.resolve())
60
+ os.makedirs(out_dir, exist_ok=True)
61
+
62
+ if self.toolchain_type == "msvc":
63
+ return self._compile_msvc(c_source_path, output_pyd_path, fastmath, parallel, extra_flags)
64
+ else:
65
+ return self._compile_gcc_or_clang(c_source_path, output_pyd_path, fastmath, parallel, extra_flags)
66
+
67
+ def _compile_msvc(
68
+ self,
69
+ c_source_path: str,
70
+ output_pyd_path: str,
71
+ fastmath: bool,
72
+ parallel: bool,
73
+ extra_flags: Optional[List[str]]
74
+ ) -> str:
75
+ from setuptools._distutils._msvccompiler import MSVCCompiler
76
+
77
+ try:
78
+ compiler = MSVCCompiler()
79
+ compiler.initialize()
80
+
81
+ flags = self.cpu_info.get_msvc_flags(fastmath=fastmath, parallel=parallel)
82
+ if extra_flags:
83
+ flags.extend(extra_flags)
84
+
85
+ py_include = os.path.join(sys.base_prefix, "include")
86
+ py_lib = os.path.join(sys.base_prefix, "libs")
87
+
88
+ temp_dir = str(Path(output_pyd_path).parent)
89
+
90
+ # Compile source to .obj
91
+ obj_files = compiler.compile(
92
+ [c_source_path],
93
+ output_dir=temp_dir,
94
+ include_dirs=[py_include],
95
+ extra_postargs=flags
96
+ )
97
+
98
+ # Link into .pyd with linker optimizations
99
+ py_lib_name = f"python{sys.version_info.major}{sys.version_info.minor}"
100
+ compiler.link_shared_object(
101
+ obj_files,
102
+ output_pyd_path,
103
+ libraries=[py_lib_name],
104
+ library_dirs=[py_lib],
105
+ extra_postargs=["/OPT:REF", "/OPT:ICF"]
106
+ )
107
+
108
+ if not os.path.exists(output_pyd_path):
109
+ raise CompilationError(f"Linking failed: {output_pyd_path} was not created.")
110
+
111
+ return output_pyd_path
112
+
113
+ except Exception as e:
114
+ raise CompilationError(f"MSVC compilation failed: {e}") from e
115
+
116
+ def _compile_gcc_or_clang(
117
+ self,
118
+ c_source_path: str,
119
+ output_pyd_path: str,
120
+ fastmath: bool,
121
+ parallel: bool,
122
+ extra_flags: Optional[List[str]]
123
+ ) -> str:
124
+ compiler_cmd = "gcc" if self.toolchain_type == "gcc" else "clang"
125
+ flags = self.cpu_info.get_gcc_flags(fastmath=fastmath, parallel=parallel)
126
+ if extra_flags:
127
+ flags.extend(extra_flags)
128
+
129
+ py_include = os.path.join(sys.base_prefix, "include")
130
+ py_lib = os.path.join(sys.base_prefix, "libs")
131
+ py_lib_name = f"python{sys.version_info.major}{sys.version_info.minor}"
132
+
133
+ cmd = [
134
+ compiler_cmd,
135
+ "-shared",
136
+ c_source_path,
137
+ "-o", output_pyd_path,
138
+ f"-I{py_include}",
139
+ f"-L{py_lib}",
140
+ f"-l{py_lib_name}",
141
+ ] + flags
142
+
143
+ proc = subprocess.run(cmd, capture_output=True, text=True)
144
+ if proc.returncode != 0:
145
+ raise CompilationError(f"Compilation command failed with code {proc.returncode}:\n{proc.stderr}")
146
+
147
+ return output_pyd_path
@@ -0,0 +1,3 @@
1
+ from .cpu import CPUFeatures, get_cpu_info
2
+
3
+ __all__ = ["CPUFeatures", "get_cpu_info"]
@@ -0,0 +1,209 @@
1
+ """
2
+ Hardware detection module for FastPython.
3
+ Identifies host CPU architecture, instruction set extensions (AVX, AVX2, AVX-512, FMA, NEON),
4
+ core count, and generates optimal compiler optimization flags.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import platform
10
+ import ctypes
11
+ from ctypes import wintypes
12
+ from dataclasses import dataclass
13
+ from typing import List, Dict, Any
14
+
15
+
16
+ @dataclass
17
+ class CPUFeatures:
18
+ vendor: str
19
+ brand: str
20
+ architecture: str
21
+ physical_cores: int
22
+ logical_cores: int
23
+ has_avx: bool
24
+ has_avx2: bool
25
+ has_avx512f: bool
26
+ has_fma: bool
27
+ has_sse41: bool
28
+ has_sse42: bool
29
+ has_bmi1: bool
30
+ has_bmi2: bool
31
+ has_neon: bool
32
+
33
+ def summary(self) -> str:
34
+ features = []
35
+ if self.has_avx512f:
36
+ features.append("AVX-512")
37
+ elif self.has_avx2:
38
+ features.append("AVX2")
39
+ elif self.has_avx:
40
+ features.append("AVX")
41
+
42
+ if self.has_fma:
43
+ features.append("FMA3")
44
+ if self.has_sse42:
45
+ features.append("SSE4.2")
46
+ if self.has_bmi2:
47
+ features.append("BMI2")
48
+ if self.has_neon:
49
+ features.append("NEON")
50
+
51
+ feat_str = ", ".join(features) if features else "Baseline x86"
52
+ return f"{self.brand} ({self.logical_cores} threads, Features: {feat_str})"
53
+
54
+ def get_msvc_flags(self, fastmath: bool = True, parallel: bool = True) -> List[str]:
55
+ # Ultra-native bare-metal optimization flags for MSVC
56
+ flags = [
57
+ "/O2", # Maximize speed
58
+ "/Oi", # Enable intrinsic functions
59
+ "/Ot", # Favor fast code over small code
60
+ "/Gy", # Separate functions for linker
61
+ "/Gw", # Optimize global data
62
+ "/GS-", # Disable stack buffer security checks in compute kernels
63
+ "/Ob2", # Inline any suitable function
64
+ ]
65
+ if self.has_avx512f:
66
+ flags.append("/arch:AVX512")
67
+ elif self.has_avx2:
68
+ flags.append("/arch:AVX2")
69
+ elif self.has_avx:
70
+ flags.append("/arch:AVX")
71
+
72
+ if fastmath:
73
+ flags.append("/fp:fast")
74
+ if parallel and self.logical_cores > 1:
75
+ flags.append("/openmp")
76
+ return flags
77
+
78
+ def get_gcc_flags(self, fastmath: bool = True, parallel: bool = True) -> List[str]:
79
+ flags = [
80
+ "-O3",
81
+ "-march=native",
82
+ "-fPIC",
83
+ "-fomit-frame-pointer",
84
+ "-funroll-loops",
85
+ ]
86
+ if fastmath:
87
+ flags.append("-ffast-math")
88
+ if parallel and self.logical_cores > 1:
89
+ flags.append("-fopenmp")
90
+ return flags
91
+
92
+
93
+ def _detect_x86_cpuid() -> Dict[str, Any]:
94
+ """Execute CPUID on Windows AMD64 or Linux/macOS to query instruction sets."""
95
+ features = {
96
+ "vendor": "Unknown",
97
+ "brand": platform.processor() or "x86_64 Processor",
98
+ "has_avx": False,
99
+ "has_avx2": False,
100
+ "has_avx512f": False,
101
+ "has_fma": False,
102
+ "has_sse41": False,
103
+ "has_sse42": False,
104
+ "has_bmi1": False,
105
+ "has_bmi2": False,
106
+ }
107
+
108
+ if sys.platform == "win32" and platform.machine() in ("AMD64", "x86_64"):
109
+ try:
110
+ kernel32 = ctypes.windll.kernel32
111
+ kernel32.VirtualAlloc.restype = ctypes.c_void_p
112
+ kernel32.VirtualAlloc.argtypes = [wintypes.LPVOID, ctypes.c_size_t, wintypes.DWORD, wintypes.DWORD]
113
+ kernel32.VirtualFree.restype = wintypes.BOOL
114
+ kernel32.VirtualFree.argtypes = [wintypes.LPVOID, ctypes.c_size_t, wintypes.DWORD]
115
+
116
+ code = bytes([
117
+ 0x48, 0x89, 0x5C, 0x24, 0x08, # mov [rsp+8], rbx
118
+ 0x89, 0xC8, # mov eax, ecx
119
+ 0x89, 0xD1, # mov ecx, edx
120
+ 0x0F, 0xA2, # cpuid
121
+ 0x41, 0x89, 0x00, # mov [r8], eax
122
+ 0x41, 0x89, 0x58, 0x04, # mov [r8+4], ebx
123
+ 0x41, 0x89, 0x48, 0x08, # mov [r8+8], ecx
124
+ 0x41, 0x89, 0x50, 0x0C, # mov [r8+12], edx
125
+ 0x48, 0x8B, 0x5C, 0x24, 0x08, # mov rbx, [rsp+8]
126
+ 0xC3 # ret
127
+ ])
128
+ addr = kernel32.VirtualAlloc(None, len(code), 0x3000, 0x40)
129
+ if addr:
130
+ ctypes.memmove(addr, code, len(code))
131
+ func_type = ctypes.CFUNCTYPE(None, ctypes.c_uint32, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32 * 4))
132
+ cpuid_call = func_type(addr)
133
+
134
+ regs = (ctypes.c_uint32 * 4)()
135
+
136
+ # Vendor string (Leaf 0)
137
+ cpuid_call(0, 0, ctypes.byref(regs))
138
+ vendor_bytes = bytearray()
139
+ for reg_idx in [1, 3, 2]: # ebx, edx, ecx
140
+ vendor_bytes.extend(int(regs[reg_idx]).to_bytes(4, 'little'))
141
+ features["vendor"] = vendor_bytes.decode('ascii', errors='ignore').strip()
142
+
143
+ # Features (Leaf 1)
144
+ cpuid_call(1, 0, ctypes.byref(regs))
145
+ eax, ebx, ecx, edx = regs
146
+ features["has_fma"] = bool(ecx & (1 << 12))
147
+ features["has_sse41"] = bool(ecx & (1 << 19))
148
+ features["has_sse42"] = bool(ecx & (1 << 20))
149
+ features["has_avx"] = bool(ecx & (1 << 28))
150
+
151
+ # Extended features (Leaf 7)
152
+ cpuid_call(7, 0, ctypes.byref(regs))
153
+ eax, ebx, ecx, edx = regs
154
+ features["has_bmi1"] = bool(ebx & (1 << 3))
155
+ features["has_avx2"] = bool(ebx & (1 << 5))
156
+ features["has_bmi2"] = bool(ebx & (1 << 8))
157
+ features["has_avx512f"] = bool(ebx & (1 << 16))
158
+
159
+ # Brand String (Leaf 0x80000002 .. 0x80000004)
160
+ brand_bytes = bytearray()
161
+ for leaf in [0x80000002, 0x80000003, 0x80000004]:
162
+ cpuid_call(leaf, 0, ctypes.byref(regs))
163
+ for r in regs:
164
+ brand_bytes.extend(int(r).to_bytes(4, 'little'))
165
+ brand = brand_bytes.decode('ascii', errors='ignore').strip()
166
+ if brand:
167
+ features["brand"] = brand
168
+
169
+ kernel32.VirtualFree(addr, 0, 0x8000)
170
+ except Exception:
171
+ pass
172
+
173
+ return features
174
+
175
+
176
+ _CACHED_CPU_INFO = None
177
+
178
+
179
+ def get_cpu_info() -> CPUFeatures:
180
+ """Retrieve and cache host CPU capabilities."""
181
+ global _CACHED_CPU_INFO
182
+ if _CACHED_CPU_INFO is not None:
183
+ return _CACHED_CPU_INFO
184
+
185
+ arch = platform.machine()
186
+ logical_cores = os.cpu_count() or 4
187
+ physical_cores = max(1, logical_cores // 2)
188
+
189
+ has_neon = arch.lower() in ("arm64", "aarch64")
190
+
191
+ detected = _detect_x86_cpuid()
192
+
193
+ _CACHED_CPU_INFO = CPUFeatures(
194
+ vendor=detected["vendor"],
195
+ brand=detected["brand"],
196
+ architecture=arch,
197
+ physical_cores=physical_cores,
198
+ logical_cores=logical_cores,
199
+ has_avx=detected["has_avx"],
200
+ has_avx2=detected["has_avx2"],
201
+ has_avx512f=detected["has_avx512f"],
202
+ has_fma=detected["has_fma"],
203
+ has_sse41=detected["has_sse41"],
204
+ has_sse42=detected["has_sse42"],
205
+ has_bmi1=detected["has_bmi1"],
206
+ has_bmi2=detected["has_bmi2"],
207
+ has_neon=has_neon,
208
+ )
209
+ return _CACHED_CPU_INFO
fastpython/importer.py ADDED
@@ -0,0 +1,181 @@
1
+ """
2
+ Automatic Project Importer & AST Hook for FastPython.
3
+ Intercepts imports of project modules, detects computational functions,
4
+ and compiles them Ahead-of-Time (AOT) to hardware-native machine code.
5
+ """
6
+
7
+ import sys
8
+ import os
9
+ import ast
10
+ from pathlib import Path
11
+ from importlib.abc import MetaPathFinder, Loader
12
+ from importlib.machinery import ModuleSpec
13
+ from typing import Optional, List, Set
14
+
15
+ from .hardware.cpu import get_cpu_info
16
+
17
+
18
+ def is_acceleration_candidate(node: ast.FunctionDef) -> bool:
19
+ """
20
+ Check if a function is an ideal candidate for hardware machine code acceleration:
21
+ contains loops (for/while) and arithmetic, and avoids non-kernel constructs.
22
+ """
23
+ has_loop = False
24
+ has_arithmetic = False
25
+
26
+ for child in ast.walk(node):
27
+ # Disqualify generators or complex dynamic features
28
+ if isinstance(child, (ast.Yield, ast.YieldFrom, ast.AsyncFunctionDef, ast.Try)):
29
+ return False
30
+ if isinstance(child, (ast.For, ast.While)):
31
+ has_loop = True
32
+ if isinstance(child, (ast.BinOp, ast.AugAssign, ast.Compare)):
33
+ has_arithmetic = True
34
+
35
+ return has_loop or (has_arithmetic and len(node.body) > 1)
36
+
37
+
38
+ class FastPythonLoader(Loader):
39
+ def __init__(self, fullname: str, filepath: str, verbose: bool = True):
40
+ self.fullname = fullname
41
+ self.filepath = filepath
42
+ self.verbose = verbose
43
+
44
+ def create_module(self, spec: ModuleSpec):
45
+ return None # Use default module creation
46
+
47
+ def exec_module(self, module):
48
+ module.__file__ = self.filepath
49
+
50
+ with open(self.filepath, "r", encoding="utf-8") as f:
51
+ source = f.read()
52
+
53
+ try:
54
+ tree = ast.parse(source, filename=self.filepath)
55
+ except Exception:
56
+ # Fall back to standard execution if parsing fails
57
+ exec(compile(source, self.filepath, "exec"), module.__dict__)
58
+ return
59
+
60
+ candidates: List[str] = []
61
+ for node in tree.body:
62
+ if isinstance(node, ast.FunctionDef) and is_acceleration_candidate(node):
63
+ candidates.append(node.name)
64
+ # Inject decorator: @_fastpython_compile(fastmath=True)
65
+ call_node = ast.Call(
66
+ func=ast.Name(id="_fastpython_compile", ctx=ast.Load()),
67
+ args=[],
68
+ keywords=[
69
+ ast.keyword(arg="fastmath", value=ast.Constant(value=True)),
70
+ ast.keyword(arg="verbose", value=ast.Constant(value=self.verbose)),
71
+ ]
72
+ )
73
+ node.decorator_list.insert(0, call_node)
74
+
75
+ if candidates:
76
+ if self.verbose:
77
+ cpu = get_cpu_info()
78
+ print(f"[FastPython.Auto] Accelerating module '{self.fullname}' ({len(candidates)} functions: {', '.join(candidates)}) for {cpu.brand.strip()}...")
79
+
80
+ # Inject import of compile_native at top of module
81
+ import_stmt = ast.ImportFrom(
82
+ module="fastpython",
83
+ names=[ast.alias(name="compile_native", asname="_fastpython_compile")],
84
+ level=0
85
+ )
86
+ tree.body.insert(0, import_stmt)
87
+ ast.fix_missing_locations(tree)
88
+
89
+ code_obj = compile(tree, filename=self.filepath, mode="exec")
90
+ exec(code_obj, module.__dict__)
91
+
92
+
93
+ class FastPythonFinder(MetaPathFinder):
94
+ def __init__(self, project_root: str, verbose: bool = True):
95
+ self.project_root = Path(project_root).resolve()
96
+ self.verbose = verbose
97
+ self._cache_ignore_prefixes = {
98
+ sys.base_prefix.lower(),
99
+ sys.prefix.lower(),
100
+ }
101
+
102
+ def find_spec(self, fullname: str, path: Optional[List[str]] = None, target=None) -> Optional[ModuleSpec]:
103
+ # Ignore fastpython internal modules
104
+ if fullname.startswith("fastpython"):
105
+ return None
106
+
107
+ # Determine file path
108
+ mod_parts = fullname.split(".")
109
+ search_dirs = [self.project_root]
110
+ if path:
111
+ search_dirs = [Path(p).resolve() for p in path]
112
+ else:
113
+ # Check all sys.path directories belonging to the project
114
+ for p in sys.path:
115
+ try:
116
+ p_path = Path(p).resolve()
117
+ if p_path not in search_dirs and self._is_in_project(p_path):
118
+ search_dirs.append(p_path)
119
+ except Exception:
120
+ pass
121
+
122
+ for base_dir in search_dirs:
123
+ # Check for module.py
124
+ candidate_file = base_dir / f"{mod_parts[-1]}.py"
125
+ if candidate_file.is_file():
126
+ resolved = candidate_file.resolve()
127
+ if self._is_in_project(resolved):
128
+ return ModuleSpec(
129
+ fullname,
130
+ FastPythonLoader(fullname, str(resolved), verbose=self.verbose),
131
+ origin=str(resolved)
132
+ )
133
+
134
+ # Check for package/__init__.py
135
+ candidate_pkg = base_dir / mod_parts[-1] / "__init__.py"
136
+ if candidate_pkg.is_file():
137
+ resolved = candidate_pkg.resolve()
138
+ if self._is_in_project(resolved):
139
+ spec = ModuleSpec(
140
+ fullname,
141
+ FastPythonLoader(fullname, str(resolved), verbose=self.verbose),
142
+ origin=str(resolved),
143
+ is_package=True
144
+ )
145
+ spec.submodule_search_locations = [str(candidate_pkg.parent)]
146
+ return spec
147
+
148
+ return None
149
+
150
+ def _is_in_project(self, file_path: Path) -> bool:
151
+ path_str = str(file_path).lower()
152
+ for ignore in self._cache_ignore_prefixes:
153
+ if path_str.startswith(ignore) and "site-packages" in path_str:
154
+ return False
155
+ try:
156
+ file_path.relative_to(self.project_root)
157
+ return True
158
+ except ValueError:
159
+ return False
160
+
161
+
162
+ _HOOK_INSTALLED = False
163
+
164
+
165
+ def auto_accelerate(project_dir: Optional[str] = None, verbose: bool = True):
166
+ """
167
+ Installs the FastPython automatic compilation hook.
168
+ Every module imported in the project will be analyzed and compiled AOT into
169
+ hardware-native machine code before execution.
170
+ """
171
+ global _HOOK_INSTALLED
172
+ if _HOOK_INSTALLED:
173
+ return
174
+
175
+ root = project_dir or os.getcwd()
176
+ finder = FastPythonFinder(project_root=root, verbose=verbose)
177
+ sys.meta_path.insert(0, finder)
178
+ _HOOK_INSTALLED = True
179
+ if verbose:
180
+ cpu = get_cpu_info()
181
+ print(f"[FastPython.Auto] Hook active for project '{root}'. Hardware: {cpu.summary()}")
@@ -0,0 +1,3 @@
1
+ from .aligned_mem import allocate, AlignedBuffer
2
+
3
+ __all__ = ["allocate", "AlignedBuffer"]
@@ -0,0 +1,38 @@
1
+ import time
2
+ from typing import Optional, Dict, Any
3
+ from . import native_server
4
+
5
+
6
+ class FastWebServer:
7
+ """
8
+ High-performance, hardware-native web server.
9
+ Bypasses Python GIL, uses AVX2 SIMD request line parsing,
10
+ and distributes TCP connections across all hardware CPU threads.
11
+ """
12
+ def __init__(self, port: int = 8080, threads: int = 16):
13
+ self.port = port
14
+ self.threads = threads
15
+ self.is_running = False
16
+
17
+ def start(self):
18
+ if not self.is_running:
19
+ native_server.start(self.port, self.threads)
20
+ self.is_running = True
21
+
22
+ def stop(self):
23
+ if self.is_running:
24
+ native_server.stop()
25
+ self.is_running = False
26
+
27
+ def stats(self) -> Dict[str, Any]:
28
+ return native_server.get_stats()
29
+
30
+ def __enter__(self):
31
+ self.start()
32
+ return self
33
+
34
+ def __exit__(self, exc_type, exc_val, exc_tb):
35
+ self.stop()
36
+
37
+
38
+ __all__ = ["FastWebServer", "native_server"]
@@ -0,0 +1,10 @@
1
+ try:
2
+ from .fast_simd import find_char, find_crlf, parse_http_request, dumps
3
+ except ImportError:
4
+ # If not yet compiled in package directory
5
+ find_char = None
6
+ find_crlf = None
7
+ parse_http_request = None
8
+ dumps = None
9
+
10
+ __all__ = ["find_char", "find_crlf", "parse_http_request", "dumps"]