File Coverage

blib/lib/Hypersonic/Future.pm
Criterion Covered Total %
statement 120 129 93.0
branch 2 4 50.0
condition 4 8 50.0
subroutine 17 20 85.0
pod 0 6 0.0
total 143 167 85.6


line stmt bran cond sub pod time code
1             package Hypersonic::Future;
2              
3 9     9   420246 use strict;
  9         13  
  9         260  
4 9     9   81 use warnings;
  9         10  
  9         323  
5 9     9   169 use 5.010;
  9         23  
6              
7             our $VERSION = '0.15';
8              
9             # High-performance JIT-compiled Future for async operations
10             # Serves as the foundation for all async in Hypersonic:
11             # - DB operations (thread pool)
12             # - HTTP client (UA::Async)
13             # - Timers, file I/O, etc.
14              
15 9     9   4988 use XS::JIT::Builder;
  9         11456  
  9         485  
16 9     9   3981 use Hypersonic::JIT::Util;
  9         20  
  9         348  
17              
18             # Future states
19             use constant {
20 9         1026 STATE_PENDING => 0,
21             STATE_DONE => 1,
22             STATE_FAILED => 2,
23             STATE_CANCELLED => 3,
24 9     9   50 };
  9         13  
25              
26             # Callback types (bitmask)
27             use constant {
28 9         492 CB_DONE => 1,
29             CB_FAIL => 2,
30             CB_CANCEL => 4,
31             CB_READY => 7, # CB_DONE | CB_FAIL | CB_CANCEL
32 9     9   39 };
  9         10  
33              
34             # Object slots (array-based for performance)
35             use constant {
36 9         488 SLOT_ID => 0,
37             SLOT_STATE => 1,
38             SLOT_RESULT => 2,
39             SLOT_FAILURE => 3,
40             SLOT_LABEL => 4,
41 9     9   38 };
  9         11  
42              
43             # Configuration
44             use constant {
45 9         470 MAX_FUTURES => 65536,
46             MAX_CALLBACKS => 16,
47             MAX_SUBFUTURES => 256,
48             THREADPOOL_SIZE => 8,
49             THREADPOOL_QUEUE_SIZE => 4096,
50 9     9   56 };
  9         18  
51              
52             # Export constants
53 9     9   43 use Exporter 'import';
  9         10  
  9         31860  
