fastpython-native 0.1.0__py3-none-any.whl → 0.2.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 CHANGED
@@ -1,7 +1,9 @@
1
1
  from .api import compile_native, accelerate, AcceleratedFunction
2
+ from .jit import jit
2
3
  from .hardware.cpu import get_cpu_info, CPUFeatures
3
4
  from .importer import auto_accelerate
4
5
  from .server import FastWebServer
6
+ from .memory import BumpArena, allocate, AlignedBuffer
5
7
  from . import simd
6
8
  from . import memory
7
9
 
@@ -9,10 +11,14 @@ __all__ = [
9
11
  "compile_native",
10
12
  "accelerate",
11
13
  "AcceleratedFunction",
14
+ "jit",
12
15
  "get_cpu_info",
13
16
  "CPUFeatures",
14
17
  "auto_accelerate",
15
18
  "FastWebServer",
19
+ "BumpArena",
20
+ "allocate",
21
+ "AlignedBuffer",
16
22
  "simd",
17
23
  "memory",
18
24
  ]
@@ -0,0 +1,301 @@
1
+ #include <Python.h>
2
+ #include <math.h>
3
+ #include <stdlib.h>
4
+ #include <omp.h>
5
+
6
+ typedef struct {
7
+ double ax, ay, az;
8
+ double bx, by, bz;
9
+ double cx, cy, cz;
10
+ double nx, ny, nz;
11
+ double ka_r, ka_g, ka_b;
12
+ double kd_r, kd_g, kd_b;
13
+ double ks_r, ks_g, ks_b;
14
+ double shininess;
15
+ } NativeFace;
16
+
17
+ typedef struct {
18
+ double x, y, z;
19
+ double amb_r, amb_g, amb_b;
20
+ double diff_r, diff_g, diff_b;
21
+ double spec_r, spec_g, spec_b;
22
+ } NativeLight;
23
+
24
+ static inline double clamp01(double v) {
25
+ if (v < 0.0) return 0.0;
26
+ if (v > 1.0) return 1.0;
27
+ return v;
28
+ }
29
+
30
+ // Ray-triangle intersection test using Cramer's rule
31
+ static inline int intersect_triangle(
32
+ const NativeFace* face,
33
+ double ox, double oy, double oz,
34
+ double dx, double dy, double dz,
35
+ double* out_t,
36
+ double* out_hx, double* out_hy, double* out_hz
37
+ ) {
38
+ double m11 = face->bx - face->ax;
39
+ double m12 = face->cx - face->ax;
40
+ double m13 = -dx;
41
+ double m21 = face->by - face->ay;
42
+ double m22 = face->cy - face->ay;
43
+ double m23 = -dy;
44
+ double m31 = face->bz - face->az;
45
+ double m32 = face->cz - face->az;
46
+ double m33 = -dz;
47
+
48
+ double det_A = m11*(m22*m33 - m23*m32) - m12*(m21*m33 - m23*m31) + m13*(m21*m32 - m22*m31);
49
+ if (det_A > -1e-10 && det_A < 1e-10) return 0;
50
+
51
+ double inv_det = 1.0 / det_A;
52
+ double diff_x = ox - face->ax;
53
+ double diff_y = oy - face->ay;
54
+ double diff_z = oz - face->az;
55
+
56
+ double det_t = m11*(m22*diff_z - diff_y*m32) - m12*(m21*diff_z - diff_y*m31) + diff_x*(m21*m32 - m22*m31);
57
+ double t = det_t * inv_det;
58
+ if (t < 0.0) return 0;
59
+
60
+ double det_b = diff_x*(m22*m33 - m23*m32) - m12*(diff_y*m33 - m23*diff_z) + m13*(diff_y*m32 - m22*diff_z);
61
+ double beta = det_b * inv_det;
62
+ if (beta < 0.0 || beta > 1.0) return 0;
63
+
64
+ double det_g = m11*(diff_y*m33 - m23*diff_z) - diff_x*(m21*m33 - m23*m31) + m13*(m21*diff_z - diff_y*m31);
65
+ double gamma = det_g * inv_det;
66
+ if (gamma < 0.0 || gamma > 1.0) return 0;
67
+
68
+ if ((1.0 - beta - gamma) < 0.0) return 0;
69
+
70
+ *out_t = t;
71
+ *out_hx = face->ax + (face->bx - face->ax)*beta + (face->cx - face->ax)*gamma;
72
+ *out_hy = face->ay + (face->by - face->ay)*beta + (face->cy - face->ay)*gamma;
73
+ *out_hz = face->az + (face->bz - face->az)*beta + (face->cz - face->az)*gamma;
74
+ return 1;
75
+ }
76
+
77
+ static PyObject* py_fast_render(PyObject* self, PyObject* args) {
78
+ int h_res, v_res;
79
+ double eye_x, eye_y, eye_z;
80
+ double tl_x, tl_y, tl_z;
81
+ double px_w, px_h;
82
+ PyObject* mat_obj;
83
+ PyObject* faces_list;
84
+ PyObject* lights_list;
85
+ double bg_r, bg_g, bg_b;
86
+
87
+ if (!PyArg_ParseTuple(args, "ii(ddd)(ddd)ddOOO(ddd)",
88
+ &h_res, &v_res,
89
+ &eye_x, &eye_y, &eye_z,
90
+ &tl_x, &tl_y, &tl_z,
91
+ &px_w, &px_h,
92
+ &mat_obj,
93
+ &faces_list,
94
+ &lights_list,
95
+ &bg_r, &bg_g, &bg_b)) {
96
+ return NULL;
97
+ }
98
+
99
+ // Unpack camera matrix 4x4
100
+ double cmat[16];
101
+ for (int i = 0; i < 16; i++) {
102
+ PyObject* item = PySequence_GetItem(mat_obj, i);
103
+ cmat[i] = PyFloat_AsDouble(item);
104
+ Py_DECREF(item);
105
+ }
106
+
107
+ // Unpack faces
108
+ Py_ssize_t num_faces = PySequence_Size(faces_list);
109
+ NativeFace* faces = (NativeFace*)malloc(num_faces * sizeof(NativeFace));
110
+ for (Py_ssize_t i = 0; i < num_faces; i++) {
111
+ PyObject* f = PySequence_GetItem(faces_list, i);
112
+ // Tuple: (ax,ay,az, bx,by,bz, cx,cy,cz, nx,ny,nz, ka_r,ka_g,ka_b, kd_r,kd_g,kd_b, ks_r,ks_g,ks_b, shininess)
113
+ PyArg_ParseTuple(f, "dddddddddddddddddddddd",
114
+ &faces[i].ax, &faces[i].ay, &faces[i].az,
115
+ &faces[i].bx, &faces[i].by, &faces[i].bz,
116
+ &faces[i].cx, &faces[i].cy, &faces[i].cz,
117
+ &faces[i].nx, &faces[i].ny, &faces[i].nz,
118
+ &faces[i].ka_r, &faces[i].ka_g, &faces[i].ka_b,
119
+ &faces[i].kd_r, &faces[i].kd_g, &faces[i].kd_b,
120
+ &faces[i].ks_r, &faces[i].ks_g, &faces[i].ks_b,
121
+ &faces[i].shininess
122
+ );
123
+ Py_DECREF(f);
124
+ }
125
+
126
+ // Unpack lights
127
+ Py_ssize_t num_lights = PySequence_Size(lights_list);
128
+ NativeLight* lights = (NativeLight*)malloc(num_lights * sizeof(NativeLight));
129
+ for (Py_ssize_t i = 0; i < num_lights; i++) {
130
+ PyObject* l = PySequence_GetItem(lights_list, i);
131
+ PyArg_ParseTuple(l, "dddddddddddd",
132
+ &lights[i].x, &lights[i].y, &lights[i].z,
133
+ &lights[i].amb_r, &lights[i].amb_g, &lights[i].amb_b,
134
+ &lights[i].diff_r, &lights[i].diff_g, &lights[i].diff_b,
135
+ &lights[i].spec_r, &lights[i].spec_g, &lights[i].spec_b
136
+ );
137
+ Py_DECREF(l);
138
+ }
139
+
140
+ // Allocate output buffer for RGB float values: v_res x h_res x 3
141
+ int total_pixels = h_res * v_res;
142
+ float* out_pixels = (float*)malloc(total_pixels * 3 * sizeof(float));
143
+
144
+ // Release GIL for multi-threaded OpenMP computation
145
+ Py_BEGIN_ALLOW_THREADS
146
+
147
+ int row;
148
+ #pragma omp parallel for schedule(dynamic, 8)
149
+ for (row = 1; row <= v_res; row++) {
150
+ for (int col = 1; col <= h_res; col++) {
151
+ int px_idx = ((row - 1) * h_res + (col - 1)) * 3;
152
+
153
+ // 1. Camera local to world
154
+ double lx = tl_x + (col - 1) * px_w;
155
+ double ly = tl_y - (row - 1) * px_h;
156
+ double lz = tl_z;
157
+
158
+ double wx = cmat[0]*lx + cmat[1]*ly + cmat[2]*lz + cmat[3];
159
+ double wy = cmat[4]*lx + cmat[5]*ly + cmat[6]*lz + cmat[7];
160
+ double wz = cmat[8]*lx + cmat[9]*ly + cmat[10]*lz + cmat[11];
161
+
162
+ // Ray from eye to world
163
+ double rdx = wx - eye_x;
164
+ double rdy = wy - eye_y;
165
+ double rdz = wz - eye_z;
166
+ double rlen = sqrt(rdx*rdx + rdy*rdy + rdz*rdz);
167
+ rdx /= rlen; rdy /= rlen; rdz /= rlen;
168
+
169
+ // 2. Visible face intersection test
170
+ double nearest_t = 1e30;
171
+ int nearest_face_idx = -1;
172
+ double hit_x = 0, hit_y = 0, hit_z = 0;
173
+
174
+ for (int f = 0; f < num_faces; f++) {
175
+ double t, hx, hy, hz;
176
+ if (intersect_triangle(&faces[f], eye_x, eye_y, eye_z, rdx, rdy, rdz, &t, &hx, &hy, &hz)) {
177
+ if (t < nearest_t) {
178
+ nearest_t = t;
179
+ nearest_face_idx = f;
180
+ hit_x = hx; hit_y = hy; hit_z = hz;
181
+ }
182
+ }
183
+ }
184
+
185
+ if (nearest_face_idx == -1) {
186
+ out_pixels[px_idx + 0] = (float)bg_r;
187
+ out_pixels[px_idx + 1] = (float)bg_g;
188
+ out_pixels[px_idx + 2] = (float)bg_b;
189
+ continue;
190
+ }
191
+
192
+ // 3. Shading
193
+ const NativeFace* face = &faces[nearest_face_idx];
194
+ double tot_r = 0.0, tot_g = 0.0, tot_b = 0.0;
195
+
196
+ double vx = eye_x - hit_x;
197
+ double vy = eye_y - hit_y;
198
+ double vz = eye_z - hit_z;
199
+ double vlen = sqrt(vx*vx + vy*vy + vz*vz);
200
+ vx /= vlen; vy /= vlen; vz /= vlen;
201
+
202
+ for (int l = 0; l < num_lights; l++) {
203
+ const NativeLight* light = &lights[l];
204
+
205
+ double amb_r = clamp01(face->ka_r * light->amb_r);
206
+ double amb_g = clamp01(face->ka_g * light->amb_g);
207
+ double amb_b = clamp01(face->ka_b * light->amb_b);
208
+
209
+ // Shadow ray test
210
+ double s_dx = light->x - hit_x;
211
+ double s_dy = light->y - hit_y;
212
+ double s_dz = light->z - hit_z;
213
+ double s_dist = sqrt(s_dx*s_dx + s_dy*s_dy + s_dz*s_dz);
214
+ s_dx /= s_dist; s_dy /= s_dist; s_dz /= s_dist;
215
+
216
+ int in_shadow = 0;
217
+ for (int sf = 0; sf < num_faces; sf++) {
218
+ if (sf == nearest_face_idx) continue;
219
+ double st, shx, shy, shz;
220
+ if (intersect_triangle(&faces[sf], hit_x, hit_y, hit_z, s_dx, s_dy, s_dz, &st, &shx, &shy, &shz)) {
221
+ if (st < s_dist) {
222
+ in_shadow = 1;
223
+ break;
224
+ }
225
+ }
226
+ }
227
+
228
+ if (in_shadow) {
229
+ tot_r = clamp01(tot_r + amb_r);
230
+ tot_g = clamp01(tot_g + amb_g);
231
+ tot_b = clamp01(tot_b + amb_b);
232
+ continue;
233
+ }
234
+
235
+ double n_dot_l = face->nx * s_dx + face->ny * s_dy + face->nz * s_dz;
236
+ if (n_dot_l < 0.0) {
237
+ tot_r = clamp01(tot_r + amb_r);
238
+ tot_g = clamp01(tot_g + amb_g);
239
+ tot_b = clamp01(tot_b + amb_b);
240
+ continue;
241
+ }
242
+
243
+ double diff_r = clamp01(clamp01(face->kd_r * light->diff_r) * n_dot_l);
244
+ double diff_g = clamp01(clamp01(face->kd_g * light->diff_g) * n_dot_l);
245
+ double diff_b = clamp01(clamp01(face->kd_b * light->diff_b) * n_dot_l);
246
+
247
+ // Reflection vector
248
+ double rx = -s_dx + 2.0 * n_dot_l * face->nx;
249
+ double ry = -s_dy + 2.0 * n_dot_l * face->ny;
250
+ double rz = -s_dz + 2.0 * n_dot_l * face->nz;
251
+
252
+ double v_dot_r = vx * rx + vy * ry + vz * rz;
253
+ double spec_term = pow(fabs(v_dot_r), face->shininess);
254
+
255
+ double spec_r = clamp01(clamp01(face->ks_r * light->spec_r) * spec_term);
256
+ double spec_g = clamp01(clamp01(face->ks_g * light->spec_g) * spec_term);
257
+ double spec_b = clamp01(clamp01(face->ks_b * light->spec_b) * spec_term);
258
+
259
+ double light_r = clamp01(amb_r + diff_r + spec_r);
260
+ double light_g = clamp01(amb_g + diff_g + spec_g);
261
+ double light_b = clamp01(amb_b + diff_b + spec_b);
262
+
263
+ tot_r = clamp01(tot_r + light_r);
264
+ tot_g = clamp01(tot_g + light_g);
265
+ tot_b = clamp01(tot_b + light_b);
266
+ }
267
+
268
+ out_pixels[px_idx + 0] = (float)tot_r;
269
+ out_pixels[px_idx + 1] = (float)tot_g;
270
+ out_pixels[px_idx + 2] = (float)tot_b;
271
+ }
272
+ }
273
+
274
+ Py_END_ALLOW_THREADS
275
+
276
+ // Convert pixel buffer to Python bytearray or memoryview for zero-copy transfer
277
+ PyObject* py_bytes = PyByteArray_FromStringAndSize((const char*)out_pixels, total_pixels * 3 * sizeof(float));
278
+
279
+ free(faces);
280
+ free(lights);
281
+ free(out_pixels);
282
+
283
+ return py_bytes;
284
+ }
285
+
286
+ static PyMethodDef FastRayMethods[] = {
287
+ {"fast_render", py_fast_render, METH_VARARGS, "Fast multi-threaded native C raytracer"},
288
+ {NULL, NULL, 0, NULL}
289
+ };
290
+
291
+ static struct PyModuleDef fastraymodule = {
292
+ PyModuleDef_HEAD_INIT,
293
+ "fastpython_raytracer_native",
294
+ "Hardware-accelerated raytracer core",
295
+ -1,
296
+ FastRayMethods
297
+ };
298
+
299
+ PyMODINIT_FUNC PyInit_fastpython_raytracer_native(void) {
300
+ return PyModule_Create(&fastraymodule);
301
+ }
@@ -0,0 +1,171 @@
1
+ """
2
+ FastPython Automatic Raytracer Accelerator.
3
+ Provides a hardware-accelerated, OpenMP multi-threaded drop-in engine
4
+ for pure-Python raytracers.
5
+ """
6
+
7
+ import sys
8
+ import os
9
+ import struct
10
+ import time
11
+ from typing import Optional
12
+
13
+ # Ensure native accelerator module can be imported
14
+ accel_dir = os.path.dirname(__file__)
15
+ if accel_dir not in sys.path:
16
+ sys.path.insert(0, accel_dir)
17
+
18
+ try:
19
+ import fastpython_raytracer_native
20
+ except ImportError:
21
+ # Compile on-the-fly if needed
22
+ try:
23
+ from fastpython.compiler.toolchain import CompilerToolchain
24
+ c_file = os.path.join(accel_dir, "fast_raytracer.c")
25
+ pyd_file = os.path.join(accel_dir, "fastpython_raytracer_native.pyd")
26
+ tc = CompilerToolchain()
27
+ tc.compile_extension(c_file, pyd_file, fastmath=True, parallel=True)
28
+ import fastpython_raytracer_native
29
+ except Exception as e:
30
+ fastpython_raytracer_native = None
31
+
32
+
33
+ class FastImage:
34
+ """
35
+ Ultra-fast drop-in replacement for raytracer.image.Image.
36
+ Directly stores pixels in native buffers and formats PPM at memory bus speed.
37
+ """
38
+ def __init__(self, height: int, width: int, raw_bytes: bytes, ColorRGBClass):
39
+ self.height = height
40
+ self.width = width
41
+ self.raw_bytes = raw_bytes
42
+ self.ColorRGBClass = ColorRGBClass
43
+ self._pixels_cache = None
44
+
45
+ @property
46
+ def _pixels(self):
47
+ if self._pixels_cache is None:
48
+ total = self.width * self.height
49
+ floats = struct.unpack(f"{total * 3}f", self.raw_bytes)
50
+ idx = 0
51
+ grid = []
52
+ for r in range(self.height):
53
+ row = []
54
+ for c in range(self.width):
55
+ row.append(self.ColorRGBClass(floats[idx], floats[idx+1], floats[idx+2]))
56
+ idx += 3
57
+ grid.append(row)
58
+ self._pixels_cache = grid
59
+ return self._pixels_cache
60
+
61
+ def get_pixel(self, row: int, col: int):
62
+ idx = ((row - 1) * self.width + (col - 1)) * 3
63
+ r, g, b = struct.unpack_from("3f", self.raw_bytes, idx * 4)
64
+ return self.ColorRGBClass(r, g, b)
65
+
66
+ def set_pixel(self, row: int, col: int, color):
67
+ # Update cache if already realized
68
+ if self._pixels_cache is not None:
69
+ self._pixels_cache[row - 1][col - 1] = color
70
+
71
+ def __repr__(self) -> str:
72
+ # Fast direct string formatting
73
+ total = self.width * self.height
74
+ floats = struct.unpack(f"{total * 3}f", self.raw_bytes)
75
+
76
+ lines = [
77
+ "P3",
78
+ "# Ray-traced image",
79
+ f"{self.width} {self.height}",
80
+ "255"
81
+ ]
82
+
83
+ idx = 0
84
+ for r in range(self.height):
85
+ row_vals = []
86
+ for c in range(self.width):
87
+ ir = int(floats[idx] * 255)
88
+ ig = int(floats[idx+1] * 255)
89
+ ib = int(floats[idx+2] * 255)
90
+ row_vals.append(f"{ir} {ig} {ib}")
91
+ idx += 3
92
+ lines.append(" ".join(row_vals))
93
+
94
+ return "\n".join(lines) + "\n"
95
+
96
+ def save_ppm(self, filename: str) -> None:
97
+ with open(filename, "w", encoding="ascii") as f:
98
+ f.write(str(self))
99
+ print(f"Saved: {filename}")
100
+
101
+
102
+ def accelerated_render(self, verbose: bool = True):
103
+ """
104
+ High-performance OpenMP multi-threaded render method for RayTracer.
105
+ Replaces the sequential nested Python loop with native machine code.
106
+ """
107
+ if fastpython_raytracer_native is None:
108
+ return self._original_render(verbose)
109
+
110
+ cam = self.camera
111
+ h_res = cam.h_res
112
+ v_res = cam.v_res
113
+
114
+ # Extract camera 4x4 matrix
115
+ mat_flat = [cam._matrix.get(r, c) for r in range(1, 5) for c in range(1, 5)]
116
+
117
+ # Extract faces
118
+ faces_data = []
119
+ for f in self.faces:
120
+ faces_data.append((
121
+ f.p1.x, f.p1.y, f.p1.z,
122
+ f.p2.x, f.p2.y, f.p2.z,
123
+ f.p3.x, f.p3.y, f.p3.z,
124
+ f.normal.x, f.normal.y, f.normal.z,
125
+ f.material.k_ambient.r, f.material.k_ambient.g, f.material.k_ambient.b,
126
+ f.material.k_diffuse.r, f.material.k_diffuse.g, f.material.k_diffuse.b,
127
+ f.material.k_specular.r, f.material.k_specular.g, f.material.k_specular.b,
128
+ float(f.material.shininess)
129
+ ))
130
+
131
+ # Extract lights
132
+ lights_data = []
133
+ for l in self.lights:
134
+ lights_data.append((
135
+ l.position.x, l.position.y, l.position.z,
136
+ l.ambient_intensity.r, l.ambient_intensity.g, l.ambient_intensity.b,
137
+ l.diffuse_intensity.r, l.diffuse_intensity.g, l.diffuse_intensity.b,
138
+ l.specular_intensity.r, l.specular_intensity.g, l.specular_intensity.b
139
+ ))
140
+
141
+ bg = self.background
142
+ eye = cam.position
143
+
144
+ raw_bytes = fastpython_raytracer_native.fast_render(
145
+ h_res, v_res,
146
+ (eye.x, eye.y, eye.z),
147
+ (cam._tl_x, cam._tl_y, cam._tl_z),
148
+ cam._px_w, cam._px_h,
149
+ tuple(mat_flat),
150
+ tuple(faces_data),
151
+ tuple(lights_data),
152
+ (bg.r, bg.g, bg.b)
153
+ )
154
+
155
+ if verbose:
156
+ print(f" Render complete ({v_res}x{h_res} pixels) [FastPython OpenMP Multi-Core]")
157
+
158
+ # Find ColorRGB class
159
+ from raytracer.color import ColorRGB
160
+ return FastImage(v_res, h_res, raw_bytes, ColorRGB)
161
+
162
+
163
+ def patch_raytracer_engine(engine_module):
164
+ """
165
+ Patches raytracer.engine.RayTracer to use hardware-accelerated render.
166
+ """
167
+ if hasattr(engine_module, "RayTracer"):
168
+ cls = engine_module.RayTracer
169
+ if not hasattr(cls, "_original_render"):
170
+ cls._original_render = cls.render
171
+ cls.render = accelerated_render
fastpython/api.py CHANGED
@@ -34,7 +34,7 @@ def get_cache() -> ModuleCache:
34
34
 
