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.
- fastpython/__init__.py +19 -0
- fastpython/__main__.py +4 -0
- fastpython/api.py +185 -0
- fastpython/auto.py +10 -0
- fastpython/cli.py +84 -0
- fastpython/codegen/__init__.py +3 -0
- fastpython/codegen/transpiler.py +545 -0
- fastpython/compiler/__init__.py +4 -0
- fastpython/compiler/cache.py +57 -0
- fastpython/compiler/toolchain.py +147 -0
- fastpython/hardware/__init__.py +3 -0
- fastpython/hardware/cpu.py +209 -0
- fastpython/importer.py +181 -0
- fastpython/memory/__init__.py +3 -0
- fastpython/server/__init__.py +38 -0
- fastpython/simd/__init__.py +10 -0
- fastpython_native-0.1.0.dist-info/METADATA +108 -0
- fastpython_native-0.1.0.dist-info/RECORD +21 -0
- fastpython_native-0.1.0.dist-info/WHEEL +5 -0
- fastpython_native-0.1.0.dist-info/entry_points.txt +2 -0
- fastpython_native-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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,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,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"]
|