54             our @EXPORT_OK = qw(
55             STATE_PENDING STATE_DONE STATE_FAILED STATE_CANCELLED
56             CB_DONE CB_FAIL CB_CANCEL CB_READY
57             SLOT_ID SLOT_STATE SLOT_RESULT SLOT_FAILURE SLOT_LABEL
58             MAX_FUTURES MAX_CALLBACKS MAX_SUBFUTURES
59             );
60              
61             # JIT compilation state - our so submodules can check
62             our $COMPILED = 0;
63             my $MODULE_NAME;
64              
65             #############################################################################
66             # XS Function Registry
67             #############################################################################
68              
69             sub get_xs_functions {
70             return {
71             # Lifecycle - direct method binding (no Perl wrappers)
72 10     10 0 8762 'Hypersonic::Future::_new' => { source => 'xs_future_new', is_xs_native => 1 },
73             'Hypersonic::Future::_new_done' => { source => 'xs_future_new_done', is_xs_native => 1 },
74             'Hypersonic::Future::_new_fail' => { source => 'xs_future_new_fail', is_xs_native => 1 },
75             'Hypersonic::Future::done' => { source => 'xs_future_done', is_xs_native => 1 },
76             'Hypersonic::Future::fail' => { source => 'xs_future_fail', is_xs_native => 1 },
77             'Hypersonic::Future::cancel' => { source => 'xs_future_cancel', is_xs_native => 1 },
78              
79             # State inspection - direct XS, custom ops registered for compile-time optimization
80             'Hypersonic::Future::is_ready' => { source => 'xs_future_is_ready', is_xs_native => 1 },
81             'Hypersonic::Future::is_done' => { source => 'xs_future_is_done', is_xs_native => 1 },
82             'Hypersonic::Future::is_failed' => { source => 'xs_future_is_failed', is_xs_native => 1 },
83             'Hypersonic::Future::is_cancelled'=> { source => 'xs_future_is_cancelled', is_xs_native => 1 },
84              
85             # Result access - direct XS
86             'Hypersonic::Future::result' => { source => 'xs_future_result', is_xs_native => 1 },
87             'Hypersonic::Future::failure' => { source => 'xs_future_failure', is_xs_native => 1 },
88              
89             # Callbacks - direct XS
90             'Hypersonic::Future::on_ready' => { source => 'xs_future_on_ready', is_xs_native => 1 },
91             'Hypersonic::Future::on_done' => { source => 'xs_future_on_done', is_xs_native => 1 },
92             'Hypersonic::Future::on_fail' => { source => 'xs_future_on_fail', is_xs_native => 1 },
93             'Hypersonic::Future::on_cancel' => { source => 'xs_future_on_cancel', is_xs_native => 1 },
94              
95             # Sequencing - direct XS
96             'Hypersonic::Future::then' => { source => 'xs_future_then', is_xs_native => 1 },
97             'Hypersonic::Future::catch' => { source => 'xs_future_catch', is_xs_native => 1 },
98             'Hypersonic::Future::finally' => { source => 'xs_future_finally', is_xs_native => 1 },
99              
100             # Convergent - direct XS
101             'Hypersonic::Future::needs_all' => { source => 'xs_future_needs_all', is_xs_native => 1 },
102             'Hypersonic::Future::needs_any' => { source => 'xs_future_needs_any', is_xs_native => 1 },
103             'Hypersonic::Future::wait_all' => { source => 'xs_future_wait_all', is_xs_native => 1 },
104             'Hypersonic::Future::wait_any' => { source => 'xs_future_wait_any', is_xs_native => 1 },
105              
106             # Thread pool - direct XS
107             'Hypersonic::Future::submit' => { source => 'xs_future_submit', is_xs_native => 1 },
108             'Hypersonic::Future::poll' => { source => 'xs_future_poll', is_xs_native => 1 },
109              
110             # Notification - direct XS
111             'Hypersonic::Future::get_notify_fd' => { source => 'xs_future_get_notify_fd', is_xs_native => 1 },
112             'Hypersonic::Future::process_ready' => { source => 'xs_future_process_ready', is_xs_native => 1 },
113              
114             # Cleanup
115             'Hypersonic::Future::DESTROY' => { source => 'xs_future_destroy', is_xs_native => 1 },
116              
117             # Custom op registration
118             'Hypersonic::Future::_register_ops' => { source => 'xs_register_custom_ops', is_xs_native => 1 },
119             };
120             }
121              
122             #############################################################################
123             # C Code Generation - Main Entry Point
124             #############################################################################
125              
126             sub generate_c_code {
127 9     9 0 25 my ($class, $builder, $opts) = @_;
128              
129 9   50     27 $opts //= {};
130 9   50     41 my $max_futures = $opts->{max_futures} // MAX_FUTURES;
131 9   50     62 my $max_callbacks = $opts->{max_callbacks} // MAX_CALLBACKS;
132 9         61 my $inline = Hypersonic::JIT::Util->inline_keyword;
133              
134             # PERL_VERSION_* macros are already provided by XS::JIT preamble
135             # No need to emit them here
136              
137             # System includes via centralized utility
138 9         4054 Hypersonic::JIT::Util->add_standard_includes($builder,
139             qw(unistd fcntl threading eventfd));
140              
141             # Defines
142 9         241 $builder->line("#define MAX_FUTURES $max_futures")
143             ->line("#define MAX_CALLBACKS $max_callbacks")
144             ->blank
145             ->line('#define FUTURE_STATE_PENDING 0')
146             ->line('#define FUTURE_STATE_DONE 1')
147             ->line('#define FUTURE_STATE_FAILED 2')
148             ->line('#define FUTURE_STATE_CANCELLED 3')
149             ->blank
150             ->line('#define FUTURE_CB_DONE 1')
151             ->line('#define FUTURE_CB_FAIL 2')
152             ->line('#define FUTURE_CB_CANCEL 4')
153             ->line('#define FUTURE_CB_READY 7')
154             ->blank;
155              
156             # Callback struct
157 9         114 $builder->line('typedef struct FutureCallback {')
158             ->line(' int type;')
159             ->line(' SV *code;')
160             ->line(' int target_slot;')
161             ->line('} FutureCallback;')
162             ->blank;
163              
164             # FutureContext struct
165 9         232 $builder->line('typedef struct FutureContext {')
166             ->line(' int state;')
167             ->line(' int in_use;')
168             ->line(' int refcount;')
169             ->line(' SV **result_values;')
170             ->line(' int result_count;')
171             ->line(' char *fail_message;')
172             ->line(' char *fail_category;')
173             ->line(' FutureCallback callbacks[MAX_CALLBACKS];')
174             ->line(' int callback_count;')
175             ->line(' int cancel_target;')
176             ->line(' /* For convergent futures */')
177             ->line(' int *subfuture_slots;')
178             ->line(' int subfuture_count;')
179             ->line(' int subfutures_pending;')
180             ->line(' int convergent_mode; /* 0=none, 1=needs_all, 2=needs_any */')
181             ->line('} FutureContext;')
182             ->blank;
183              
184             # Registry
185 9         170 $builder->line('static FutureContext future_registry[MAX_FUTURES];')
186             ->line('static int future_freelist[MAX_FUTURES];')
187             ->line('static int future_freelist_count = 0;')
188             ->line('static int future_freelist_initialized = 0;')
189             ->blank;
190              
191             # Helper functions
192 9         136 $class->_gen_helpers($builder);
193              
194             # XS functions
195 9         63 $class->_gen_xs_functions($builder);
196              
197             # Custom ops for zero-overhead method calls
198 9         60 $class->_gen_custom_ops($builder);
199             }
200              
201             sub _gen_custom_ops {
202 9     9   26 my ($class, $builder) = @_;
203              
204             # Generate pp functions for custom ops
205             # These bypass all Perl method dispatch overhead
206              
207             # pp_future_is_done - direct state check
208 9         109 $builder->line('static OP* pp_future_is_done(pTHX) {')
209             ->line(' dSP;')
210             ->line(' SV* self = TOPs;')
211             ->line(' int slot = SvIV(SvRV(self));')
212             ->line(' SETs(boolSV(future_registry[slot].state == FUTURE_STATE_DONE));')
213             ->line(' return NORMAL;')
214             ->line('}')
215             ->blank;
216              
217             # pp_future_is_ready
218 9         118 $builder->line('static OP* pp_future_is_ready(pTHX) {')
219             ->line(' dSP;')
220             ->line(' SV* self = TOPs;')
221             ->line(' int slot = SvIV(SvRV(self));')
222             ->line(' SETs(boolSV(future_registry[slot].state != FUTURE_STATE_PENDING));')
223             ->line(' return NORMAL;')
224             ->line('}')
225             ->blank;
226              
227             # pp_future_is_failed
228 9         83 $builder->line('static OP* pp_future_is_failed(pTHX) {')
229             ->line(' dSP;')
230             ->line(' SV* self = TOPs;')
231             ->line(' int slot = SvIV(SvRV(self));')
232             ->line(' SETs(boolSV(future_registry[slot].state == FUTURE_STATE_FAILED));')
233             ->line(' return NORMAL;')
234             ->line('}')
235             ->blank;
236              
237             # pp_future_is_cancelled
238 9         86 $builder->line('static OP* pp_future_is_cancelled(pTHX) {')
239             ->line(' dSP;')
240             ->line(' SV* self = TOPs;')
241             ->line(' int slot = SvIV(SvRV(self));')
242             ->line(' SETs(boolSV(future_registry[slot].state == FUTURE_STATE_CANCELLED));')
243             ->line(' return NORMAL;')
244             ->line('}')
245             ->blank;
246              
247             # XOP declarations
248 9         176 $builder->xop_declare('future_is_done_xop', 'pp_future_is_done', 'future is_done')
249             ->xop_declare('future_is_ready_xop', 'pp_future_is_ready', 'future is_ready')
250             ->xop_declare('future_is_failed_xop', 'pp_future_is_failed', 'future is_failed')
251             ->xop_declare('future_is_cancelled_xop', 'pp_future_is_cancelled', 'future is_cancelled')
252             ->blank;
253              
254             # Call checkers
255 9         125 $builder->ck_start('ck_future_is_done')
256             ->ck_preamble
257             ->ck_build_unop('pp_future_is_done', '0')
258             ->ck_end;
259              
260 9         83 $builder->ck_start('ck_future_is_ready')
261             ->ck_preamble
262             ->ck_build_unop('pp_future_is_ready', '0')
263             ->ck_end;
264              
265 9         83 $builder->ck_start('ck_future_is_failed')
266             ->ck_preamble
267             ->ck_build_unop('pp_future_is_failed', '0')
268             ->ck_end;
269              
270 9         583 $builder->ck_start('ck_future_is_cancelled')
271             ->ck_preamble
272             ->ck_build_unop('pp_future_is_cancelled', '0')
273             ->ck_end;
274              
275             # Register checkers function
276             # cv_set_call_checker requires Perl 5.14+ - check at JIT time, not C compile time
277 9         67 $builder->xs_function('xs_register_custom_ops')
278             ->xs_preamble
279             ->line('register_xop_future_is_done_xop(aTHX);')
280             ->line('register_xop_future_is_ready_xop(aTHX);')
281             ->line('register_xop_future_is_failed_xop(aTHX);')
282             ->line('register_xop_future_is_cancelled_xop(aTHX);');
283              
284             # JIT optimization: only emit cv_set_call_checker code if Perl >= 5.14
285             # This eliminates dead code from the generated C file on older Perls
286 9 50       35 if ($] >= 5.014000) {
287 9         106 $builder->line('{')
288             ->line(' CV *custom_cv;')
289             ->line(' custom_cv = get_cv("Hypersonic::Future::is_done", 0);')
290             ->line(' if (custom_cv) cv_set_call_checker(custom_cv, S_ck_ck_future_is_done, &PL_sv_undef);')
291             ->line(' custom_cv = get_cv("Hypersonic::Future::is_ready", 0);')
292             ->line(' if (custom_cv) cv_set_call_checker(custom_cv, S_ck_ck_future_is_ready, &PL_sv_undef);')
293             ->line(' custom_cv = get_cv("Hypersonic::Future::is_failed", 0);')
294             ->line(' if (custom_cv) cv_set_call_checker(custom_cv, S_ck_ck_future_is_failed, &PL_sv_undef);')
295             ->line(' custom_cv = get_cv("Hypersonic::Future::is_cancelled", 0);')
296             ->line(' if (custom_cv) cv_set_call_checker(custom_cv, S_ck_ck_future_is_cancelled, &PL_sv_undef);')
297             ->line('}');
298             }
299              
300 9         87 $builder->xs_return('0')
301             ->xs_end;
302             }
303              
304             sub _gen_helpers {
305 9     9   28 my ($class, $builder) = @_;
306 9         47 my $inline = Hypersonic::JIT::Util->inline_keyword;
307              
308             # Forward declarations
309 9         42 $builder->line('static void future_convergent_check(int parent_slot, int child_slot, int mask);')
310             ->blank;
311              
312             # Freelist init - called once
313 9         185 $builder->line("static $inline void future_freelist_init(void) {")
314             ->line(' int i;')
315             ->line(' if (future_freelist_initialized) return;')
316             ->line(' for (i = MAX_FUTURES - 1; i >= 0; i--) {')
317             ->line(' future_freelist[future_freelist_count++] = i;')
318             ->line(' }')
319             ->line(' future_freelist_initialized = 1;')
320             ->line('}')
321             ->blank;
322              
323             # Alloc slot - hot path optimized
324 9         207 $builder->line("static $inline int future_alloc_slot(void) {")
325             ->line(' if (!future_freelist_initialized) future_freelist_init();')
326             ->line(' if (future_freelist_count > 0) {')
327             ->line(' int slot = future_freelist[--future_freelist_count];')
328             ->line(' FutureContext *ctx = &future_registry[slot];')
329             ->line(' /* Minimal init - only 5 fields needed for basic pending future */')
330             ->line(' ctx->in_use = 1;')
331             ->line(' ctx->state = FUTURE_STATE_PENDING;')
332             ->line(' ctx->refcount = 0;')
333             ->line(' ctx->callback_count = 0;')
334             ->line(' ctx->cancel_target = -1;')
335             ->line(' /* These are only accessed after checking state/count, lazy init */')
336             ->line(' /* result_values, fail_message, subfuture_slots set when used */')
337             ->line(' return slot;')
338             ->line(' }')
339             ->line(' return -1;')
340             ->line('}')
341             ->blank;
342              
343             # Free slot
344 9         304 $builder->line('static void future_free_slot(int slot) {')
345             ->line(' int i;')
346             ->line(' if (slot < 0 || slot >= MAX_FUTURES) return;')
347             ->line(' FutureContext *ctx = &future_registry[slot];')
348             ->line(' if (!ctx->in_use) return;')
349             ->line(' if (ctx->result_values) {')
350             ->line(' for (i = 0; i < ctx->result_count; i++) {')
351             ->line(' if (ctx->result_values[i]) SvREFCNT_dec(ctx->result_values[i]);')
352             ->line(' }')
353             ->line(' free(ctx->result_values);')
354             ->line(' ctx->result_values = NULL;')
355             ->line(' ctx->result_count = 0;')
356             ->line(' }')
357             ->line(' if (ctx->fail_message) { free(ctx->fail_message); ctx->fail_message = NULL; }')
358             ->line(' if (ctx->fail_category) { free(ctx->fail_category); ctx->fail_category = NULL; }')
359             ->line(' for (i = 0; i < ctx->callback_count; i++) {')
360             ->line(' if (ctx->callbacks[i].code) SvREFCNT_dec(ctx->callbacks[i].code);')
361             ->line(' }')
362             ->line(' if (ctx->subfuture_slots) { free(ctx->subfuture_slots); ctx->subfuture_slots = NULL; }')
363             ->line(' ctx->subfuture_count = 0;')
364             ->line(' ctx->subfutures_pending = 0;')
365             ->line(' ctx->convergent_mode = 0;')
366             ->line(' ctx->in_use = 0;')
367             ->line(' if (future_freelist_count < MAX_FUTURES) {')
368             ->line(' future_freelist[future_freelist_count++] = slot;')
369             ->line(' }')
370             ->line('}')
371             ->blank;
372              
373             # Invoke callbacks - NOT static so Pool.pm can access
374 9         1201 $builder->line('void future_invoke_callbacks(int slot, int mask) {')
375             ->line(' int i, j;')
376             ->line(' FutureContext *ctx = &future_registry[slot];')
377             ->line(' for (i = 0; i < ctx->callback_count; i++) {')
378             ->line(' FutureCallback *cb = &ctx->callbacks[i];')
379             ->line(' if (!(cb->type & mask)) continue;')
380             ->line(' if (cb->target_slot >= 0) {')
381             ->line(' FutureContext *target = &future_registry[cb->target_slot];')
382             ->line(' /* Decrement refcount since callback is firing */')
383             ->line(' target->refcount--;')
384             ->line(' if (target->in_use && target->state == FUTURE_STATE_PENDING) {')
385             ->line(' if (cb->code && cb->type == FUTURE_CB_READY) {')
386             ->line(' /* finally: call code, then propagate original state */')
387             ->line(' dSP;')
388             ->line(' ENTER; SAVETMPS;')
389             ->line(' PUSHMARK(SP);')
390             ->line(' PUTBACK;')
391             ->line(' call_sv(cb->code, G_DISCARD);')
392             ->line(' FREETMPS; LEAVE;')
393             ->line(' /* Propagate original state */')
394             ->line(' target->state = ctx->state;')
395             ->line(' if (ctx->state == FUTURE_STATE_DONE && ctx->result_count > 0) {')
396             ->line(' target->result_values = (SV **)malloc(ctx->result_count * sizeof(SV *));')
397             ->line(' for (j = 0; j < ctx->result_count; j++) {')
398             ->line(' target->result_values[j] = SvREFCNT_inc(ctx->result_values[j]);')
399             ->line(' }')
400             ->line(' target->result_count = ctx->result_count;')
401             ->line(' } else if (ctx->state == FUTURE_STATE_FAILED) {')
402             ->line(' if (ctx->fail_message) target->fail_message = strdup(ctx->fail_message);')
403             ->line(' if (ctx->fail_category) target->fail_category = strdup(ctx->fail_category);')
404             ->line(' }')
405             ->line(' future_invoke_callbacks(cb->target_slot, mask);')
406             ->line(' } else if (cb->code) {')
407             ->line(' /* then/catch: call transform callback, use result for target */')
408             ->line(' dSP;')
409             ->line(' ENTER; SAVETMPS;')
410             ->line(' PUSHMARK(SP);')
411             ->line(' if (mask & FUTURE_CB_DONE) {')
412             ->line(' for (j = 0; j < ctx->result_count; j++) XPUSHs(ctx->result_values[j]);')
413             ->line(' } else if (mask & FUTURE_CB_FAIL) {')
414             ->line(' if (ctx->fail_message) XPUSHs(sv_2mortal(newSVpv(ctx->fail_message, 0)));')
415             ->line(' if (ctx->fail_category) XPUSHs(sv_2mortal(newSVpv(ctx->fail_category, 0)));')
416             ->line(' }')
417             ->line(' PUTBACK;')
418             ->line(' int count = call_sv(cb->code, G_ARRAY);')
419             ->line(' SPAGAIN;')
420             ->line(' if (count > 0) {')
421             ->line(' target->result_values = (SV **)malloc(count * sizeof(SV *));')
422             ->line(' for (j = count - 1; j >= 0; j--) target->result_values[j] = SvREFCNT_inc(POPs);')
423             ->line(' target->result_count = count;')
424             ->line(' }')
425             ->line(' target->state = FUTURE_STATE_DONE;')
426             ->line(' PUTBACK;')
427             ->line(' FREETMPS; LEAVE;')
428             ->line(' future_invoke_callbacks(cb->target_slot, FUTURE_CB_DONE);')
429             ->line(' } else {')
430             ->line(' /* No code: check for convergent or propagate state directly */')
431             ->line(' if (target->convergent_mode > 0) {')
432             ->line(' /* This is a convergent future - use convergent check */')
433             ->line(' future_convergent_check(cb->target_slot, slot, mask);')
434             ->line(' } else if (mask & FUTURE_CB_DONE) {')
435             ->line(' target->state = FUTURE_STATE_DONE;')
436             ->line(' if (ctx->result_count > 0) {')
437             ->line(' target->result_values = (SV **)malloc(ctx->result_count * sizeof(SV *));')
438             ->line(' for (j = 0; j < ctx->result_count; j++) {')
439             ->line(' target->result_values[j] = SvREFCNT_inc(ctx->result_values[j]);')
440             ->line(' }')
441             ->line(' target->result_count = ctx->result_count;')
442             ->line(' }')
443             ->line(' future_invoke_callbacks(cb->target_slot, FUTURE_CB_DONE);')
444             ->line(' } else if (mask & FUTURE_CB_FAIL) {')
445             ->line(' target->state = FUTURE_STATE_FAILED;')
446             ->line(' if (ctx->fail_message) target->fail_message = strdup(ctx->fail_message);')
447             ->line(' if (ctx->fail_category) target->fail_category = strdup(ctx->fail_category);')
448             ->line(' future_invoke_callbacks(cb->target_slot, FUTURE_CB_FAIL);')
449             ->line(' } else if (mask & FUTURE_CB_CANCEL) {')
450             ->line(' target->state = FUTURE_STATE_CANCELLED;')
451             ->line(' future_invoke_callbacks(cb->target_slot, FUTURE_CB_CANCEL);')
452             ->line(' }')
453             ->line(' }')
454             ->line(' }')
455             ->line(' } else if (cb->code) {')
456             ->line(' /* No target: just call callback */')
457             ->line(' dSP;')
458             ->line(' ENTER; SAVETMPS;')
459             ->line(' PUSHMARK(SP);')
460             ->line(' if (mask & FUTURE_CB_DONE) {')
461             ->line(' for (j = 0; j < ctx->result_count; j++) {')
462             ->line(' XPUSHs(ctx->result_values[j]);')
463             ->line(' }')
464             ->line(' } else if (mask & FUTURE_CB_FAIL) {')
465             ->line(' if (ctx->fail_message) XPUSHs(sv_2mortal(newSVpv(ctx->fail_message, 0)));')
466             ->line(' if (ctx->fail_category) XPUSHs(sv_2mortal(newSVpv(ctx->fail_category, 0)));')
467             ->line(' }')
468             ->line(' PUTBACK;')
469             ->line(' call_sv(cb->code, G_DISCARD);')
470             ->line(' FREETMPS; LEAVE;')
471             ->line(' }')
472             ->line(' }')
473             ->line('}')
474             ->blank;
475              
476             # Convergent helper - called when a subfuture resolves
477 9         686 $builder->line('static void future_convergent_check(int parent_slot, int child_slot, int mask) {')
478             ->line(' int i;')
479             ->line(' FutureContext *parent = &future_registry[parent_slot];')
480             ->line(' FutureContext *child = &future_registry[child_slot];')
481             ->line(' if (parent->state != FUTURE_STATE_PENDING) return;')
482             ->line(' if (parent->convergent_mode == 1) {')
483             ->line(' /* needs_all: all must succeed, first fail fails all */')
484             ->line(' if (mask & FUTURE_CB_FAIL) {')
485             ->line(' parent->state = FUTURE_STATE_FAILED;')
486             ->line(' if (child->fail_message) parent->fail_message = strdup(child->fail_message);')
487             ->line(' if (child->fail_category) parent->fail_category = strdup(child->fail_category);')
488             ->line(' future_invoke_callbacks(parent_slot, FUTURE_CB_FAIL);')
489             ->line(' } else if (mask & FUTURE_CB_CANCEL) {')
490             ->line(' parent->state = FUTURE_STATE_CANCELLED;')
491             ->line(' future_invoke_callbacks(parent_slot, FUTURE_CB_CANCEL);')
492             ->line(' } else {')
493             ->line(' parent->subfutures_pending--;')
494             ->line(' if (parent->subfutures_pending == 0) {')
495             ->line(' /* All done - collect results as flat list */')
496             ->line(' int count = parent->subfuture_count;')
497             ->line(' parent->result_values = (SV**)malloc(count * sizeof(SV*));')
498             ->line(' parent->result_count = 0;')
499             ->line(' for (i = 0; i < count; i++) {')
500             ->line(' FutureContext *sub = &future_registry[parent->subfuture_slots[i]];')
501             ->line(' if (sub->result_count > 0) {')
502             ->line(' parent->result_values[parent->result_count++] = SvREFCNT_inc(sub->result_values[0]);')
503             ->line(' }')
504             ->line(' }')
505             ->line(' parent->state = FUTURE_STATE_DONE;')
506             ->line(' future_invoke_callbacks(parent_slot, FUTURE_CB_DONE);')
507             ->line(' }')
508             ->line(' }')
509             ->line(' } else if (parent->convergent_mode == 2) {')
510             ->line(' /* needs_any: first success wins, all fail to fail */')
511             ->line(' if (mask & FUTURE_CB_DONE) {')
512             ->line(' parent->state = FUTURE_STATE_DONE;')
513             ->line(' if (child->result_count > 0) {')
514             ->line(' parent->result_values = (SV**)malloc(child->result_count * sizeof(SV*));')
515             ->line(' for (i = 0; i < child->result_count; i++) {')
516             ->line(' parent->result_values[i] = SvREFCNT_inc(child->result_values[i]);')
517             ->line(' }')
518             ->line(' parent->result_count = child->result_count;')
519             ->line(' }')
520             ->line(' future_invoke_callbacks(parent_slot, FUTURE_CB_DONE);')
521             ->line(' } else {')
522             ->line(' parent->subfutures_pending--;')
523             ->line(' if (parent->subfutures_pending == 0) {')
524             ->line(' /* All failed - propagate last failure */')
525             ->line(' parent->state = FUTURE_STATE_FAILED;')
526             ->line(' if (child->fail_message) parent->fail_message = strdup(child->fail_message);')
527             ->line(' if (child->fail_category) parent->fail_category = strdup(child->fail_category);')
528             ->line(' future_invoke_callbacks(parent_slot, FUTURE_CB_FAIL);')
529             ->line(' }')
530             ->line(' }')
531             ->line(' }')
532             ->line('}')
533             ->blank;
534             }
535              
536             sub _gen_xs_functions {
537 9     9   40 my ($class, $builder) = @_;
538              
539             # xs_future_new
540 9         234 $builder->xs_function('xs_future_new')
541             ->xs_preamble
542             ->line('int slot = future_alloc_slot();')
543             ->line('if (slot < 0) croak("Future registry full");')
544             ->line('SV *slot_sv = newSViv(slot);')
545             ->line('SV *self_ref = newRV_noinc(slot_sv);')
546             ->line('sv_bless(self_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
547             ->line('future_registry[slot].refcount = 1;')
548             ->line('ST(0) = sv_2mortal(self_ref);')
549             ->xs_return('1')
550             ->xs_end;
551              
552             # xs_future_new_done - create already-done future in one call
553 9         231 $builder->xs_function('xs_future_new_done')
554             ->xs_preamble
555             ->line('int i;')
556             ->line('int slot = future_alloc_slot();')
557             ->line('if (slot < 0) croak("Future registry full");')
558             ->line('FutureContext *ctx = &future_registry[slot];')
559             ->line('int value_count = items - 1;')
560             ->line('if (value_count > 0) {')
561             ->line(' ctx->result_values = (SV **)malloc(value_count * sizeof(SV *));')
562             ->line(' for (i = 0; i < value_count; i++) {')
563             ->line(' ctx->result_values[i] = SvREFCNT_inc(ST(i + 1));')
564             ->line(' }')
565             ->line(' ctx->result_count = value_count;')
566             ->line('}')
567             ->line('ctx->state = FUTURE_STATE_DONE;')
568             ->line('SV *slot_sv = newSViv(slot);')
569             ->line('SV *self_ref = newRV_noinc(slot_sv);')
570             ->line('sv_bless(self_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
571             ->line('ctx->refcount = 1;')
572             ->line('ST(0) = sv_2mortal(self_ref);')
573             ->xs_return('1')
574             ->xs_end;
575              
576             # xs_future_new_fail - create already-failed future in one call
577 9         436 $builder->xs_function('xs_future_new_fail')
578             ->xs_preamble
579             ->line('if (items < 2) croak("Usage: Hypersonic::Future->new_fail($message, [$category])");')
580             ->line('int slot = future_alloc_slot();')
581             ->line('if (slot < 0) croak("Future registry full");')
582             ->line('FutureContext *ctx = &future_registry[slot];')
583             ->line('STRLEN msg_len;')
584             ->line('const char *msg = SvPV(ST(1), msg_len);')
585             ->line('ctx->fail_message = (char *)malloc(msg_len + 1);')
586             ->line('memcpy(ctx->fail_message, msg, msg_len);')
587             ->line('ctx->fail_message[msg_len] = \'\\0\';')
588             ->line('if (items > 2 && SvOK(ST(2))) {')
589             ->line(' STRLEN cat_len;')
590             ->line(' const char *cat = SvPV(ST(2), cat_len);')
591             ->line(' ctx->fail_category = (char *)malloc(cat_len + 1);')
592             ->line(' memcpy(ctx->fail_category, cat, cat_len);')
593             ->line(' ctx->fail_category[cat_len] = \'\\0\';')
594             ->line('}')
595             ->line('ctx->state = FUTURE_STATE_FAILED;')
596             ->line('SV *slot_sv = newSViv(slot);')
597             ->line('SV *self_ref = newRV_noinc(slot_sv);')
598             ->line('sv_bless(self_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
599             ->line('ctx->refcount = 1;')
600             ->line('ST(0) = sv_2mortal(self_ref);')
601             ->xs_return('1')
602             ->xs_end;
603              
604             # xs_future_done
605 9         358 $builder->xs_function('xs_future_done')
606             ->xs_preamble
607             ->line('int i;')
608             ->line('if (items < 1) croak("Usage: $future->done(@values)");')
609             ->line('int slot = SvIV(SvRV(ST(0)));')
610             ->line('FutureContext *ctx = &future_registry[slot];')
611             ->line('if (ctx->state != FUTURE_STATE_PENDING) croak("Future already resolved");')
612             ->line('int value_count = items - 1;')
613             ->line('if (value_count > 0) {')
614             ->line(' ctx->result_values = (SV **)malloc(value_count * sizeof(SV *));')
615             ->line(' for (i = 0; i < value_count; i++) {')
616             ->line(' ctx->result_values[i] = SvREFCNT_inc(ST(i + 1));')
617             ->line(' }')
618             ->line('}')
619             ->line('ctx->result_count = value_count;')
620             ->line('ctx->state = FUTURE_STATE_DONE;')
621             ->line('future_invoke_callbacks(slot, FUTURE_CB_DONE);')
622             ->line('ST(0) = ST(0);')
623             ->xs_return('1')
624             ->xs_end;
625              
626             # xs_future_fail
627 9         212 $builder->xs_function('xs_future_fail')
628             ->xs_preamble
629             ->line('if (items < 2) croak("Usage: $future->fail($message, [$category])");')
630             ->line('int slot = SvIV(SvRV(ST(0)));')
631             ->line('FutureContext *ctx = &future_registry[slot];')
632             ->line('if (ctx->state != FUTURE_STATE_PENDING) croak("Future already resolved");')
633             ->line('STRLEN msg_len;')
634             ->line('const char *msg = SvPV(ST(1), msg_len);')
635             ->line('ctx->fail_message = (char *)malloc(msg_len + 1);')
636             ->line('memcpy(ctx->fail_message, msg, msg_len);')
637             ->line('ctx->fail_message[msg_len] = \'\\0\';')
638             ->line('if (items > 2 && SvOK(ST(2))) {')
639             ->line(' STRLEN cat_len;')
640             ->line(' const char *cat = SvPV(ST(2), cat_len);')
641             ->line(' ctx->fail_category = (char *)malloc(cat_len + 1);')
642             ->line(' memcpy(ctx->fail_category, cat, cat_len);')
643             ->line(' ctx->fail_category[cat_len] = \'\\0\';')
644             ->line('}')
645             ->line('ctx->state = FUTURE_STATE_FAILED;')
646             ->line('future_invoke_callbacks(slot, FUTURE_CB_FAIL);')
647             ->line('ST(0) = ST(0);')
648             ->xs_return('1')
649             ->xs_end;
650              
651             # xs_future_cancel
652 9         153 $builder->xs_function('xs_future_cancel')
653             ->xs_preamble
654             ->line('int slot = SvIV(SvRV(ST(0)));')
655             ->line('FutureContext *ctx = &future_registry[slot];')
656             ->line('if (ctx->state != FUTURE_STATE_PENDING) { ST(0) = ST(0); XSRETURN(1); }')
657             ->line('ctx->state = FUTURE_STATE_CANCELLED;')
658             ->line('future_invoke_callbacks(slot, FUTURE_CB_CANCEL);')
659             ->line('ST(0) = ST(0);')
660             ->xs_return('1')
661             ->xs_end;
662              
663             # State checks
664 9         97 $builder->xs_function('xs_future_is_ready')
665             ->xs_preamble
666             ->line('int slot = SvIV(SvRV(ST(0)));')
667             ->line('ST(0) = boolSV(future_registry[slot].state != FUTURE_STATE_PENDING);')
668             ->xs_return('1')
669             ->xs_end;
670              
671 9         112 $builder->xs_function('xs_future_is_done')
672             ->xs_preamble
673             ->line('int slot = SvIV(SvRV(ST(0)));')
674             ->line('ST(0) = boolSV(future_registry[slot].state == FUTURE_STATE_DONE);')
675             ->xs_return('1')
676             ->xs_end;
677              
678 9         76 $builder->xs_function('xs_future_is_failed')
679             ->xs_preamble
680             ->line('int slot = SvIV(SvRV(ST(0)));')
681             ->line('ST(0) = boolSV(future_registry[slot].state == FUTURE_STATE_FAILED);')
682             ->xs_return('1')
683             ->xs_end;
684              
685 9         91 $builder->xs_function('xs_future_is_cancelled')
686             ->xs_preamble
687             ->line('int slot = SvIV(SvRV(ST(0)));')
688             ->line('ST(0) = boolSV(future_registry[slot].state == FUTURE_STATE_CANCELLED);')
689             ->xs_return('1')
690             ->xs_end;
691              
692             # Result access
693 9         163 $builder->xs_function('xs_future_result')
694             ->xs_preamble
695             ->line('int i;')
696             ->line('int slot = SvIV(SvRV(ST(0)));')
697             ->line('FutureContext *ctx = &future_registry[slot];')
698             ->line('if (ctx->state != FUTURE_STATE_DONE) croak("Future is not done");')
699             ->line('SP -= items;')
700             ->line('for (i = 0; i < ctx->result_count; i++) {')
701             ->line(' XPUSHs(ctx->result_values[i]);')
702             ->line('}')
703             ->line('PUTBACK;')
704             ->line('return;')
705             ->xs_end;
706              
707 9         136 $builder->xs_function('xs_future_failure')
708             ->xs_preamble
709             ->line('int slot = SvIV(SvRV(ST(0)));')
710             ->line('FutureContext *ctx = &future_registry[slot];')
711             ->line('if (ctx->state != FUTURE_STATE_FAILED) croak("Future is not failed");')
712             ->line('SP -= items;')
713             ->line('if (ctx->fail_message) XPUSHs(sv_2mortal(newSVpv(ctx->fail_message, 0)));')
714             ->line('else XPUSHs(&PL_sv_undef);')
715             ->line('if (ctx->fail_category) XPUSHs(sv_2mortal(newSVpv(ctx->fail_category, 0)));')
716             ->line('PUTBACK;')
717             ->line('return;')
718             ->xs_end;
719              
720             # Callbacks
721 9         546 $builder->xs_function('xs_future_on_ready')
722             ->xs_preamble
723             ->line('if (items != 2) croak("Usage: $future->on_ready($code)");')
724             ->line('int slot = SvIV(SvRV(ST(0)));')
725             ->line('FutureContext *ctx = &future_registry[slot];')
726             ->line('SV *code = ST(1);')
727             ->line('if (ctx->state != FUTURE_STATE_PENDING) {')
728             ->line(' dSP;')
729             ->line(' ENTER; SAVETMPS;')
730             ->line(' PUSHMARK(SP);')
731             ->line(' XPUSHs(ST(0));')
732             ->line(' PUTBACK;')
733             ->line(' call_sv(code, G_DISCARD);')
734             ->line(' FREETMPS; LEAVE;')
735             ->line('} else if (ctx->callback_count < MAX_CALLBACKS) {')
736             ->line(' FutureCallback *cb = &ctx->callbacks[ctx->callback_count++];')
737             ->line(' cb->type = FUTURE_CB_READY;')
738             ->line(' cb->code = SvREFCNT_inc(code);')
739             ->line(' cb->target_slot = -1;')
740             ->line('}')
741             ->line('ST(0) = ST(0);')
742             ->xs_return('1')
743             ->xs_end;
744              
745 9         226 $builder->xs_function('xs_future_on_done')
746             ->xs_preamble
747             ->line('int i;')
748             ->line('if (items != 2) croak("Usage: $future->on_done($code)");')
749             ->line('int slot = SvIV(SvRV(ST(0)));')
750             ->line('FutureContext *ctx = &future_registry[slot];')
751             ->line('SV *code = ST(1);')
752             ->line('if (ctx->state == FUTURE_STATE_DONE) {')
753             ->line(' dSP;')
754             ->line(' ENTER; SAVETMPS;')
755             ->line(' PUSHMARK(SP);')
756             ->line(' for (i = 0; i < ctx->result_count; i++) XPUSHs(ctx->result_values[i]);')
757             ->line(' PUTBACK;')
758             ->line(' call_sv(code, G_DISCARD);')
759             ->line(' FREETMPS; LEAVE;')
760             ->line('} else if (ctx->state == FUTURE_STATE_PENDING && ctx->callback_count < MAX_CALLBACKS) {')
761             ->line(' FutureCallback *cb = &ctx->callbacks[ctx->callback_count++];')
762             ->line(' cb->type = FUTURE_CB_DONE;')
763             ->line(' cb->code = SvREFCNT_inc(code);')
764             ->line(' cb->target_slot = -1;')
765             ->line('}')
766             ->line('ST(0) = ST(0);')
767             ->xs_return('1')
768             ->xs_end;
769              
770 9         283 $builder->xs_function('xs_future_on_fail')
771             ->xs_preamble
772             ->line('if (items != 2) croak("Usage: $future->on_fail($code)");')
773             ->line('int slot = SvIV(SvRV(ST(0)));')
774             ->line('FutureContext *ctx = &future_registry[slot];')
775             ->line('SV *code = ST(1);')
776             ->line('if (ctx->state == FUTURE_STATE_FAILED) {')
777             ->line(' dSP;')
778             ->line(' ENTER; SAVETMPS;')
779             ->line(' PUSHMARK(SP);')
780             ->line(' if (ctx->fail_message) XPUSHs(sv_2mortal(newSVpv(ctx->fail_message, 0)));')
781             ->line(' if (ctx->fail_category) XPUSHs(sv_2mortal(newSVpv(ctx->fail_category, 0)));')
782             ->line(' PUTBACK;')
783             ->line(' call_sv(code, G_DISCARD);')
784             ->line(' FREETMPS; LEAVE;')
785             ->line('} else if (ctx->state == FUTURE_STATE_PENDING && ctx->callback_count < MAX_CALLBACKS) {')
786             ->line(' FutureCallback *cb = &ctx->callbacks[ctx->callback_count++];')
787             ->line(' cb->type = FUTURE_CB_FAIL;')
788             ->line(' cb->code = SvREFCNT_inc(code);')
789             ->line(' cb->target_slot = -1;')
790             ->line('}')
791             ->line('ST(0) = ST(0);')
792             ->xs_return('1')
793             ->xs_end;
794              
795 9         203 $builder->xs_function('xs_future_on_cancel')
796             ->xs_preamble
797             ->line('if (items != 2) croak("Usage: $future->on_cancel($code)");')
798             ->line('int slot = SvIV(SvRV(ST(0)));')
799             ->line('FutureContext *ctx = &future_registry[slot];')
800             ->line('SV *code = ST(1);')
801             ->line('if (ctx->state == FUTURE_STATE_CANCELLED) {')
802             ->line(' dSP;')
803             ->line(' ENTER; SAVETMPS;')
804             ->line(' PUSHMARK(SP);')
805             ->line(' PUTBACK;')
806             ->line(' call_sv(code, G_DISCARD);')
807             ->line(' FREETMPS; LEAVE;')
808             ->line('} else if (ctx->state == FUTURE_STATE_PENDING && ctx->callback_count < MAX_CALLBACKS) {')
809             ->line(' FutureCallback *cb = &ctx->callbacks[ctx->callback_count++];')
810             ->line(' cb->type = FUTURE_CB_CANCEL;')
811             ->line(' cb->code = SvREFCNT_inc(code);')
812             ->line(' cb->target_slot = -1;')
813             ->line('}')
814             ->line('ST(0) = ST(0);')
815             ->xs_return('1')
816             ->xs_end;
817              
818             # Sequencing: then
819 9         755 $builder->xs_function('xs_future_then')
820             ->xs_preamble
821             ->line('int i;')
822             ->line('if (items != 2) croak("Usage: $future->then($code)");')
823             ->line('int slot = SvIV(SvRV(ST(0)));')
824             ->line('FutureContext *ctx = &future_registry[slot];')
825             ->line('SV *code = ST(1);')
826             ->line('int new_slot = future_alloc_slot();')
827             ->line('if (new_slot < 0) croak("Future registry full");')
828             ->line('FutureContext *new_ctx = &future_registry[new_slot];')
829             ->line('new_ctx->cancel_target = slot;')
830             ->line('/* Keep parent alive while child exists (for chaining) */')
831             ->line('ctx->refcount++;')
832             ->line('if (ctx->state == FUTURE_STATE_DONE) {')
833             ->line(' dSP;')
834             ->line(' ENTER; SAVETMPS;')
835             ->line(' PUSHMARK(SP);')
836             ->line(' for (i = 0; i < ctx->result_count; i++) XPUSHs(ctx->result_values[i]);')
837             ->line(' PUTBACK;')
838             ->line(' int count = call_sv(code, G_ARRAY);')
839             ->line(' SPAGAIN;')
840             ->line(' if (count > 0) {')
841             ->line(' new_ctx->result_values = (SV **)malloc(count * sizeof(SV *));')
842             ->line(' for (i = count - 1; i >= 0; i--) new_ctx->result_values[i] = SvREFCNT_inc(POPs);')
843             ->line(' new_ctx->result_count = count;')
844             ->line(' }')
845             ->line(' new_ctx->state = FUTURE_STATE_DONE;')
846             ->line(' PUTBACK;')
847             ->line(' FREETMPS; LEAVE;')
848             ->line('} else if (ctx->state == FUTURE_STATE_FAILED) {')
849             ->line(' new_ctx->state = FUTURE_STATE_FAILED;')
850             ->line(' if (ctx->fail_message) new_ctx->fail_message = strdup(ctx->fail_message);')
851             ->line(' if (ctx->fail_category) new_ctx->fail_category = strdup(ctx->fail_category);')
852             ->line('} else if (ctx->state == FUTURE_STATE_CANCELLED) {')
853             ->line(' new_ctx->state = FUTURE_STATE_CANCELLED;')
854             ->line('} else {')
855             ->line(' if (ctx->callback_count < MAX_CALLBACKS - 1) {')
856             ->line(' FutureCallback *cb = &ctx->callbacks[ctx->callback_count++];')
857             ->line(' cb->type = FUTURE_CB_DONE;')
858             ->line(' cb->code = SvREFCNT_inc(code);')
859             ->line(' cb->target_slot = new_slot;')
860             ->line(' FutureCallback *fail_cb = &ctx->callbacks[ctx->callback_count++];')
861             ->line(' fail_cb->type = FUTURE_CB_FAIL | FUTURE_CB_CANCEL;')
862             ->line(' fail_cb->code = NULL;')
863             ->line(' fail_cb->target_slot = new_slot;')
864             ->line(' /* Keep child alive until parent resolves */')
865             ->line(' new_ctx->refcount++;')
866             ->line(' }')
867             ->line('}')
868             ->line('SV *new_slot_sv = newSViv(new_slot);')
869             ->line('SV *new_ref = newRV_noinc(new_slot_sv);')
870             ->line('sv_bless(new_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
871             ->line('/* refcount already 1 from alloc_slot for Perl ref */')
872             ->line('ST(0) = sv_2mortal(new_ref);')
873             ->xs_return('1')
874             ->xs_end;
875              
876             # Sequencing: catch
877 9         692 $builder->xs_function('xs_future_catch')
878             ->xs_preamble
879             ->line('int i;')
880             ->line('if (items != 2) croak("Usage: $future->catch($code)");')
881             ->line('int slot = SvIV(SvRV(ST(0)));')
882             ->line('FutureContext *ctx = &future_registry[slot];')
883             ->line('SV *code = ST(1);')
884             ->line('int new_slot = future_alloc_slot();')
885             ->line('if (new_slot < 0) croak("Future registry full");')
886             ->line('FutureContext *new_ctx = &future_registry[new_slot];')
887             ->line('new_ctx->cancel_target = slot;')
888             ->line('/* Keep parent alive while child exists (for chaining) */')
889             ->line('ctx->refcount++;')
890             ->line('if (ctx->state == FUTURE_STATE_FAILED) {')
891             ->line(' dSP;')
892             ->line(' ENTER; SAVETMPS;')
893             ->line(' PUSHMARK(SP);')
894             ->line(' if (ctx->fail_message) XPUSHs(sv_2mortal(newSVpv(ctx->fail_message, 0)));')
895             ->line(' if (ctx->fail_category) XPUSHs(sv_2mortal(newSVpv(ctx->fail_category, 0)));')
896             ->line(' PUTBACK;')
897             ->line(' int count = call_sv(code, G_ARRAY);')
898             ->line(' SPAGAIN;')
899             ->line(' if (count > 0) {')
900             ->line(' new_ctx->result_values = (SV **)malloc(count * sizeof(SV *));')
901             ->line(' for (i = count - 1; i >= 0; i--) new_ctx->result_values[i] = SvREFCNT_inc(POPs);')
902             ->line(' new_ctx->result_count = count;')
903             ->line(' }')
904             ->line(' new_ctx->state = FUTURE_STATE_DONE;')
905             ->line(' PUTBACK;')
906             ->line(' FREETMPS; LEAVE;')
907             ->line('} else if (ctx->state == FUTURE_STATE_DONE) {')
908             ->line(' new_ctx->state = FUTURE_STATE_DONE;')
909             ->line(' if (ctx->result_count > 0) {')
910             ->line(' new_ctx->result_values = (SV **)malloc(ctx->result_count * sizeof(SV *));')
911             ->line(' for (i = 0; i < ctx->result_count; i++) new_ctx->result_values[i] = SvREFCNT_inc(ctx->result_values[i]);')
912             ->line(' new_ctx->result_count = ctx->result_count;')
913             ->line(' }')
914             ->line('} else if (ctx->state == FUTURE_STATE_CANCELLED) {')
915             ->line(' new_ctx->state = FUTURE_STATE_CANCELLED;')
916             ->line('} else {')
917             ->line(' if (ctx->callback_count < MAX_CALLBACKS - 1) {')
918             ->line(' FutureCallback *cb = &ctx->callbacks[ctx->callback_count++];')
919             ->line(' cb->type = FUTURE_CB_FAIL;')
920             ->line(' cb->code = SvREFCNT_inc(code);')
921             ->line(' cb->target_slot = new_slot;')
922             ->line(' FutureCallback *done_cb = &ctx->callbacks[ctx->callback_count++];')
923             ->line(' done_cb->type = FUTURE_CB_DONE | FUTURE_CB_CANCEL;')
924             ->line(' done_cb->code = NULL;')
925             ->line(' done_cb->target_slot = new_slot;')
926             ->line(' /* Keep child alive until parent resolves */')
927             ->line(' new_ctx->refcount++;')
928             ->line(' }')
929             ->line('}')
930             ->line('SV *new_slot_sv = newSViv(new_slot);')
931             ->line('SV *new_ref = newRV_noinc(new_slot_sv);')
932             ->line('sv_bless(new_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
933             ->line('/* refcount already 1 from alloc_slot for Perl ref */')
934             ->line('ST(0) = sv_2mortal(new_ref);')
935             ->xs_return('1')
936             ->xs_end;
937              
938             # Sequencing: finally
939 9         9942 $builder->xs_function('xs_future_finally')
940             ->xs_preamble
941             ->line('int i;')
942             ->line('if (items != 2) croak("Usage: $future->finally($code)");')
943             ->line('int slot = SvIV(SvRV(ST(0)));')
944             ->line('FutureContext *ctx = &future_registry[slot];')
945             ->line('SV *code = ST(1);')
946             ->line('int new_slot = future_alloc_slot();')
947             ->line('if (new_slot < 0) croak("Future registry full");')
948             ->line('FutureContext *new_ctx = &future_registry[new_slot];')
949             ->line('new_ctx->cancel_target = slot;')
950             ->line('/* Keep parent alive while child exists (for chaining) */')
951             ->line('ctx->refcount++;')
952             ->line('if (ctx->state != FUTURE_STATE_PENDING) {')
953             ->line(' dSP;')
954             ->line(' ENTER; SAVETMPS;')
955             ->line(' PUSHMARK(SP);')
956             ->line(' PUTBACK;')
957             ->line(' call_sv(code, G_DISCARD);')
958             ->line(' FREETMPS; LEAVE;')
959             ->line(' new_ctx->state = ctx->state;')
960             ->line(' if (ctx->state == FUTURE_STATE_DONE && ctx->result_count > 0) {')
961             ->line(' new_ctx->result_values = (SV **)malloc(ctx->result_count * sizeof(SV *));')
962             ->line(' for (i = 0; i < ctx->result_count; i++) new_ctx->result_values[i] = SvREFCNT_inc(ctx->result_values[i]);')
963             ->line(' new_ctx->result_count = ctx->result_count;')
964             ->line(' } else if (ctx->state == FUTURE_STATE_FAILED) {')
965             ->line(' if (ctx->fail_message) new_ctx->fail_message = strdup(ctx->fail_message);')
966             ->line(' if (ctx->fail_category) new_ctx->fail_category = strdup(ctx->fail_category);')
967             ->line(' }')
968             ->line('} else {')
969             ->line(' if (ctx->callback_count < MAX_CALLBACKS) {')
970             ->line(' FutureCallback *cb = &ctx->callbacks[ctx->callback_count++];')
971             ->line(' cb->type = FUTURE_CB_READY;')
972             ->line(' cb->code = SvREFCNT_inc(code);')
973             ->line(' cb->target_slot = new_slot;')
974             ->line(' /* Keep child alive until parent resolves */')
975             ->line(' new_ctx->refcount++;')
976             ->line(' }')
977             ->line('}')
978             ->line('SV *new_slot_sv = newSViv(new_slot);')
979             ->line('SV *new_ref = newRV_noinc(new_slot_sv);')
980             ->line('sv_bless(new_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
981             ->line('/* refcount already 1 from alloc_slot for Perl ref */')
982             ->line('ST(0) = sv_2mortal(new_ref);')
983             ->xs_return('1')
984             ->xs_end;
985              
986             # Convergent: needs_all - all must succeed
987 9         848 $builder->xs_function('xs_future_needs_all')
988             ->xs_preamble
989             ->line('int i;')
990             ->line('if (items < 2) croak("Usage: Hypersonic::Future->needs_all(@futures)");')
991             ->line('int new_slot = future_alloc_slot();')
992             ->line('if (new_slot < 0) croak("Future registry full");')
993             ->line('FutureContext *ctx = &future_registry[new_slot];')
994             ->line('int count = items - 1;')
995             ->line('ctx->subfuture_slots = (int*)malloc(count * sizeof(int));')
996             ->line('ctx->subfuture_count = count;')
997             ->line('ctx->subfutures_pending = count;')
998             ->line('ctx->convergent_mode = 1;')
999             ->line('int all_done = 1;')
1000             ->line('for (i = 0; i < count; i++) {')
1001             ->line(' SV *sub_sv = ST(i + 1);')
1002             ->line(' if (!sv_isobject(sub_sv)) croak("needs_all: argument %d is not a Future", i+1);')
1003             ->line(' int sub_slot = SvIV(SvRV(sub_sv));')
1004             ->line(' ctx->subfuture_slots[i] = sub_slot;')
1005             ->line(' FutureContext *sub = &future_registry[sub_slot];')
1006             ->line(' if (sub->state == FUTURE_STATE_PENDING) {')
1007             ->line(' all_done = 0;')
1008             ->line(' /* Register callback on subfuture */')
1009             ->line(' if (sub->callback_count < MAX_CALLBACKS) {')
1010             ->line(' FutureCallback *cb = &sub->callbacks[sub->callback_count++];')
1011             ->line(' cb->type = FUTURE_CB_READY;')
1012             ->line(' cb->code = NULL;')
1013             ->line(' cb->target_slot = new_slot;')
1014             ->line(' ctx->refcount++;')
1015             ->line(' }')
1016             ->line(' } else if (sub->state == FUTURE_STATE_FAILED) {')
1017             ->line(' ctx->state = FUTURE_STATE_FAILED;')
1018             ->line(' if (sub->fail_message) ctx->fail_message = strdup(sub->fail_message);')
1019             ->line(' if (sub->fail_category) ctx->fail_category = strdup(sub->fail_category);')
1020             ->line(' break;')
1021             ->line(' } else if (sub->state == FUTURE_STATE_CANCELLED) {')
1022             ->line(' ctx->state = FUTURE_STATE_CANCELLED;')
1023             ->line(' break;')
1024             ->line(' } else {')
1025             ->line(' ctx->subfutures_pending--;')
1026             ->line(' }')
1027             ->line('}')
1028             ->line('if (ctx->state == FUTURE_STATE_PENDING && all_done) {')
1029             ->line(' /* All already done - collect results as flat list */')
1030             ->line(' ctx->result_values = (SV**)malloc(count * sizeof(SV*));')
1031             ->line(' ctx->result_count = 0;')
1032             ->line(' for (i = 0; i < count; i++) {')
1033             ->line(' FutureContext *sub = &future_registry[ctx->subfuture_slots[i]];')
1034             ->line(' if (sub->result_count > 0) {')
1035             ->line(' ctx->result_values[ctx->result_count++] = SvREFCNT_inc(sub->result_values[0]);')
1036             ->line(' }')
1037             ->line(' }')
1038             ->line(' ctx->state = FUTURE_STATE_DONE;')
1039             ->line('}')
1040             ->line('SV *slot_sv = newSViv(new_slot);')
1041             ->line('SV *self_ref = newRV_noinc(slot_sv);')
1042             ->line('sv_bless(self_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
1043             ->line('/* refcount already 1 from alloc_slot for Perl ref */')
1044             ->line('ST(0) = sv_2mortal(self_ref);')
1045             ->xs_return('1')
1046             ->xs_end;
1047              
1048             # Convergent: needs_any - first success wins
1049 9         513 $builder->xs_function('xs_future_needs_any')
1050             ->xs_preamble
1051             ->line('int i, j;')
1052             ->line('if (items < 2) croak("Usage: Hypersonic::Future->needs_any(@futures)");')
1053             ->line('int new_slot = future_alloc_slot();')
1054             ->line('if (new_slot < 0) croak("Future registry full");')
1055             ->line('FutureContext *ctx = &future_registry[new_slot];')
1056             ->line('int count = items - 1;')
1057             ->line('ctx->subfuture_slots = (int*)malloc(count * sizeof(int));')
1058             ->line('ctx->subfuture_count = count;')
1059             ->line('ctx->subfutures_pending = count;')
1060             ->line('ctx->convergent_mode = 2;')
1061             ->line('for (i = 0; i < count; i++) {')
1062             ->line(' SV *sub_sv = ST(i + 1);')
1063             ->line(' if (!sv_isobject(sub_sv)) croak("needs_any: argument %d is not a Future", i+1);')
1064             ->line(' int sub_slot = SvIV(SvRV(sub_sv));')
1065             ->line(' ctx->subfuture_slots[i] = sub_slot;')
1066             ->line(' FutureContext *sub = &future_registry[sub_slot];')
1067             ->line(' if (sub->state == FUTURE_STATE_DONE) {')
1068             ->line(' /* First success - use it */')
1069             ->line(' if (sub->result_count > 0) {')
1070             ->line(' ctx->result_values = (SV**)malloc(sub->result_count * sizeof(SV*));')
1071             ->line(' for (j = 0; j < sub->result_count; j++) {')
1072             ->line(' ctx->result_values[j] = SvREFCNT_inc(sub->result_values[j]);')
1073             ->line(' }')
1074             ->line(' ctx->result_count = sub->result_count;')
1075             ->line(' }')
1076             ->line(' ctx->state = FUTURE_STATE_DONE;')
1077             ->line(' break;')
1078             ->line(' } else if (sub->state == FUTURE_STATE_PENDING) {')
1079             ->line(' /* Register callback on subfuture */')
1080             ->line(' if (sub->callback_count < MAX_CALLBACKS) {')
1081             ->line(' FutureCallback *cb = &sub->callbacks[sub->callback_count++];')
1082             ->line(' cb->type = FUTURE_CB_READY;')
1083             ->line(' cb->code = NULL;')
1084             ->line(' cb->target_slot = new_slot;')
1085             ->line(' ctx->refcount++;')
1086             ->line(' }')
1087             ->line(' } else {')
1088             ->line(' ctx->subfutures_pending--;')
1089             ->line(' }')
1090             ->line('}')
1091             ->line('if (ctx->state == FUTURE_STATE_PENDING && ctx->subfutures_pending == 0) {')
1092             ->line(' /* All failed - propagate last failure */')
1093             ->line(' FutureContext *last = &future_registry[ctx->subfuture_slots[count-1]];')
1094             ->line(' ctx->state = FUTURE_STATE_FAILED;')
1095             ->line(' if (last->fail_message) ctx->fail_message = strdup(last->fail_message);')
1096             ->line(' if (last->fail_category) ctx->fail_category = strdup(last->fail_category);')
1097             ->line('}')
1098             ->line('SV *slot_sv = newSViv(new_slot);')
1099             ->line('SV *self_ref = newRV_noinc(slot_sv);')
1100             ->line('sv_bless(self_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
1101             ->line('/* refcount already 1 from alloc_slot for Perl ref */')
1102             ->line('ST(0) = sv_2mortal(self_ref);')
1103             ->xs_return('1')
1104             ->xs_end;
1105              
1106             # wait_all and wait_any are aliases that also wait synchronously
1107             # For now, they just return the convergent future (async behavior)
1108 9         515 $builder->xs_function('xs_future_wait_all')
1109             ->xs_preamble
1110             ->line('int i;')
1111             ->line('/* Same as needs_all for now */')
1112             ->line('if (items < 2) croak("Usage: Hypersonic::Future->wait_all(@futures)");')
1113             ->line('int new_slot = future_alloc_slot();')
1114             ->line('if (new_slot < 0) croak("Future registry full");')
1115             ->line('FutureContext *ctx = &future_registry[new_slot];')
1116             ->line('int count = items - 1;')
1117             ->line('ctx->subfuture_slots = (int*)malloc(count * sizeof(int));')
1118             ->line('ctx->subfuture_count = count;')
1119             ->line('ctx->subfutures_pending = count;')
1120             ->line('ctx->convergent_mode = 1;')
1121             ->line('int all_done = 1;')
1122             ->line('for (i = 0; i < count; i++) {')
1123             ->line(' SV *sub_sv = ST(i + 1);')
1124             ->line(' if (!sv_isobject(sub_sv)) croak("wait_all: argument %d is not a Future", i+1);')
1125             ->line(' int sub_slot = SvIV(SvRV(sub_sv));')
1126             ->line(' ctx->subfuture_slots[i] = sub_slot;')
1127             ->line(' FutureContext *sub = &future_registry[sub_slot];')
1128             ->line(' if (sub->state == FUTURE_STATE_PENDING) {')
1129             ->line(' all_done = 0;')
1130             ->line(' if (sub->callback_count < MAX_CALLBACKS) {')
1131             ->line(' FutureCallback *cb = &sub->callbacks[sub->callback_count++];')
1132             ->line(' cb->type = FUTURE_CB_READY;')
1133             ->line(' cb->code = NULL;')
1134             ->line(' cb->target_slot = new_slot;')
1135             ->line(' ctx->refcount++;')
1136             ->line(' }')
1137             ->line(' } else if (sub->state != FUTURE_STATE_DONE) {')
1138             ->line(' ctx->state = sub->state;')
1139             ->line(' if (sub->fail_message) ctx->fail_message = strdup(sub->fail_message);')
1140             ->line(' if (sub->fail_category) ctx->fail_category = strdup(sub->fail_category);')
1141             ->line(' break;')
1142             ->line(' } else {')
1143             ->line(' ctx->subfutures_pending--;')
1144             ->line(' }')
1145             ->line('}')
1146             ->line('if (ctx->state == FUTURE_STATE_PENDING && all_done) {')
1147             ->line(' /* All already done - collect results as flat list */')
1148             ->line(' ctx->result_values = (SV**)malloc(count * sizeof(SV*));')
1149             ->line(' ctx->result_count = 0;')
1150             ->line(' for (i = 0; i < count; i++) {')
1151             ->line(' FutureContext *sub = &future_registry[ctx->subfuture_slots[i]];')
1152             ->line(' if (sub->result_count > 0) {')
1153             ->line(' ctx->result_values[ctx->result_count++] = SvREFCNT_inc(sub->result_values[0]);')
1154             ->line(' }')
1155             ->line(' }')
1156             ->line(' ctx->state = FUTURE_STATE_DONE;')
1157             ->line('}')
1158             ->line('SV *slot_sv = newSViv(new_slot);')
1159             ->line('SV *self_ref = newRV_noinc(slot_sv);')
1160             ->line('sv_bless(self_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
1161             ->line('/* refcount already 1 from alloc_slot for Perl ref */')
1162             ->line('ST(0) = sv_2mortal(self_ref);')
1163             ->xs_return('1')
1164             ->xs_end;
1165              
1166 9         551 $builder->xs_function('xs_future_wait_any')
1167             ->xs_preamble
1168             ->line('int i, j;')
1169             ->line('/* Same as needs_any for now */')
1170             ->line('if (items < 2) croak("Usage: Hypersonic::Future->wait_any(@futures)");')
1171             ->line('int new_slot = future_alloc_slot();')
1172             ->line('if (new_slot < 0) croak("Future registry full");')
1173             ->line('FutureContext *ctx = &future_registry[new_slot];')
1174             ->line('int count = items - 1;')
1175             ->line('ctx->subfuture_slots = (int*)malloc(count * sizeof(int));')
1176             ->line('ctx->subfuture_count = count;')
1177             ->line('ctx->subfutures_pending = count;')
1178             ->line('ctx->convergent_mode = 2;')
1179             ->line('for (i = 0; i < count; i++) {')
1180             ->line(' SV *sub_sv = ST(i + 1);')
1181             ->line(' if (!sv_isobject(sub_sv)) croak("wait_any: argument %d is not a Future", i+1);')
1182             ->line(' int sub_slot = SvIV(SvRV(sub_sv));')
1183             ->line(' ctx->subfuture_slots[i] = sub_slot;')
1184             ->line(' FutureContext *sub = &future_registry[sub_slot];')
1185             ->line(' if (sub->state == FUTURE_STATE_DONE) {')
1186             ->line(' if (sub->result_count > 0) {')
1187             ->line(' ctx->result_values = (SV**)malloc(sub->result_count * sizeof(SV*));')
1188             ->line(' for (j = 0; j < sub->result_count; j++) {')
1189             ->line(' ctx->result_values[j] = SvREFCNT_inc(sub->result_values[j]);')
1190             ->line(' }')
1191             ->line(' ctx->result_count = sub->result_count;')
1192             ->line(' }')
1193             ->line(' ctx->state = FUTURE_STATE_DONE;')
1194             ->line(' break;')
1195             ->line(' } else if (sub->state == FUTURE_STATE_PENDING) {')
1196             ->line(' if (sub->callback_count < MAX_CALLBACKS) {')
1197             ->line(' FutureCallback *cb = &sub->callbacks[sub->callback_count++];')
1198             ->line(' cb->type = FUTURE_CB_READY;')
1199             ->line(' cb->code = NULL;')
1200             ->line(' cb->target_slot = new_slot;')
1201             ->line(' ctx->refcount++;')
1202             ->line(' }')
1203             ->line(' } else {')
1204             ->line(' ctx->subfutures_pending--;')
1205             ->line(' }')
1206             ->line('}')
1207             ->line('if (ctx->state == FUTURE_STATE_PENDING && ctx->subfutures_pending == 0) {')
1208             ->line(' FutureContext *last = &future_registry[ctx->subfuture_slots[count-1]];')
1209             ->line(' ctx->state = FUTURE_STATE_FAILED;')
1210             ->line(' if (last->fail_message) ctx->fail_message = strdup(last->fail_message);')
1211             ->line(' if (last->fail_category) ctx->fail_category = strdup(last->fail_category);')
1212             ->line('}')
1213             ->line('SV *slot_sv = newSViv(new_slot);')
1214             ->line('SV *self_ref = newRV_noinc(slot_sv);')
1215             ->line('sv_bless(self_ref, gv_stashpv("Hypersonic::Future", GV_ADD));')
1216             ->line('/* refcount already 1 from alloc_slot for Perl ref */')
1217             ->line('ST(0) = sv_2mortal(self_ref);')
1218             ->xs_return('1')
1219             ->xs_end;
1220              
1221             # Placeholder thread pool functions
1222 9         65 $builder->xs_function('xs_future_submit')
1223             ->xs_preamble
1224             ->line('croak("Thread pool not yet implemented");')
1225             ->xs_return('0')
1226             ->xs_end;
1227              
1228 9         63 $builder->xs_function('xs_future_poll')
1229             ->xs_preamble
1230             ->line('croak("Thread pool not yet implemented");')
1231             ->xs_return('0')
1232             ->xs_end;
1233              
1234 9         62 $builder->xs_function('xs_future_get_notify_fd')
1235             ->xs_preamble
1236             ->line('ST(0) = sv_2mortal(newSViv(-1));')
1237             ->xs_return('1')
1238             ->xs_end;
1239              
1240 9         62 $builder->xs_function('xs_future_process_ready')
1241             ->xs_preamble
1242             ->line('ST(0) = sv_2mortal(newSViv(0));')
1243             ->xs_return('1')
1244             ->xs_end;
1245              
1246             # DESTROY
1247 9         382 $builder->xs_function('xs_future_destroy')
1248             ->xs_preamble
1249             ->line('int i;')
1250             ->line('int slot = SvIV(SvRV(ST(0)));')
1251             ->line('if (slot >= 0 && slot < MAX_FUTURES) {')
1252             ->line(' FutureContext *ctx = &future_registry[slot];')
1253             ->line(' if (ctx->in_use) {')
1254             ->line(' ctx->refcount--;')
1255             ->line(' if (ctx->refcount <= 0) {')
1256             ->line(' /* Decrement refcounts of targets in unfired callbacks */')
1257             ->line(' for (i = 0; i < ctx->callback_count; i++) {')
1258             ->line(' FutureCallback *cb = &ctx->callbacks[i];')
1259             ->line(' if (cb->target_slot >= 0 && cb->target_slot < MAX_FUTURES) {')
1260             ->line(' FutureContext *target = &future_registry[cb->target_slot];')
1261             ->line(' if (target->in_use) {')
1262             ->line(' target->refcount--;')
1263             ->line(' if (target->refcount <= 0) future_free_slot(cb->target_slot);')
1264             ->line(' }')
1265             ->line(' }')
1266             ->line(' }')
1267             ->line(' /* Decrement parent refcount if we had a cancel_target (chained child) */')
1268             ->line(' if (ctx->cancel_target >= 0 && ctx->cancel_target < MAX_FUTURES) {')
1269             ->line(' FutureContext *parent = &future_registry[ctx->cancel_target];')
1270             ->line(' if (parent->in_use) {')
1271             ->line(' parent->refcount--;')
1272             ->line(' if (parent->refcount <= 0) future_free_slot(ctx->cancel_target);')
1273             ->line(' }')
1274             ->line(' }')
1275             ->line(' future_free_slot(slot);')
1276             ->line(' }')
1277             ->line(' }')
1278             ->line('}')
1279             ->xs_return('0')
1280             ->xs_end;
1281             }
1282              
1283              
1284             #############################################################################
1285             # Compilation
1286             #############################################################################
1287              
1288             sub compile {
1289 9     9 0 1525430 my ($class, %opts) = @_;
1290              
1291 9 50       40 return 1 if $COMPILED;
1292              
1293 9         4117 require XS::JIT;
1294              
1295 9   50     8547 my $cache_dir = $opts{cache_dir} // '_hypersonic_cache/future';
1296 9         89 $MODULE_NAME = 'Hypersonic::Future::XS_' . $$;
1297              
1298 9         114 my $builder = XS::JIT::Builder->new;
1299              
1300             # Generate Future code
1301 9         69 $class->generate_c_code($builder, \%opts);
1302              
1303             # Collect functions from Future
1304 9         32 my %functions = %{ $class->get_xs_functions };
  9         86  
1305              
1306             # Include Pool - Future compilation always includes Pool
1307 9         3415 require Hypersonic::Future::Pool;
1308 9         99 Hypersonic::Future::Pool->generate_c_code($builder, \%opts);
1309 9         66 %functions = (%functions, %{ Hypersonic::Future::Pool->get_xs_functions });
  9         43  
1310 9         52 $Hypersonic::Future::Pool::COMPILED = 1;
1311              
1312 9         687 my $code = $builder->code;
1313              
1314             # Pool emits pthread_create/_mutex_*/_cond_* calls. Linux's libc
1315             # provides these via libc symbols (glibc) so the link often succeeds
1316             # without -lpthread, but on FreeBSD/OpenBSD and on Linux with musl
1317             # they live in libpthread and the JIT-compiled .so fails dlopen
1318             # with "Undefined symbol 'pthread_create'". Add the flags
1319             # unconditionally - they're no-ops on platforms that don't need them.
1320 9         18174221 XS::JIT->compile(
1321             code => $code,
1322             name => $MODULE_NAME,
1323             cache_dir => $cache_dir,
1324             functions => \%functions,
1325             extra_cflags => '-pthread',
1326             extra_ldflags => '-lpthread',
1327             );
1328              
1329             # Register custom ops to replace method calls at compile time
1330 9         471 $class->_register_ops();
1331 9         57 Hypersonic::Future::Pool->_register_ops();
1332              
1333             # Install direct XS function aliases - eliminates Perl wrapper overhead
1334 9     9   1691 no warnings 'redefine';
  9         11  
  9         1666  
1335 9         66 *new = \&_new;
1336 9         35 *new_done = \&_new_done;
1337 9         32 *new_fail = \&_new_fail;
1338              
1339 9         23 $COMPILED = 1;
1340 9         811 return 1;
1341             }
1342              
1343             #############################################################################
1344             # Minimal Perl API - triggers compilation on first use
1345             # After compile(), these subs are replaced with direct XS bindings
1346             #############################################################################
1347              
1348             sub new {
1349 0     0 0   my $class = shift;
1350 0           $class->compile;
1351             # compile() replaces *new with *_new, so call it again
1352 0           return $class->new(@_);
1353             }
1354              
1355             sub new_done {
1356 0     0 0   my $class = shift;
1357 0           $class->compile;
1358 0           return $class->new_done(@_);
1359             }
1360              
1361             sub new_fail {
1362 0     0 0   my $class = shift;
1363 0           $class->compile;
1364 0           return $class->new_fail(@_);
1365             }
1366              
1367             1;
1368              
1369             __END__