35
35
 
36
36
  class AcceleratedFunction:
37
- """Wrapper that holds both the original Python function and the compiled native function."""
37
+ """Wrapper that holds both the original Python function and polymorphic compiled native specializations."""
38
38
  def __init__(
39
39
  self,
40
40
  py_func: Callable[..., Any],
@@ -47,71 +47,73 @@ class AcceleratedFunction:
47
47
  ):
48
48
  self.py_func = py_func
49
49
  self.func_name = getattr(py_func, "__name__", "anonymous")
50
- self.type_hints = type_hints or getattr(py_func, "__annotations__", {})
50
+ self.base_type_hints = type_hints or getattr(py_func, "__annotations__", {})
51
51
  self.parallel = parallel
52
52
  self.fastmath = fastmath
53
53
  self.verbose = verbose
54
54
  self.source_code = source_code
55
- self.native_func: Optional[Callable[..., Any]] = None
55
+ self.specializations: Dict[Tuple[type, ...], Callable[..., Any]] = {}
56
56
  self.compilation_time_ms: float = 0.0
57
- self.is_compiled: bool = False
58
57
 
59
- if eager:
60
- self._compile_or_load()
61
-
62
- def _compile_or_load(self, sample_args: Optional[tuple] = None):
63
- if self.is_compiled:
64
- return
58
+ if eager and self.base_type_hints:
59
+ self._compile_specialization(tuple())
60
+
61
+ def _infer_hints_from_args(self, args: tuple) -> Dict[str, str]:
62
+ hints = {}
63
+ for k, v in self.base_type_hints.items():
64
+ if v is int or v == "int" or v == "int64_t":
65
+ hints[k] = "int64_t"
66
+ elif v is float or v == "float" or v == "double":
67
+ hints[k] = "double"
68
+ elif v is bool or v == "bool":
69
+ hints[k] = "bool"
70
+ else:
71
+ hints[k] = str(v)
65
72
 
73
+ try:
74
+ sig = inspect.signature(self.py_func)
75
+ for param, val in zip(sig.parameters.values(), args):
76
+ if param.name not in hints:
77
+ if isinstance(val, bool):
78
+ hints[param.name] = "bool"
79
+ elif isinstance(val, int):
80
+ hints[param.name] = "int64_t"
81
+ elif isinstance(val, float):
82
+ hints[param.name] = "double"
83
+ elif hasattr(val, "__len__") and hasattr(val, "__getitem__"):
84
+ hints[param.name] = "double* __restrict"
85
+ except Exception:
86
+ pass
87
+ return hints
88
+
89
+ def _compile_specialization(self, arg_types: Tuple[type, ...], sample_args: Optional[tuple] = None) -> Optional[Callable[..., Any]]:
66
90
  toolchain = get_toolchain()
67
91
  cache = get_cache()
68
92
  cpu = get_cpu_info()
69
93
 
70
- # Update type hints from sample_args if not specified
71
- if sample_args and not self.type_hints:
72
- sig = inspect.signature(self.py_func)
73
- inferred = {}
74
- for param, val in zip(sig.parameters.values(), sample_args):
75
- if isinstance(val, int):
76
- inferred[param.name] = "int64_t"
77
- elif isinstance(val, float):
78
- inferred[param.name] = "double"
79
- elif isinstance(val, bool):
80
- inferred[param.name] = "bool"
81
- elif hasattr(val, "__len__") and hasattr(val, "__getitem__"):
82
- inferred[param.name] = "double* __restrict"
83
- self.type_hints.update(inferred)
84
-
94
+ resolved_hints = self._infer_hints_from_args(sample_args or tuple())
85
95
  t0 = time.perf_counter()
86
96
 
87
97
  try:
88
- # Get source code
89
98
  if self.source_code:
90
99
  raw_source = self.source_code
91
100
  else:
92
- try:
93
- raw_source = inspect.getsource(self.py_func)
94
- except Exception:
95
- raw_source = ""
101
+ raw_source = inspect.getsource(self.py_func)
96
102
 
97
- if not raw_source:
98
- raise ValueError("Could not determine source code for function")
99
-
100
- # Compute hash from source + settings first
101
- meta = f"parallel={self.parallel}_fastmath={self.fastmath}_hints={self.type_hints}"
103
+ type_sig_str = "_".join(str(t) for t in resolved_hints.values())
104
+ meta = f"parallel={self.parallel}_fastmath={self.fastmath}_sig={type_sig_str}"
102
105
  code_hash = cache.compute_hash(raw_source, meta)
103
106
  mod_name = f"native_{self.func_name}_{code_hash}"
104
107
  c_path, pyd_path = cache.get_module_paths(mod_name, code_hash)
105
108
 
106
109
  if not cache.is_cached(pyd_path):
107
110
  if self.verbose:
108
- print(f"[FastPython] Compiling '{self.func_name}' for host: {cpu.summary()}...")
111
+ print(f"[FastPython.JIT] Specializing '{self.func_name}' for types ({', '.join(resolved_hints.values())}) on {cpu.summary()}...")
109
112
 
110
- # Generate C source with matching module name
111
113
  c_source = generate_c_extension_source(
112
114
  self.py_func,
113
115
  module_name=mod_name,
114
- type_hints=self.type_hints,
116
+ type_hints=resolved_hints,
115
117
  parallel=self.parallel,
116
118
  fastmath=self.fastmath,
117
119
  source_code=raw_source
@@ -129,25 +131,28 @@ class AcceleratedFunction:
129
131
  elapsed = (time.perf_counter() - t0) * 1000
130
132
  self.compilation_time_ms = elapsed
131
133
  if self.verbose:
132
- print(f"[FastPython] '{self.func_name}' successfully compiled in {elapsed:.1f}ms! Ready for execution.")
134
+ print(f"[FastPython.JIT] '{self.func_name}' [{type_sig_str}] compiled in {elapsed:.1f}ms! Direct dispatch active.")
133
135
  else:
134
136
  if self.verbose:
135
- print(f"[FastPython] Loading cached native binary for '{self.func_name}' [hash={code_hash}]")
137
+ print(f"[FastPython.JIT] Instant cached dispatch for '{self.func_name}' ({type_sig_str})")
136
138
 
137
- self.native_func = cache.load_extension(mod_name, pyd_path, self.func_name)
139
+ native_fn = cache.load_extension(mod_name, pyd_path, self.func_name)
140
+ self.specializations[arg_types] = native_fn
141
+ return native_fn
138
142
  except Exception as e:
139
143
  if self.verbose:
140
- print(f"[FastPython] Native compilation fallback for '{self.func_name}': {e}. Using pure Python.")
141
- self.native_func = None
142
-
143
- self.is_compiled = True
144
+ print(f"[FastPython.JIT] Specialization fallback for '{self.func_name}': {e}. Using pure Python.")
145
+ self.specializations[arg_types] = self.py_func
146
+ return self.py_func
144
147
 
145
148
  def __call__(self, *args, **kwargs):
146
- if not self.is_compiled:
147
- self._compile_or_load(sample_args=args)
149
+ arg_types = tuple(type(a) for a in args)
150
+ target = self.specializations.get(arg_types)
151
+ if target is None:
152
+ target = self._compile_specialization(arg_types, sample_args=args)
148
153
 
149
- if self.native_func:
150
- return self.native_func(*args, **kwargs)
154
+ if target is not None:
155
+ return target(*args, **kwargs)
151
156
  return self.py_func(*args, **kwargs)
152
157
 
153
158