@shd101wyy/yo 0.1.19 → 0.1.21

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.
package/out/esm/index.mjs CHANGED
@@ -149,7 +149,7 @@ Given: "${S(t.type)}"`)}}}return{expectedEnv:e.env,givenEnv:t.env}}function vl(e
149
149
  `)}function gc(e){let t={moduleDoc:null,declarations:[]},n=[],r=null,i="",o=0;for(;o<e.length;){let a=e[o];if(ag(a))if(r===null&&(r=a.position,i=a.modulePath),a.type==="inner_doc_line_comment"){for(n.push($l(a.value)),o++;o<e.length&&e[o].type==="whitespace";){if(e[o].value.includes(`
150
150
  `)){let s=o+1;for(;s<e.length&&e[s].type==="whitespace";)s++;if(s<e.length&&e[s].type==="inner_doc_line_comment"){o=s;break}else break}o++}continue}else{n.push(mc(a.value)),o++;continue}if(a.type==="doc_line_comment"){let s=[],l=a.position,u=a.modulePath;for(s.push($l(a.value)),o++;o<e.length;){let p=o;for(;p<e.length&&e[p].type==="whitespace";)p++;if(p<e.length&&e[p].type==="doc_line_comment")s.push($l(e[p].value)),o=p+1;else break}let c={content:s.join(`
151
151
  `),inner:!1,position:l,modulePath:u},{name:_,position:f}=yc(e,o);t.declarations.push({comment:c,declarationName:_,declarationPosition:f});continue}if(a.type==="doc_block_comment"){let s={content:mc(a.value),inner:!1,position:a.position,modulePath:a.modulePath};o++;let{name:l,position:u}=yc(e,o);t.declarations.push({comment:s,declarationName:l,declarationPosition:u});continue}o++}return n.length>0&&r!==null&&(t.moduleDoc={content:n.join(`
152
- `),inner:!0,position:r,modulePath:i}),t}function yc(e,t){let n=t;for(;n<e.length;){let r=e[n];if(sg(r)||og(r)){n++;continue}if(r.type==="identifier")return{name:r.value,position:r.position};if(r.type==="("){n++;continue}return{name:"",position:null}}return{name:"",position:null}}function gs(e){return E(e,["->","=>"])?!!(e.$?.isAnonymousFunctionDefinition===!0||F(e)&&F(e.func)&&(E(e.func,V.fn)||E(e.func,V.unsafe_fn)||E(e.func,V.Fn))||!e.$):!1}function hs(e,t){if(!F(e))return!1;if(t(e.func))return!0;for(let n of e.args)if(F(n)&&E(n,"=>")){for(let r of n.args)if(t(r))return!0}else if(t(n))return!0;return!1}function cr(e){if(B(e))return ht(e,V.escape);if(F(e)){if(e.$?.macroExpansion)return cr(e.$.macroExpansion);if(E(e,V.cond)||E(e,V.match))return hs(e,cr);if(gs(e)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&W(e.func.$.value.value)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&Zt(e.func.$.value.value))return!1;if(cr(e.func))return!0;for(let t of e.args)if(cr(t))return!0}return!1}function uo(e){if(B(e))return ht(e,V.return)||ht(e,V.escape);if(F(e)){if(E(e,V.return)||E(e,V.escape))return!0;if(e.$?.macroExpansion)return uo(e.$.macroExpansion);if(E(e,V.cond)||E(e,V.match))return hs(e,uo);if(gs(e)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&W(e.func.$.value.value)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&Zt(e.func.$.value.value))return!1;if(uo(e.func))return!0;for(let t of e.args)if(uo(t))return!0}return!1}function pn(e){if(F(e)){if(e.func.$?.type?.ioBuiltin==="io_await")return!0;if(e.$?.macroExpansion)return pn(e.$.macroExpansion);if(E(e,V.cond)||E(e,V.match))return hs(e,pn);if(e.func.$?.type?.ioBuiltin==="io_async"||gs(e)||O(e.func.$?.value)&&W(e.func.$.value.value)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&Zt(e.func.$.value.value))return!1;if(pn(e.func))return!0;for(let t of e.args)if(pn(t))return!0}return!1}function co(e){if(B(e))return ht(e,V.break)||ht(e,V.return)||ht(e,V.escape);if(F(e)){if(E(e,V.return)||E(e,V.escape))return!0;if(e.$?.macroExpansion)return co(e.$.macroExpansion);if(E(e,V.cond)||E(e,V.match))return hs(e,co);if(gs(e)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&W(e.func.$.value.value)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&Zt(e.func.$.value.value))return!1;if(co(e.func))return!0;for(let t of e.args)if(co(t))return!0}return!1}var Q={tag:"Unit",type:Yn()};function Xo(e){if(e&&e.tag==="FnCall"&&E(e,":=")){let t=e.args[0];if(t&&t.tag==="Atom"&&t.token.type==="identifier"&&t.$){let n=t.token.value,r=K(t.$.env,n);if(r.length>0)return r[r.length-1].id}}}function vs(e,t){let n=[],r=new Map,i=new Map,o=new Map;if(Lr(e,n,r,i,o,t),e.$?.deferredDropExpressions)for(let a of e.$.deferredDropExpressions)Lr(a,n,r,i,o,t);return n.length===0&&r.clear(),{suspensionPoints:n,capturedVariables:Array.from(r.values()),hasSuspensions:n.length>0,variableIdRemapping:o}}function Lr(e,t,n,r,i,o,a){switch(e.tag){case"Atom":if(e.$&&e.token.type==="identifier"){let s=e.token.value,l=e.$.type,u=K(e.$.env,s);if(u.length>0){let c=u[u.length-1];if(c&&!n.has(c.id)&&!c.isCompileTimeOnly){let _=`${c.name}:${c.frameLevel}`,f=r.get(_);if(f&&f!==c.id)i.set(c.id,f);else if(c.isOwningTheSameRcValueAs){let p=c.isOwningTheSameRcValueAs;if(!n.has(p.id)){let d={id:p.id,name:p.name,type:p.type,isOwningTheSameRcValueAs:void 0};n.set(p.id,d);let h=`${p.name}:${p.frameLevel}`;r.has(h)||r.set(h,p.id)}}else n.set(c.id,{id:c.id,name:s,type:l,isOwningTheSameRcValueAs:void 0}),r.has(_)||r.set(_,c.id)}}}break;case"FnCall":{if(E(e,V.while)){let s=t.length;Lr(e.func,t,n,r,i,o,e);for(let u of e.args)Lr(u,t,n,r,i,o,e);let l=t.length;if(l>s)for(let u=s;u<l;u++)t[u].isInsideWhile=!0,t[u].whileNestingDepth=(t[u].whileNestingDepth??0)+1,t[u].enclosingWhileExpr||(t[u].enclosingWhileExpr=e);break}if(E(e,V.cond)){hc(e,t,n,r,i,o);break}if(E(e,V.match)){hc(e,t,n,r,i,o);break}if(o.detect(e,a,t),o.shouldSkipBody(e)){if(e.$?.deferredDupExpressions)for(let s of e.$.deferredDupExpressions)Lr(s,t,n,r,i,o,e);break}Lr(e.func,t,n,r,i,o,e);for(let s of e.args)Lr(s,t,n,r,i,o,e);if(e.$?.deferredDropExpressions)for(let s of e.$.deferredDropExpressions)Lr(s,t,n,r,i,o,e);break}}}function hc(e,t,n,r,i,o){if(e.tag!=="FnCall")return;let a=t.length;Lr(e.func,t,n,r,i,o,e);let s=new Map(r),l=[];for(let c of e.args){let _=t.length;Lr(c,t,n,r,i,o,e),l.push(t.slice(_))}r.clear();for(let[c,_]of s)r.set(c,_);let u=Math.max(...l.map(c=>c.length),0);if(u>0){t.splice(a);let c=a;for(let _=0;_<u;_++){let f;for(let p of l)if(_<p.length){f=p[_];break}f&&(f.index=t.length,f.isInsideCond=!0,_===0&&(f.needsOwnCondBranchField=!0),_>0&&(f.condBranchSourceIndex=c),t.push(f))}}}function vc(e){let n=vs(e,{detect(i,o,a){if(i.tag==="FnCall"&&En(i)){let s=i.args[0];if(!s)return;let l=s.$?.type;if(l&&Se(l)){let u=Rt(l);if(!u)return;let c=u.isFuture.outputType,_;if(s.tag==="Atom"&&s.token.type==="identifier"&&s.$){let p=s.token.value,d=K(s.$.env,p);if(d.length>0){let h=d[d.length-1];h.isOwningTheSameRcValueAs?_=h.isOwningTheSameRcValueAs.id:_=h.id}}let f=Xo(o);a.push({index:a.length,expr:i,resultType:c,futureType:u,targetVariableId:f,futureVariableId:_})}}},shouldSkipBody(i){return zt(i)}}),r=n.capturedVariables.map(i=>({id:i.id,name:i.name,type:i.type,kind:"local",isOwningTheSameRcValueAs:void 0}));return{awaitPoints:n.suspensionPoints,capturedVariables:r,hasAwaits:n.hasSuspensions,variableIdRemapping:n.variableIdRemapping}}function zt(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="io_async"}function En(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="io_await"}function Tc(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="io_state"}function Ts(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="io_spawn"}function Ec(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="join_handle_await"}function Ke(e){return B(e)&&e.token.type==="identifier"||e.token.type==="operator"}function Qo(e,t,n){if(!e.$?.variableName)return;let r=K(t,e.$.variableName);if(!r.length)return;let i=r[r.length-1],o=new Set;for(;i&&i.isOwningTheSameRcValueAs;){if(o.has(i.id))return;o.add(i.id),i=i.isOwningTheSameRcValueAs}if(i&&i.isOwningTheRcValue)return i}function ai(e){let t=e.$?.env;if(!t)return;let n;if(e.$?.pathCollection)for(let r of e.$.pathCollection)r.length>0&&typeof r[0]=="string"&&(n=r[0]);else B(e)&&(n=e.token.value);if(n){let r=K(t,n),i=r[r.length-1];if(i?.isImplicit)throw m({token:e.token,errorMessage:`Cannot use implicit variable "${i.name}" in assignment. Implicit variables must be passed via using() parameters.`})}}var Es=!0;function Cl({variablesToDrop:e,env:t,context:n}){let r=[],i=t;for(let o of e){let a=Qe(`${k.___drop[0]}(${o.name})`),s=x({expr:a,env:i,context:{...n,expectedType:{env:i,type:Q.type}}});r.push(s),s.$&&s.$.env&&(i=s.$.env)}return{deferredDropExpressions:r.length>0?r:void 0,env:i}}function Zo(e,t,n){if(F(e)&&zt(e))return;if(e.$?.deferredDupExpressions)for(let s of e.$.deferredDupExpressions)Zo(s,t,n);if(F(e)&&F(e.func)&&E(e.func,".",2)&&B(e.func.args[0])&&B(e.func.args[1])&&e.func.args[1].token.value===k.___dup[0]&&e.args.length===0&&e.$?.env){let s=e.func.args[0].token.value,l=K(e.$.env,s);if(l.length>0){let u=l[l.length-1];t.has(u.id)||t.set(u.id,[]),t.get(u.id).push(e)}return}if(F(e)&&E(e,V.while))return;function i(s){if(rt(s.$?.controlFlow))return!0;if(F(s)&&E(s,V.begin)){let l=s.args[s.args.length-1];if(rt(l?.$?.controlFlow)||F(l)&&E(l,V.return))return!0}return!!(F(s)&&E(s,V.return))}function o(s){if(F(s)&&E(s,V.tuple,0))return!0;if(F(s)&&E(s,V.begin)){if(s.args.length===0)return!0;if(s.args.length===1){let l=s.args[0];if(F(l)&&E(l,V.tuple,0))return!0}}return!1}function a(s,l){let u=[],c=[],_=[];for(let p=l;p<s.args.length;p++){let d=s.args[p];if(F(d)&&E(d,"=>",2)){let h=d.args[1],g=$c(h);u.push(g),c.push(i(h)),_.push(o(h))}}let f=new Set;for(let p of u){for(let d of p.dupCalls.keys())f.add(d);for(let d of p.varsWithPartialBranchDups)n.add(d)}if(u.length>0)for(let p of f){let d=[],h=[];for(let g=0;g<u.length;g++){let y=u[g],v=y.dupCalls.has(p),T=c[g];if(v){let $=y.dupCalls.get(p);T?d.push(...$):h.push(...$)}}for(let g of d)t.has(p)||t.set(p,[]),g.__isEarlyReturnDup=!0,t.get(p).push(g);if(h.length>0){let g=0,y=0;for(let v=0;v<u.length;v++)c[v]||(g++,u[v].dupCalls.has(p)&&y++);if(y===g){t.has(p)||t.set(p,[]);for(let v of h)t.get(p).push(v)}else n.add(p)}}}if(F(e)&&E(e,V.cond)){a(e,0);return}if(F(e)&&E(e,V.match)){e.args[0]&&Zo(e.args[0],t,n),a(e,1);return}if(F(e)){Zo(e.func,t,n);for(let s of e.args)Zo(s,t,n)}}function $c(e){let t=new Map,n=new Set;return Zo(e,t,n),{dupCalls:t,varsWithPartialBranchDups:n}}function $s(e){return F(e)&&E(e,V.tuple,0)}function bl(e,t){if(e.$?.deferredDupExpressions&&(e.$.deferredDupExpressions=e.$.deferredDupExpressions.filter(n=>!t.has(n)),e.$.deferredDupExpressions.length===0&&(e.$.deferredDupExpressions=void 0)),F(e)){bl(e.func,t);for(let n of e.args)bl(n,t)}}function lg(e,t){let n=new Set,r=e.args;for(let i=0;i<r.length;i++){let o=r[i];if(!F(o)||!E(o,V.while))continue;let a=o.args[o.args.length-1];if(!(!a||!F(a)||!E(a,V.begin)))for(let s of a.args){if(!F(s)||!E(s,V.match))continue;let l=s.args[0];if(!l||!B(l))continue;let u=l.token.value,c,_,f;for(let $=1;$<s.args.length;$++){let b=s.args[$];if(!F(b)||!E(b,"=>",2))continue;let L=b.args[1];if(!(!F(L)||!E(L,V.begin))){for(let C of L.args)if(F(C)&&E(C,"=",2)){let I=C.args[0];if(B(I)&&I.token.value===u){c=C.args[1],_=C,f=L;break}}if(c)break}}if(!c||!_||!f||!c.$?.deferredDupExpressions||c.$.deferredDupExpressions.length===0)continue;let p;for(let $=0;$<i;$++){let b=r[$];if(F(b)&&E(b,"=",2)){let L=b.args[0];if(B(L)&&L.token.value===u){p=b.args[1];break}if(F(L)&&L.args.length>0&&B(L.args[0])&&L.args[0].token.value===u){p=b.args[1];break}}}if(!p)continue;let d=p.$?.variableName;if(!d||!p.$?.env)continue;let h=K(p.$.env,d);if(h.length===0||h[h.length-1].isOwningTheRcValue||!p.$?.deferredDupExpressions||p.$.deferredDupExpressions.length===0)continue;let y=!1;for(let $=i+1;$<r.length;$++)if(kl(r[$],u)){y=!0;break}if(y)continue;p.$.deferredDupExpressions=void 0,c.$.deferredDupExpressions=void 0;let v=_.$?.variableName;v&&f.$?.deferredDropExpressions&&(f.$.deferredDropExpressions=f.$.deferredDropExpressions.filter($=>ug($)!==v),f.$.deferredDropExpressions.length===0&&(f.$.deferredDropExpressions=void 0)),_.$&&(_.$.variableName=void 0);let T=K(t,u);if(T.length>0){let $=T[T.length-1];t=qe(t,$,{...$,consumedAtToken:$.token})}n.add(u)}}return{optimizedVarNames:n,env:t}}function ug(e){if(F(e)&&e.args.length===0&&F(e.func)&&E(e.func,".",2)&&B(e.func.args[1])&&e.func.args[1].token.value===k.___drop[0]&&B(e.func.args[0]))return e.func.args[0].token.value;if(F(e)&&E(e,k.___drop)&&e.args.length>=1&&B(e.args[0]))return e.args[0].token.value}function kl(e,t){if(B(e)&&e.token.value===t)return!0;if(F(e)){if(kl(e.func,t))return!0;for(let n of e.args)if(kl(n,t))return!0}return!1}function vt({expr:e,env:t,context:n,variablesToAdd:r=[],isEvaluatingFunctionBodyBeginBlock:i=!1}){if(F(e)&&E(e,"_")&&!E(e,V.begin)){let L=e;L.args.some(I=>F(I)&&E(I,":"))||(L.func={...L.func,token:{...L.func.token,value:V.begin[0]}})}if(!F(e)||!E(e,V.begin)){let L={tag:"FnCall",func:{tag:"Atom",token:{...e.token,value:V.begin[0]}},args:[Me(e)],token:{...e.token,value:V.begin[0]}};wr(e,L),e=e}let o=e.args,a=n.expectedType;if(o.length===0)return e.$={env:t,type:Q.type,value:Q,pathCollection:[]},e;t=Ve(t,void 0,!0);for(let L=0;L<r.length;L++){let C=r[L],{env:I}=fe({env:t,variable:C});t=I}let s=o[o.length-1],l,u=!1;for(let L=0;L<o.length;L++){let C=o[L];if(B(C)&&ht(C,V.return)||F(C)&&E(C,V.return)){if(L!==o.length-1&&!(L===o.length-2&&$s(o[o.length-1])))throw m({token:C.token,errorMessage:'The "return" keyword can only be used as the last expression.'});if(F(C)&&he(C,V.return,1),!n.isEvaluatingFunctionBodyOrAsyncBlock)throw m({token:C.token,errorMessage:'The "return" keyword can only be used inside a function body or async block.'});if(l=C,B(C)){C.$={env:t,type:Q.type,value:Q,pathCollection:[],controlFlow:ii("return")},s=C;break}else{he(C,V.return,1);let I=C.args[0],A=x({expr:I,env:t,context:{...n,expectedType:n.isEvaluatingFunctionBodyOrAsyncBlock.kind==="function-body"?{type:n.isEvaluatingFunctionBodyOrAsyncBlock.type.return.type,env:t}:n.expectedType}});if(!A.$)throw m({token:I.token,errorMessage:`Return expression is not evaluated correctly:
152
+ `),inner:!0,position:r,modulePath:i}),t}function yc(e,t){let n=t;for(;n<e.length;){let r=e[n];if(sg(r)||og(r)){n++;continue}if(r.type==="identifier")return{name:r.value,position:r.position};if(r.type==="("){n++;continue}return{name:"",position:null}}return{name:"",position:null}}function gs(e){return E(e,["->","=>"])?!!(e.$?.isAnonymousFunctionDefinition===!0||e.$?.value!==void 0&&ne(e.$.value)||F(e)&&F(e.func)&&(E(e.func,V.fn)||E(e.func,V.unsafe_fn)||E(e.func,V.Fn))||!e.$):!1}function hs(e,t){if(!F(e))return!1;if(t(e.func))return!0;for(let n of e.args)if(F(n)&&E(n,"=>")){for(let r of n.args)if(t(r))return!0}else if(t(n))return!0;return!1}function cr(e){if(B(e))return ht(e,V.escape);if(F(e)){if(e.$?.macroExpansion)return cr(e.$.macroExpansion);if(E(e,V.cond)||E(e,V.match))return hs(e,cr);if(gs(e)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&W(e.func.$.value.value)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&Zt(e.func.$.value.value))return!1;if(cr(e.func))return!0;for(let t of e.args)if(cr(t))return!0}return!1}function uo(e){if(B(e))return ht(e,V.return)||ht(e,V.escape);if(F(e)){if(E(e,V.return)||E(e,V.escape))return!0;if(e.$?.macroExpansion)return uo(e.$.macroExpansion);if(E(e,V.cond)||E(e,V.match))return hs(e,uo);if(gs(e)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&W(e.func.$.value.value)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&Zt(e.func.$.value.value))return!1;if(uo(e.func))return!0;for(let t of e.args)if(uo(t))return!0}return!1}function pn(e){if(F(e)){if(e.func.$?.type?.ioBuiltin==="io_await")return!0;if(e.$?.macroExpansion)return pn(e.$.macroExpansion);if(E(e,V.cond)||E(e,V.match))return hs(e,pn);if(e.func.$?.type?.ioBuiltin==="io_async"||gs(e)||O(e.func.$?.value)&&W(e.func.$.value.value)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&Zt(e.func.$.value.value))return!1;if(pn(e.func))return!0;for(let t of e.args)if(pn(t))return!0}return!1}function co(e){if(B(e))return ht(e,V.break)||ht(e,V.return)||ht(e,V.escape);if(F(e)){if(E(e,V.return)||E(e,V.escape))return!0;if(e.$?.macroExpansion)return co(e.$.macroExpansion);if(E(e,V.cond)||E(e,V.match))return hs(e,co);if(gs(e)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&W(e.func.$.value.value)||F(e.func)&&e.func.$?.value!==void 0&&O(e.func.$.value)&&Zt(e.func.$.value.value))return!1;if(co(e.func))return!0;for(let t of e.args)if(co(t))return!0}return!1}var Q={tag:"Unit",type:Yn()};function Xo(e){if(e&&e.tag==="FnCall"&&E(e,":=")){let t=e.args[0];if(t&&t.tag==="Atom"&&t.token.type==="identifier"&&t.$){let n=t.token.value,r=K(t.$.env,n);if(r.length>0)return r[r.length-1].id}}}function vs(e,t){let n=[],r=new Map,i=new Map,o=new Map;if(Lr(e,n,r,i,o,t),e.$?.deferredDropExpressions)for(let a of e.$.deferredDropExpressions)Lr(a,n,r,i,o,t);return n.length===0&&r.clear(),{suspensionPoints:n,capturedVariables:Array.from(r.values()),hasSuspensions:n.length>0,variableIdRemapping:o}}function Lr(e,t,n,r,i,o,a){switch(e.tag){case"Atom":if(e.$&&e.token.type==="identifier"){let s=e.token.value,l=e.$.type,u=K(e.$.env,s);if(u.length>0){let c=u[u.length-1];if(c&&!n.has(c.id)&&!c.isCompileTimeOnly){let _=`${c.name}:${c.frameLevel}`,f=r.get(_);if(f&&f!==c.id)i.set(c.id,f);else if(c.isOwningTheSameRcValueAs){let p=c.isOwningTheSameRcValueAs;if(!n.has(p.id)){let d={id:p.id,name:p.name,type:p.type,isOwningTheSameRcValueAs:void 0};n.set(p.id,d);let h=`${p.name}:${p.frameLevel}`;r.has(h)||r.set(h,p.id)}}else n.set(c.id,{id:c.id,name:s,type:l,isOwningTheSameRcValueAs:void 0}),r.has(_)||r.set(_,c.id)}}}break;case"FnCall":{if(E(e,V.while)){let s=t.length;Lr(e.func,t,n,r,i,o,e);for(let u of e.args)Lr(u,t,n,r,i,o,e);let l=t.length;if(l>s)for(let u=s;u<l;u++)t[u].isInsideWhile=!0,t[u].whileNestingDepth=(t[u].whileNestingDepth??0)+1,t[u].enclosingWhileExpr||(t[u].enclosingWhileExpr=e);break}if(E(e,V.cond)){hc(e,t,n,r,i,o);break}if(E(e,V.match)){hc(e,t,n,r,i,o);break}if(o.detect(e,a,t),o.shouldSkipBody(e)){if(e.$?.deferredDupExpressions)for(let s of e.$.deferredDupExpressions)Lr(s,t,n,r,i,o,e);break}Lr(e.func,t,n,r,i,o,e);for(let s of e.args)Lr(s,t,n,r,i,o,e);if(e.$?.deferredDropExpressions)for(let s of e.$.deferredDropExpressions)Lr(s,t,n,r,i,o,e);break}}}function hc(e,t,n,r,i,o){if(e.tag!=="FnCall")return;let a=t.length;Lr(e.func,t,n,r,i,o,e);let s=new Map(r),l=[];for(let c of e.args){let _=t.length;Lr(c,t,n,r,i,o,e),l.push(t.slice(_))}r.clear();for(let[c,_]of s)r.set(c,_);let u=Math.max(...l.map(c=>c.length),0);if(u>0){t.splice(a);let c=a;for(let _=0;_<u;_++){let f;for(let p of l)if(_<p.length){f=p[_];break}f&&(f.index=t.length,f.isInsideCond=!0,_===0&&(f.needsOwnCondBranchField=!0),_>0&&(f.condBranchSourceIndex=c),t.push(f))}}}function vc(e){let n=vs(e,{detect(i,o,a){if(i.tag==="FnCall"&&En(i)){let s=i.args[0];if(!s)return;let l=s.$?.type;if(l&&Se(l)){let u=Rt(l);if(!u)return;let c=u.isFuture.outputType,_;if(s.tag==="Atom"&&s.token.type==="identifier"&&s.$){let p=s.token.value,d=K(s.$.env,p);if(d.length>0){let h=d[d.length-1];h.isOwningTheSameRcValueAs?_=h.isOwningTheSameRcValueAs.id:_=h.id}}let f=Xo(o);a.push({index:a.length,expr:i,resultType:c,futureType:u,targetVariableId:f,futureVariableId:_})}}},shouldSkipBody(i){return zt(i)}}),r=n.capturedVariables.map(i=>({id:i.id,name:i.name,type:i.type,kind:"local",isOwningTheSameRcValueAs:void 0}));return{awaitPoints:n.suspensionPoints,capturedVariables:r,hasAwaits:n.hasSuspensions,variableIdRemapping:n.variableIdRemapping}}function zt(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="io_async"}function En(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="io_await"}function Tc(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="io_state"}function Ts(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="io_spawn"}function Ec(e){return e.tag!=="FnCall"?!1:e.func.$?.type?.ioBuiltin==="join_handle_await"}function Ke(e){return B(e)&&e.token.type==="identifier"||e.token.type==="operator"}function Qo(e,t,n){if(!e.$?.variableName)return;let r=K(t,e.$.variableName);if(!r.length)return;let i=r[r.length-1],o=new Set;for(;i&&i.isOwningTheSameRcValueAs;){if(o.has(i.id))return;o.add(i.id),i=i.isOwningTheSameRcValueAs}if(i&&i.isOwningTheRcValue)return i}function ai(e){let t=e.$?.env;if(!t)return;let n;if(e.$?.pathCollection)for(let r of e.$.pathCollection)r.length>0&&typeof r[0]=="string"&&(n=r[0]);else B(e)&&(n=e.token.value);if(n){let r=K(t,n),i=r[r.length-1];if(i?.isImplicit)throw m({token:e.token,errorMessage:`Cannot use implicit variable "${i.name}" in assignment. Implicit variables must be passed via using() parameters.`})}}var Es=!0;function Cl({variablesToDrop:e,env:t,context:n}){let r=[],i=t;for(let o of e){let a=Qe(`${k.___drop[0]}(${o.name})`),s=x({expr:a,env:i,context:{...n,expectedType:{env:i,type:Q.type}}});r.push(s),s.$&&s.$.env&&(i=s.$.env)}return{deferredDropExpressions:r.length>0?r:void 0,env:i}}function Zo(e,t,n){if(F(e)&&zt(e))return;if(e.$?.deferredDupExpressions)for(let s of e.$.deferredDupExpressions)Zo(s,t,n);if(F(e)&&F(e.func)&&E(e.func,".",2)&&B(e.func.args[0])&&B(e.func.args[1])&&e.func.args[1].token.value===k.___dup[0]&&e.args.length===0&&e.$?.env){let s=e.func.args[0].token.value,l=K(e.$.env,s);if(l.length>0){let u=l[l.length-1];t.has(u.id)||t.set(u.id,[]),t.get(u.id).push(e)}return}if(F(e)&&E(e,V.while))return;function i(s){if(rt(s.$?.controlFlow))return!0;if(F(s)&&E(s,V.begin)){let l=s.args[s.args.length-1];if(rt(l?.$?.controlFlow)||F(l)&&E(l,V.return))return!0}return!!(F(s)&&E(s,V.return))}function o(s){if(F(s)&&E(s,V.tuple,0))return!0;if(F(s)&&E(s,V.begin)){if(s.args.length===0)return!0;if(s.args.length===1){let l=s.args[0];if(F(l)&&E(l,V.tuple,0))return!0}}return!1}function a(s,l){let u=[],c=[],_=[];for(let p=l;p<s.args.length;p++){let d=s.args[p];if(F(d)&&E(d,"=>",2)){let h=d.args[1],g=$c(h);u.push(g),c.push(i(h)),_.push(o(h))}}let f=new Set;for(let p of u){for(let d of p.dupCalls.keys())f.add(d);for(let d of p.varsWithPartialBranchDups)n.add(d)}if(u.length>0)for(let p of f){let d=[],h=[];for(let g=0;g<u.length;g++){let y=u[g],v=y.dupCalls.has(p),T=c[g];if(v){let $=y.dupCalls.get(p);T?d.push(...$):h.push(...$)}}for(let g of d)t.has(p)||t.set(p,[]),g.__isEarlyReturnDup=!0,t.get(p).push(g);if(h.length>0){let g=0,y=0;for(let v=0;v<u.length;v++)c[v]||(g++,u[v].dupCalls.has(p)&&y++);if(y===g){t.has(p)||t.set(p,[]);for(let v of h)t.get(p).push(v)}else n.add(p)}}}if(F(e)&&E(e,V.cond)){a(e,0);return}if(F(e)&&E(e,V.match)){e.args[0]&&Zo(e.args[0],t,n),a(e,1);return}if(F(e)){Zo(e.func,t,n);for(let s of e.args)Zo(s,t,n)}}function $c(e){let t=new Map,n=new Set;return Zo(e,t,n),{dupCalls:t,varsWithPartialBranchDups:n}}function $s(e){return F(e)&&E(e,V.tuple,0)}function bl(e,t){if(e.$?.deferredDupExpressions&&(e.$.deferredDupExpressions=e.$.deferredDupExpressions.filter(n=>!t.has(n)),e.$.deferredDupExpressions.length===0&&(e.$.deferredDupExpressions=void 0)),F(e)){bl(e.func,t);for(let n of e.args)bl(n,t)}}function lg(e,t){let n=new Set,r=e.args;for(let i=0;i<r.length;i++){let o=r[i];if(!F(o)||!E(o,V.while))continue;let a=o.args[o.args.length-1];if(!(!a||!F(a)||!E(a,V.begin)))for(let s of a.args){if(!F(s)||!E(s,V.match))continue;let l=s.args[0];if(!l||!B(l))continue;let u=l.token.value,c,_,f;for(let $=1;$<s.args.length;$++){let b=s.args[$];if(!F(b)||!E(b,"=>",2))continue;let L=b.args[1];if(!(!F(L)||!E(L,V.begin))){for(let C of L.args)if(F(C)&&E(C,"=",2)){let I=C.args[0];if(B(I)&&I.token.value===u){c=C.args[1],_=C,f=L;break}}if(c)break}}if(!c||!_||!f||!c.$?.deferredDupExpressions||c.$.deferredDupExpressions.length===0)continue;let p;for(let $=0;$<i;$++){let b=r[$];if(F(b)&&E(b,"=",2)){let L=b.args[0];if(B(L)&&L.token.value===u){p=b.args[1];break}if(F(L)&&L.args.length>0&&B(L.args[0])&&L.args[0].token.value===u){p=b.args[1];break}}}if(!p)continue;let d=p.$?.variableName;if(!d||!p.$?.env)continue;let h=K(p.$.env,d);if(h.length===0||h[h.length-1].isOwningTheRcValue||!p.$?.deferredDupExpressions||p.$.deferredDupExpressions.length===0)continue;let y=!1;for(let $=i+1;$<r.length;$++)if(kl(r[$],u)){y=!0;break}if(y)continue;p.$.deferredDupExpressions=void 0,c.$.deferredDupExpressions=void 0;let v=_.$?.variableName;v&&f.$?.deferredDropExpressions&&(f.$.deferredDropExpressions=f.$.deferredDropExpressions.filter($=>ug($)!==v),f.$.deferredDropExpressions.length===0&&(f.$.deferredDropExpressions=void 0)),_.$&&(_.$.variableName=void 0);let T=K(t,u);if(T.length>0){let $=T[T.length-1];t=qe(t,$,{...$,consumedAtToken:$.token})}n.add(u)}}return{optimizedVarNames:n,env:t}}function ug(e){if(F(e)&&e.args.length===0&&F(e.func)&&E(e.func,".",2)&&B(e.func.args[1])&&e.func.args[1].token.value===k.___drop[0]&&B(e.func.args[0]))return e.func.args[0].token.value;if(F(e)&&E(e,k.___drop)&&e.args.length>=1&&B(e.args[0]))return e.args[0].token.value}function kl(e,t){if(B(e)&&e.token.value===t)return!0;if(F(e)){if(kl(e.func,t))return!0;for(let n of e.args)if(kl(n,t))return!0}return!1}function vt({expr:e,env:t,context:n,variablesToAdd:r=[],isEvaluatingFunctionBodyBeginBlock:i=!1}){if(F(e)&&E(e,"_")&&!E(e,V.begin)){let L=e;L.args.some(I=>F(I)&&E(I,":"))||(L.func={...L.func,token:{...L.func.token,value:V.begin[0]}})}if(!F(e)||!E(e,V.begin)){let L={tag:"FnCall",func:{tag:"Atom",token:{...e.token,value:V.begin[0]}},args:[Me(e)],token:{...e.token,value:V.begin[0]}};wr(e,L),e=e}let o=e.args,a=n.expectedType;if(o.length===0)return e.$={env:t,type:Q.type,value:Q,pathCollection:[]},e;t=Ve(t,void 0,!0);for(let L=0;L<r.length;L++){let C=r[L],{env:I}=fe({env:t,variable:C});t=I}let s=o[o.length-1],l,u=!1;for(let L=0;L<o.length;L++){let C=o[L];if(B(C)&&ht(C,V.return)||F(C)&&E(C,V.return)){if(L!==o.length-1&&!(L===o.length-2&&$s(o[o.length-1])))throw m({token:C.token,errorMessage:'The "return" keyword can only be used as the last expression.'});if(F(C)&&he(C,V.return,1),!n.isEvaluatingFunctionBodyOrAsyncBlock)throw m({token:C.token,errorMessage:'The "return" keyword can only be used inside a function body or async block.'});if(l=C,B(C)){C.$={env:t,type:Q.type,value:Q,pathCollection:[],controlFlow:ii("return")},s=C;break}else{he(C,V.return,1);let I=C.args[0],A=x({expr:I,env:t,context:{...n,expectedType:n.isEvaluatingFunctionBodyOrAsyncBlock.kind==="function-body"?{type:n.isEvaluatingFunctionBodyOrAsyncBlock.type.return.type,env:t}:n.expectedType}});if(!A.$)throw m({token:I.token,errorMessage:`Return expression is not evaluated correctly:
153
153
  ${w(I)}`});if(n.isEvaluatingFunctionBodyOrAsyncBlock?.kind==="function-body"&&D(n.isEvaluatingFunctionBodyOrAsyncBlock.type.return.type)&&n.functionReturnImplConcreteType){let z=A.$.type;if(n.functionReturnImplConcreteType.length>0){let P=n.functionReturnImplConcreteType[0];if(!X({type:P.concreteType,env:P.env},{type:z,env:t}))throw ct([{token:C.token,errorMessage:`All return statements must return the same concrete type for Impl(...).
154
154
  Impl(...) uses static dispatch and requires the same concrete type across all returns.
155
155
  Consider using Dyn(...) for dynamic dispatch if different concrete types are needed.`},{token:P.token,errorMessage:`First return has concrete type: ${S(P.concreteType)}`},{token:C.token,errorMessage:`Conflicting return has concrete type: ${S(z)}`}])}else n.functionReturnImplConcreteType.push({concreteType:z,env:t,token:C.token})}He(A,!0),t=A.$.env,C.$={env:t,type:A.$.type,value:A.$.value,pathCollection:A.$.pathCollection,variableName:A.$.variableName,controlFlow:ii("return")},s=C;break}}else if(B(C)&&ht(C,V.break)){if(L!==o.length-1&&!(L===o.length-2&&$s(o[o.length-1])))throw m({token:C.token,errorMessage:'The "break" keyword can only be used as the last expression.'});if(!n.isEvaluatingLoopBody)throw m({token:C.token,errorMessage:'The "break" keyword can only be used inside a loop.'});C.$={env:t,type:Q.type,value:Q,pathCollection:[],controlFlow:ii("break")},s=C;break}else if(B(C)&&ht(C,V.continue)){if(L!==o.length-1&&!(L===o.length-2&&$s(o[o.length-1])))throw m({token:C.token,errorMessage:'The "continue" keyword can only be used as the last expression.'});if(!n.isEvaluatingLoopBody)throw m({token:C.token,errorMessage:'The "continue" keyword can only be used inside a loop.'});C.$={env:t,type:Q.type,value:Q,pathCollection:[],controlFlow:ii("continue")},s=C;break}else if(F(C)&&E(C,V.escape)){if(L!==o.length-1&&!(L===o.length-2&&$s(o[o.length-1])))throw m({token:C.token,errorMessage:'The "escape" keyword can only be used as the last expression.'});if(!n.enclosingFunctionReturnType)throw m({token:C.token,errorMessage:'The "escape" keyword can only be used inside a function that has an enclosing function.'});l=C,he(C,V.escape,1);let I=C.args[0],A=x({expr:I,env:t,context:{...n,expectedType:{type:n.enclosingFunctionReturnType,env:t}}});if(!A.$)throw m({token:I.token,errorMessage:`Escape expression is not evaluated correctly:
@@ -228,7 +228,11 @@ typedef struct {
228
228
  } __yo_async_task_queue_t;
229
229
 
230
230
  // Thread-local async runtime state
231
- ${Bt(t)?"static __declspec(thread) __yo_async_task_queue_t __yo_thread_async_queue = {NULL, NULL, 0};":"static __thread __yo_async_task_queue_t __yo_thread_async_queue = {NULL, NULL, 0};"}
231
+ ${Bt(t)?`static __declspec(thread) __yo_async_task_queue_t __yo_thread_async_queue = {NULL, NULL, 0};
232
+ // Free-list of reusable continuation nodes to avoid per-completion malloc/free.
233
+ static __declspec(thread) __yo_continuation_t* __yo_cont_free_list = NULL;`:`static __thread __yo_async_task_queue_t __yo_thread_async_queue = {NULL, NULL, 0};
234
+ // Free-list of reusable continuation nodes to avoid per-completion malloc/free.
235
+ static __thread __yo_continuation_t* __yo_cont_free_list = NULL;`}
232
236
 
233
237
  // Async scheduler initialized flag (per-thread \u2014 each thread has its own event loop)
234
238
  static ${r} bool __yo_async_scheduler_initialized = false;
@@ -260,12 +264,18 @@ static void __yo_async_scheduler_init(void) {
260
264
  // Use __yo_async_spawn_task for spawning tasks with proper lifetime management.
261
265
  static void __yo_async_enqueue_continuation(void (*resume_fn)(void*), void* state_machine) {
262
266
  ASYNC_DEBUG("[ASYNC] Enqueueing continuation: resume_fn=%p, sm=%p\\n", (void*)resume_fn, state_machine);
263
-
264
- __yo_continuation_t* cont = (__yo_continuation_t*)__yo_malloc(sizeof(__yo_continuation_t));
267
+
268
+ __yo_continuation_t* cont;
269
+ if (__yo_cont_free_list) {
270
+ cont = __yo_cont_free_list;
271
+ __yo_cont_free_list = cont->next;
272
+ } else {
273
+ cont = (__yo_continuation_t*)__yo_malloc(sizeof(__yo_continuation_t));
274
+ }
265
275
  cont->resume_fn = resume_fn;
266
276
  cont->state_machine = state_machine;
267
277
  cont->next = NULL;
268
-
278
+
269
279
  if (__yo_thread_async_queue.tail) {
270
280
  __yo_thread_async_queue.tail->next = cont;
271
281
  __yo_thread_async_queue.tail = cont;
@@ -273,7 +283,7 @@ static void __yo_async_enqueue_continuation(void (*resume_fn)(void*), void* stat
273
283
  __yo_thread_async_queue.head = cont;
274
284
  __yo_thread_async_queue.tail = cont;
275
285
  }
276
-
286
+
277
287
  __yo_thread_async_queue.count++;
278
288
  ASYNC_DEBUG("[ASYNC] Queue count: %zu\\n", __yo_thread_async_queue.count);
279
289
  }
@@ -299,7 +309,9 @@ static void __yo_async_run_ready_tasks(void) {
299
309
  }
300
310
  __yo_thread_async_queue.count--;
301
311
  cont->resume_fn(cont->state_machine);
302
- __yo_free(cont);
312
+ // Return the node to the free-list instead of freeing it.
313
+ cont->next = __yo_cont_free_list;
314
+ __yo_cont_free_list = cont;
303
315
  }
304
316
  }
305
317
 
@@ -344,7 +356,9 @@ static void __yo_async_run_until_complete(void* future_ptr) {
344
356
  (void*)cont->resume_fn, cont->state_machine, __yo_thread_async_queue.count);
345
357
 
346
358
  cont->resume_fn(cont->state_machine);
347
- __yo_free(cont);
359
+ // Return the node to the free-list instead of freeing it.
360
+ cont->next = __yo_cont_free_list;
361
+ __yo_cont_free_list = cont;
348
362
  tasks_run++;
349
363
  }
350
364
 
@@ -373,6 +387,13 @@ static void __yo_async_run_until_complete(void* future_ptr) {
373
387
 
374
388
  __yo_io_cleanup();
375
389
 
390
+ // Free the continuation node free-list so worker threads don't leak.
391
+ while (__yo_cont_free_list) {
392
+ __yo_continuation_t* node = __yo_cont_free_list;
393
+ __yo_cont_free_list = node->next;
394
+ __yo_free(node);
395
+ }
396
+
376
397
  ASYNC_DEBUG("[ASYNC] Event loop finished, future state=%d\\n", future->state);
377
398
 
378
399
  if (future->state == -2) {
@@ -422,6 +443,13 @@ static void __yo_async_wait_all(void) {
422
443
  }
423
444
  }
424
445
 
446
+ // Free the continuation node free-list so worker threads don't leak.
447
+ while (__yo_cont_free_list) {
448
+ __yo_continuation_t* node = __yo_cont_free_list;
449
+ __yo_cont_free_list = node->next;
450
+ __yo_free(node);
451
+ }
452
+
425
453
  ASYNC_DEBUG("[ASYNC] All tasks completed\\n");
426
454
  }
427
455
 
@@ -1047,6 +1075,17 @@ static _Thread_local int __yo_io_kq = -1;
1047
1075
  // __yo_io_initialized is defined in runtime-core
1048
1076
  static _Thread_local size_t __yo_pending_io_count = 0; // per-thread event loop counter
1049
1077
 
1078
+ // Batched kevent changelist. Instead of issuing one kevent() syscall per
1079
+ // I/O op to register EVFILT_READ/EVFILT_WRITE, we append changes to a
1080
+ // thread-local array and submit them in bulk as part of the kevent() call
1081
+ // in __yo_io_poll / __yo_io_wait. This is the kqueue analogue of deferred
1082
+ // io_uring submission on Linux and batched GetQueuedCompletionStatusEx on
1083
+ // Windows. Size = 64 matches the events[] buffer so EV_ERROR packets
1084
+ // always fit alongside the reaped events.
1085
+ #define __YO_KQ_CHANGES_MAX 64
1086
+ static _Thread_local struct kevent __yo_kq_changes[__YO_KQ_CHANGES_MAX];
1087
+ static _Thread_local size_t __yo_kq_n_changes = 0;
1088
+
1050
1089
  // Pending operation types for kqueue completion dispatch
1051
1090
  typedef enum {
1052
1091
  __YO_IO_OP_READ,
@@ -1075,6 +1114,43 @@ typedef struct __yo_io_pending_op_t {
1075
1114
  };
1076
1115
  } __yo_io_pending_op_t;
1077
1116
 
1117
+ // Per-thread free list for __yo_io_pending_op_t to eliminate malloc/free
1118
+ // per async I/O op. The first sizeof(void*) bytes are overlaid with a
1119
+ // 'next' pointer when the struct sits on the free list; allocation sites
1120
+ // fully re-initialize all fields they care about (op, future, and the
1121
+ // active union arm), so trampling the front of the struct is safe.
1122
+ static _Thread_local __yo_io_pending_op_t* __yo_io_pending_free_list = NULL;
1123
+
1124
+ static inline __yo_io_pending_op_t* __yo_kq_alloc_pending(void) {
1125
+ if (__yo_io_pending_free_list) {
1126
+ __yo_io_pending_op_t* p = __yo_io_pending_free_list;
1127
+ __yo_io_pending_free_list = *(__yo_io_pending_op_t**)p;
1128
+ return p;
1129
+ }
1130
+ return (__yo_io_pending_op_t*)__yo_malloc(sizeof(__yo_io_pending_op_t));
1131
+ }
1132
+
1133
+ static inline void __yo_kq_free_pending(__yo_io_pending_op_t* p) {
1134
+ if (!p) return;
1135
+ *(__yo_io_pending_op_t**)p = __yo_io_pending_free_list;
1136
+ __yo_io_pending_free_list = p;
1137
+ }
1138
+
1139
+ // Forward declaration \u2014 defined after __yo_io_init
1140
+ static void __yo_io_wake_continuation(__yo_io_future_t* future);
1141
+
1142
+ static inline void __yo_kq_cancel_pending(__yo_io_pending_op_t* pending, int err, bool wake) {
1143
+ if (!pending) return;
1144
+ pending->future->result = -err;
1145
+ __yo_pending_io_count--;
1146
+ if (wake) {
1147
+ __yo_io_wake_continuation(pending->future);
1148
+ } else {
1149
+ atomic_store_explicit(&pending->future->state, -1, memory_order_release);
1150
+ }
1151
+ __yo_kq_free_pending(pending);
1152
+ }
1153
+
1078
1154
  // Initialize kqueue subsystem
1079
1155
  static void __yo_io_init(void) {
1080
1156
  if (__yo_io_initialized) return;
@@ -1091,7 +1167,13 @@ static void __yo_io_init(void) {
1091
1167
  // Cleanup kqueue
1092
1168
  static void __yo_io_cleanup(void) {
1093
1169
  if (!__yo_io_initialized) return;
1094
-
1170
+ // If async main returns while registrations are still only queued in the
1171
+ // deferred changelist, cancel them explicitly instead of dropping them on
1172
+ // close(kqueue).
1173
+ for (size_t i = 0; i < __yo_kq_n_changes; i++) {
1174
+ __yo_kq_cancel_pending((__yo_io_pending_op_t*)__yo_kq_changes[i].udata, ECANCELED, false);
1175
+ }
1176
+ __yo_kq_n_changes = 0;
1095
1177
  if (__yo_io_kq >= 0) {
1096
1178
  close(__yo_io_kq);
1097
1179
  __yo_io_kq = -1;
@@ -1197,26 +1279,86 @@ static void __yo_io_process_event(struct kevent* ev) {
1197
1279
 
1198
1280
  __yo_pending_io_count--;
1199
1281
  __yo_io_wake_continuation(future);
1200
- __yo_free(pending);
1282
+ __yo_kq_free_pending(pending);
1283
+ }
1284
+
1285
+ // Flush any queued kevent changes to the kernel synchronously. Called when
1286
+ // the deferred changelist is full, or just before tearing down. Normal
1287
+ // flow amortizes changes into the next kevent() reap call in poll/wait.
1288
+ static void __yo_kq_flush_changes(void) {
1289
+ if (__yo_kq_n_changes == 0) return;
1290
+ struct kevent err_events[__YO_KQ_CHANGES_MAX];
1291
+ int nchanges = (int)__yo_kq_n_changes;
1292
+ int n;
1293
+ do {
1294
+ n = kevent(__yo_io_kq, __yo_kq_changes, nchanges,
1295
+ err_events, __YO_KQ_CHANGES_MAX, NULL);
1296
+ } while (n < 0 && errno == EINTR);
1297
+ // On a true syscall failure (e.g., EBADF), the changelist was not processed.
1298
+ // Surface each queued op as an error so counters/free list stay balanced.
1299
+ if (n < 0) {
1300
+ int err = errno;
1301
+ for (int i = 0; i < nchanges; i++) {
1302
+ __yo_kq_cancel_pending((__yo_io_pending_op_t*)__yo_kq_changes[i].udata, err, true);
1303
+ }
1304
+ __yo_kq_n_changes = 0;
1305
+ return;
1306
+ }
1307
+ __yo_kq_n_changes = 0;
1308
+ for (int i = 0; i < n; i++) {
1309
+ // EV_ERROR entries report per-change registration failures.
1310
+ if (err_events[i].flags & EV_ERROR) {
1311
+ __yo_kq_cancel_pending((__yo_io_pending_op_t*)err_events[i].udata, (int)err_events[i].data, true);
1312
+ } else {
1313
+ __yo_io_process_event(&err_events[i]);
1314
+ }
1315
+ }
1201
1316
  }
1202
1317
 
1203
1318
  // Process completions (non-blocking poll)
1204
1319
  static int __yo_io_poll(void) {
1205
1320
  struct kevent events[64];
1206
1321
  struct timespec ts = {0, 0}; // non-blocking
1207
- int n = kevent(__yo_io_kq, NULL, 0, events, 64, &ts);
1208
-
1322
+
1323
+ // Submit any pending changes atomically with the reap syscall.
1324
+ int nchanges = (int)__yo_kq_n_changes;
1325
+ int n;
1326
+ do {
1327
+ n = kevent(__yo_io_kq, __yo_kq_changes, nchanges, events, 64, &ts);
1328
+ } while (n < 0 && errno == EINTR);
1329
+ // Only clear the changelist if kevent accepted it (n >= 0). On hard
1330
+ // failure, surface each queued op so we do not leak pending_ops or
1331
+ // desync __yo_pending_io_count.
1332
+ if (n < 0) {
1333
+ int err = errno;
1334
+ for (int i = 0; i < nchanges; i++) {
1335
+ __yo_kq_cancel_pending((__yo_io_pending_op_t*)__yo_kq_changes[i].udata, err, true);
1336
+ }
1337
+ __yo_kq_n_changes = 0;
1338
+ return __yo_poll_and_fs_event_tick();
1339
+ }
1340
+ __yo_kq_n_changes = 0;
1341
+
1342
+ int processed = 0;
1209
1343
  for (int i = 0; i < n; i++) {
1344
+ if (events[i].flags & EV_ERROR) {
1345
+ if (events[i].udata) {
1346
+ __yo_kq_cancel_pending((__yo_io_pending_op_t*)events[i].udata, (int)events[i].data, true);
1347
+ processed++;
1348
+ }
1349
+ continue;
1350
+ }
1210
1351
  __yo_io_process_event(&events[i]);
1352
+ processed++;
1211
1353
  }
1212
-
1213
- if (n > 0) {
1214
- ASYNC_DEBUG("[IO] Polled %d kqueue completions\\n", n);
1354
+
1355
+ if (processed > 0) {
1356
+ ASYNC_DEBUG("[IO] Polled %d kqueue completions\\n", processed);
1215
1357
  }
1216
-
1358
+
1217
1359
  // Also tick poll/fs_event handles
1218
1360
  int extra = __yo_poll_and_fs_event_tick();
1219
- return (n > 0 ? n : 0) + extra;
1361
+ return processed + extra;
1220
1362
  }
1221
1363
 
1222
1364
  // Wait for at least one I/O completion (blocking)
@@ -1227,30 +1369,61 @@ static int __yo_io_wait(void) {
1227
1369
  nanosleep(&ts, NULL);
1228
1370
  return __yo_poll_and_fs_event_tick();
1229
1371
  }
1230
- if (__yo_pending_io_count == 0) return 0;
1231
-
1372
+ if (__yo_pending_io_count == 0) {
1373
+ // Defensive: if we have deferred changes but nothing pending, flush
1374
+ // them so they do not sit indefinitely (rare -- registration bumps
1375
+ // __yo_pending_io_count, so this should normally be a no-op).
1376
+ if (__yo_kq_n_changes > 0) __yo_kq_flush_changes();
1377
+ return 0;
1378
+ }
1379
+
1232
1380
  struct kevent events[64];
1233
1381
  struct timespec timeout = {0, 100 * 1000 * 1000}; // 100ms timeout
1234
- int n = kevent(__yo_io_kq, NULL, 0, events, 64, &timeout);
1235
-
1382
+
1383
+ // Submit any pending changes atomically with the blocking wait. This
1384
+ // turns N registration syscalls + 1 wait syscall into a single syscall.
1385
+ int nchanges = (int)__yo_kq_n_changes;
1386
+ int n;
1387
+ do {
1388
+ n = kevent(__yo_io_kq, __yo_kq_changes, nchanges, events, 64, &timeout);
1389
+ } while (n < 0 && errno == EINTR);
1390
+ if (n < 0) {
1391
+ int err = errno;
1392
+ for (int i = 0; i < nchanges; i++) {
1393
+ __yo_kq_cancel_pending((__yo_io_pending_op_t*)__yo_kq_changes[i].udata, err, true);
1394
+ }
1395
+ __yo_kq_n_changes = 0;
1396
+ return 0;
1397
+ }
1398
+ __yo_kq_n_changes = 0;
1399
+
1400
+ int processed = 0;
1236
1401
  for (int i = 0; i < n; i++) {
1402
+ if (events[i].flags & EV_ERROR) {
1403
+ if (events[i].udata) {
1404
+ __yo_kq_cancel_pending((__yo_io_pending_op_t*)events[i].udata, (int)events[i].data, true);
1405
+ processed++;
1406
+ }
1407
+ continue;
1408
+ }
1237
1409
  __yo_io_process_event(&events[i]);
1410
+ processed++;
1238
1411
  }
1239
-
1240
- return (n > 0) ? n : 0;
1412
+
1413
+ return processed;
1241
1414
  }
1242
1415
 
1243
- // Helper to register a kevent and track the pending operation
1416
+ // Queue a kevent change onto the per-thread deferred changelist. The
1417
+ // actual kevent() syscall is amortized into the next poll/wait call \u2014
1418
+ // see __yo_kq_flush_changes / __yo_io_poll / __yo_io_wait. If the
1419
+ // changelist is full we flush synchronously to make room.
1244
1420
  static void __yo_io_register_kevent(__yo_io_pending_op_t* pending, int fd, int16_t filter) {
1245
- struct kevent ev;
1246
- EV_SET(&ev, fd, filter, EV_ADD | EV_ONESHOT, 0, 0, (void*)pending);
1247
- if (kevent(__yo_io_kq, &ev, 1, NULL, 0, NULL) < 0) {
1248
- // Registration failed \u2014 complete immediately with error
1249
- pending->future->result = -errno;
1250
- __yo_io_wake_continuation(pending->future);
1251
- __yo_free(pending);
1252
- return;
1421
+ if (__yo_kq_n_changes >= __YO_KQ_CHANGES_MAX) {
1422
+ __yo_kq_flush_changes();
1253
1423
  }
1424
+ EV_SET(&__yo_kq_changes[__yo_kq_n_changes], fd, filter,
1425
+ EV_ADD | EV_ONESHOT, 0, 0, (void*)pending);
1426
+ __yo_kq_n_changes++;
1254
1427
  __yo_pending_io_count++;
1255
1428
  }
1256
1429
 
@@ -1293,7 +1466,7 @@ static __yo_io_future_t* __yo_async_read_start(int32_t fd, void* buffer, uint32_
1293
1466
  }
1294
1467
 
1295
1468
  // Would block \u2014 register kevent(EVFILT_READ) for readability notification
1296
- __yo_io_pending_op_t* pending = (__yo_io_pending_op_t*)__yo_malloc(sizeof(__yo_io_pending_op_t));
1469
+ __yo_io_pending_op_t* pending = __yo_kq_alloc_pending();
1297
1470
  pending->op = __YO_IO_OP_READ;
1298
1471
  pending->future = future;
1299
1472
  pending->read.fd = fd;
@@ -1354,7 +1527,7 @@ static __yo_io_future_t* __yo_async_write_start(int32_t fd, const void* buffer,
1354
1527
  }
1355
1528
 
1356
1529
  // Would block \u2014 register kevent(EVFILT_WRITE) for writability notification
1357
- __yo_io_pending_op_t* pending = (__yo_io_pending_op_t*)__yo_malloc(sizeof(__yo_io_pending_op_t));
1530
+ __yo_io_pending_op_t* pending = __yo_kq_alloc_pending();
1358
1531
  pending->op = __YO_IO_OP_WRITE;
1359
1532
  pending->future = future;
1360
1533
  pending->write.fd = fd;
@@ -1750,7 +1923,7 @@ static __yo_io_future_t* __yo_async_accept_start(int32_t sockfd, void* addr, uin
1750
1923
  atomic_init(&future->state, 0);
1751
1924
  future->result = 0;
1752
1925
 
1753
- __yo_io_pending_op_t* pending = (__yo_io_pending_op_t*)__yo_malloc(sizeof(__yo_io_pending_op_t));
1926
+ __yo_io_pending_op_t* pending = __yo_kq_alloc_pending();
1754
1927
  pending->op = __YO_IO_OP_ACCEPT;
1755
1928
  pending->future = future;
1756
1929
  pending->accept.fd = sockfd;
@@ -1794,7 +1967,7 @@ static __yo_io_future_t* __yo_async_connect_start(int32_t sockfd, const void* ad
1794
1967
  atomic_init(&future->state, 0);
1795
1968
  future->result = 0;
1796
1969
 
1797
- __yo_io_pending_op_t* pending = (__yo_io_pending_op_t*)__yo_malloc(sizeof(__yo_io_pending_op_t));
1970
+ __yo_io_pending_op_t* pending = __yo_kq_alloc_pending();
1798
1971
  pending->op = __YO_IO_OP_CONNECT;
1799
1972
  pending->future = future;
1800
1973
  pending->connect.fd = sockfd;
@@ -1836,7 +2009,7 @@ static __yo_io_future_t* __yo_async_send_start(int32_t sockfd, const void* buf,
1836
2009
  atomic_init(&future->state, 0);
1837
2010
  future->result = 0;
1838
2011
 
1839
- __yo_io_pending_op_t* pending = (__yo_io_pending_op_t*)__yo_malloc(sizeof(__yo_io_pending_op_t));
2012
+ __yo_io_pending_op_t* pending = __yo_kq_alloc_pending();
1840
2013
  pending->op = __YO_IO_OP_SEND;
1841
2014
  pending->future = future;
1842
2015
  pending->send.fd = sockfd;
@@ -1881,7 +2054,7 @@ static __yo_io_future_t* __yo_async_recv_start(int32_t sockfd, void* buf, size_t
1881
2054
  atomic_init(&future->state, 0);
1882
2055
  future->result = 0;
1883
2056
 
1884
- __yo_io_pending_op_t* pending = (__yo_io_pending_op_t*)__yo_malloc(sizeof(__yo_io_pending_op_t));
2057
+ __yo_io_pending_op_t* pending = __yo_kq_alloc_pending();
1885
2058
  pending->op = __YO_IO_OP_RECV;
1886
2059
  pending->future = future;
1887
2060
  pending->recv.fd = sockfd;
@@ -1927,7 +2100,7 @@ static __yo_io_future_t* __yo_async_sendto_start(int32_t sockfd, const void* buf
1927
2100
  atomic_init(&future->state, 0);
1928
2101
  future->result = 0;
1929
2102
 
1930
- __yo_io_pending_op_t* pending = (__yo_io_pending_op_t*)__yo_malloc(sizeof(__yo_io_pending_op_t));
2103
+ __yo_io_pending_op_t* pending = __yo_kq_alloc_pending();
1931
2104
  pending->op = __YO_IO_OP_SENDTO;
1932
2105
  pending->future = future;
1933
2106
  pending->sendto.fd = sockfd;
@@ -1975,7 +2148,7 @@ static __yo_io_future_t* __yo_async_recvfrom_start(int32_t sockfd, void* buf, si
1975
2148
  atomic_init(&future->state, 0);
1976
2149
  future->result = 0;
1977
2150
 
1978
- __yo_io_pending_op_t* pending = (__yo_io_pending_op_t*)__yo_malloc(sizeof(__yo_io_pending_op_t));
2151
+ __yo_io_pending_op_t* pending = __yo_kq_alloc_pending();
1979
2152
  pending->op = __YO_IO_OP_RECVFROM;
1980
2153
  pending->future = future;
1981
2154
  pending->recvfrom.fd = sockfd;
@@ -2560,6 +2733,13 @@ static uint64_t __yo_statx_blocks(void* statxbuf) {
2560
2733
  static _Thread_local struct io_uring __yo_io_ring;
2561
2734
  // __yo_io_initialized is defined in runtime-core
2562
2735
  static _Thread_local size_t __yo_pending_io_count = 0;
2736
+ // Ring size matches the fixed io_uring queue depth we initialize below.
2737
+ #define __YO_IO_RING_ENTRIES 1024
2738
+ // Number of SQEs queued in userspace but not yet submitted to the kernel.
2739
+ // Deferred submission lets us batch many SQEs into a single syscall.
2740
+ static _Thread_local unsigned __yo_sq_unsubmitted = 0;
2741
+ static _Thread_local unsigned __yo_sq_deferred_head = 0;
2742
+ static _Thread_local __yo_io_future_t* __yo_sq_deferred_futures[__YO_IO_RING_ENTRIES];
2563
2743
 
2564
2744
  // I/O Future types - __yo_io_future_t is defined in types/generation.ts
2565
2745
  // It has the same layout as async state machines (state, result, continuation_fn, continuation_sm)
@@ -2569,19 +2749,54 @@ static _Thread_local size_t __yo_pending_io_count = 0;
2569
2749
  // Initialize io_uring (called once at event loop start)
2570
2750
  static void __yo_io_init(void) {
2571
2751
  if (__yo_io_initialized) return;
2572
-
2573
- int ret = io_uring_queue_init(256, &__yo_io_ring, 0);
2752
+
2753
+ // Try modern kernel flags for lower overhead (requires Linux 6.0+):
2754
+ // IORING_SETUP_COOP_TASKRUN \u2014 avoids inter-processor interrupts on task wakeup
2755
+ // since we know our thread will reap completions.
2756
+ // IORING_SETUP_SINGLE_ISSUER \u2014 we only submit from the owner thread.
2757
+ // IORING_SETUP_DEFER_TASKRUN \u2014 completions run only when we call wait_cqe,
2758
+ // avoiding random overhead on timer interrupts.
2759
+ struct io_uring_params params;
2760
+ memset(&params, 0, sizeof(params));
2761
+ #ifdef IORING_SETUP_SINGLE_ISSUER
2762
+ params.flags |= IORING_SETUP_SINGLE_ISSUER;
2763
+ #endif
2764
+ #ifdef IORING_SETUP_COOP_TASKRUN
2765
+ params.flags |= IORING_SETUP_COOP_TASKRUN;
2766
+ #endif
2767
+ #ifdef IORING_SETUP_DEFER_TASKRUN
2768
+ params.flags |= IORING_SETUP_DEFER_TASKRUN;
2769
+ #endif
2770
+
2771
+ int ret = io_uring_queue_init_params(__YO_IO_RING_ENTRIES, &__yo_io_ring, &params);
2772
+ if (ret < 0) {
2773
+ // Retry without any flags for older kernels
2774
+ memset(&params, 0, sizeof(params));
2775
+ ret = io_uring_queue_init_params(__YO_IO_RING_ENTRIES, &__yo_io_ring, &params);
2776
+ }
2574
2777
  if (ret < 0) {
2575
2778
  fprintf(stderr, "[Yo] io_uring_queue_init failed: %s\\n", strerror(-ret));
2576
2779
  exit(1);
2577
2780
  }
2578
2781
  __yo_io_initialized = true;
2579
- ASYNC_DEBUG("[IO] io_uring initialized with 256 entries\\n");
2782
+ ASYNC_DEBUG("[IO] io_uring initialized with %d entries (flags=0x%x)\\n", __YO_IO_RING_ENTRIES, params.flags);
2580
2783
  }
2581
2784
 
2582
2785
  // Cleanup io_uring
2583
2786
  static void __yo_io_cleanup(void) {
2584
2787
  if (!__yo_io_initialized) return;
2788
+ // If async main returns while some SQEs are still only queued in userspace,
2789
+ // cancel them here instead of silently dropping the event-loop ref we took in
2790
+ // __yo_io_ring_submit.
2791
+ while (__yo_sq_unsubmitted > 0) {
2792
+ __yo_io_future_t* future = __yo_sq_deferred_futures[__yo_sq_deferred_head];
2793
+ __yo_sq_deferred_head = (__yo_sq_deferred_head + 1) % __YO_IO_RING_ENTRIES;
2794
+ __yo_sq_unsubmitted--;
2795
+ __yo_pending_io_count--;
2796
+ future->result = -ECANCELED;
2797
+ future->state = -1;
2798
+ __yo_decr_rc((void*)future);
2799
+ }
2585
2800
  io_uring_queue_exit(&__yo_io_ring);
2586
2801
  __yo_io_initialized = false;
2587
2802
  ASYNC_DEBUG("[IO] io_uring cleaned up\\n");
@@ -2595,13 +2810,35 @@ static inline bool __yo_has_pending_io(void) {
2595
2810
  // Forward declaration for poll/fs_event tick (defined in runtime-io-common)
2596
2811
  static int __yo_poll_and_fs_event_tick(void);
2597
2812
 
2598
- // Submit an SQE to io_uring with an event-loop RC reference.
2599
- // The reference is released in __yo_io_process_cqe after the CQE is consumed,
2600
- // preventing use-after-free if the user drops the future before completion.
2813
+ // Queue an SQE for later submission to io_uring (deferred submit).
2814
+ // The actual io_uring_submit() syscall happens inside __yo_io_poll / __yo_io_wait,
2815
+ // so many SQEs produced within one "drain tasks" pass batch into one syscall.
2601
2816
  static inline void __yo_io_ring_submit(__yo_io_future_t* future) {
2602
2817
  future->header.ref_count++; // io_uring holds a reference until CQE
2603
- io_uring_submit(&__yo_io_ring);
2604
2818
  __yo_pending_io_count++;
2819
+ __yo_sq_deferred_futures[(__yo_sq_deferred_head + __yo_sq_unsubmitted) % __YO_IO_RING_ENTRIES] = future;
2820
+ __yo_sq_unsubmitted++;
2821
+ }
2822
+
2823
+ // Flush any queued SQEs to the kernel if non-empty. Returns the liburing result.
2824
+ // On failure (ret < 0), the SQEs remain in the SQ ring so __yo_sq_unsubmitted
2825
+ // is left unchanged -- the next submit attempt (via poll/wait) will retry them.
2826
+ static inline int __yo_io_flush_sq(void) {
2827
+ if (__yo_sq_unsubmitted == 0) return 0;
2828
+ int ret;
2829
+ do {
2830
+ ret = io_uring_submit(&__yo_io_ring);
2831
+ } while (ret == -EINTR);
2832
+ if (ret > 0) {
2833
+ if ((size_t)ret >= __yo_sq_unsubmitted) {
2834
+ __yo_sq_deferred_head = (__yo_sq_deferred_head + __yo_sq_unsubmitted) % __YO_IO_RING_ENTRIES;
2835
+ __yo_sq_unsubmitted = 0;
2836
+ } else {
2837
+ __yo_sq_deferred_head = (__yo_sq_deferred_head + (unsigned)ret) % __YO_IO_RING_ENTRIES;
2838
+ __yo_sq_unsubmitted -= (size_t)ret;
2839
+ }
2840
+ }
2841
+ return ret;
2605
2842
  }
2606
2843
 
2607
2844
  // Process completions from CQ
@@ -2640,17 +2877,24 @@ static void __yo_io_process_cqe(struct io_uring_cqe* cqe) {
2640
2877
 
2641
2878
  // Poll for I/O completions (non-blocking)
2642
2879
  static int __yo_io_poll(void) {
2643
- struct io_uring_cqe* cqe;
2880
+ // Flush any queued SQEs so their completions can be reaped.
2881
+ __yo_io_flush_sq();
2882
+
2883
+ struct io_uring_cqe* cqes[64];
2644
2884
  int count = 0;
2645
-
2646
- while (io_uring_peek_cqe(&__yo_io_ring, &cqe) == 0) {
2647
- __yo_io_process_cqe(cqe);
2648
- count++;
2885
+
2886
+ unsigned n;
2887
+ while ((n = io_uring_peek_batch_cqe(&__yo_io_ring, cqes, 64)) > 0) {
2888
+ for (unsigned i = 0; i < n; ++i) {
2889
+ __yo_io_process_cqe(cqes[i]);
2890
+ count++;
2891
+ }
2892
+ if (n < 64) break;
2649
2893
  }
2650
-
2894
+
2651
2895
  // Also tick poll/fs_event handles
2652
2896
  count += __yo_poll_and_fs_event_tick();
2653
-
2897
+
2654
2898
  if (count > 0) {
2655
2899
  ASYNC_DEBUG("[IO] Polled %d completions\\n", count);
2656
2900
  }
@@ -2665,14 +2909,42 @@ static int __yo_io_wait(void) {
2665
2909
  nanosleep(&ts, NULL);
2666
2910
  return __yo_poll_and_fs_event_tick();
2667
2911
  }
2668
-
2912
+
2669
2913
  struct io_uring_cqe* cqe;
2670
- int ret = io_uring_wait_cqe(&__yo_io_ring, &cqe);
2671
- if (ret < 0) {
2672
- ASYNC_DEBUG("[IO] WARNING: io_uring_wait_cqe failed: %d\\n", ret);
2673
- return 0;
2914
+ // Combined submit + wait in a single syscall -- this is the main batching win:
2915
+ // all deferred SQEs are submitted alongside the blocking wait for a CQE.
2916
+ int ret;
2917
+ if (__yo_sq_unsubmitted > 0) {
2918
+ do {
2919
+ ret = io_uring_submit_and_wait(&__yo_io_ring, 1);
2920
+ } while (ret == -EINTR);
2921
+ if (ret < 0) {
2922
+ // SQEs remain in the SQ ring; do not drop __yo_sq_unsubmitted so the
2923
+ // next poll/wait retries them.
2924
+ ASYNC_DEBUG("[IO] WARNING: io_uring_submit_and_wait failed: %d\\n", ret);
2925
+ return 0;
2926
+ }
2927
+ // ret >= 0: that many SQEs were submitted.
2928
+ if ((size_t)ret >= __yo_sq_unsubmitted) {
2929
+ __yo_sq_deferred_head = (__yo_sq_deferred_head + __yo_sq_unsubmitted) % __YO_IO_RING_ENTRIES;
2930
+ __yo_sq_unsubmitted = 0;
2931
+ } else {
2932
+ __yo_sq_deferred_head = (__yo_sq_deferred_head + (unsigned)ret) % __YO_IO_RING_ENTRIES;
2933
+ __yo_sq_unsubmitted -= (size_t)ret;
2934
+ }
2935
+ // After submit_and_wait, at least one CQE should be available; peek it.
2936
+ if (io_uring_peek_cqe(&__yo_io_ring, &cqe) != 0) {
2937
+ // Rare: spurious wake. Fall back to explicit wait.
2938
+ if (io_uring_wait_cqe(&__yo_io_ring, &cqe) < 0) return 0;
2939
+ }
2940
+ } else {
2941
+ ret = io_uring_wait_cqe(&__yo_io_ring, &cqe);
2942
+ if (ret < 0) {
2943
+ ASYNC_DEBUG("[IO] WARNING: io_uring_wait_cqe failed: %d\\n", ret);
2944
+ return 0;
2945
+ }
2674
2946
  }
2675
-
2947
+
2676
2948
  ASYNC_DEBUG("[IO] Waiting for I/O completion...\\n");
2677
2949
  __yo_io_process_cqe(cqe);
2678
2950
  return 1 + __yo_io_poll(); // Process any additional completions
@@ -5509,6 +5781,12 @@ typedef struct {
5509
5781
  DWORD sock_flags;
5510
5782
  } __yo_win_overlapped_t;
5511
5783
 
5784
+ // Per-thread free list for __yo_win_overlapped_t to eliminate malloc/free
5785
+ // per I/O op. The first sizeof(void*) bytes of an overlapped struct overlay
5786
+ // the 'next' pointer when the struct is on the free list (it is fully
5787
+ // re-initialized on alloc, so trampling OVERLAPPED.Internal is fine).
5788
+ static __declspec(thread) __yo_win_overlapped_t* __yo_win_ov_free_list = NULL;
5789
+
5512
5790
 
5513
5791
  static void __yo_io_init(void) {
5514
5792
  if (__yo_io_initialized) return;
@@ -5632,10 +5910,27 @@ static DWORD __yo_win_timer_next_timeout(uint64_t now_ms) {
5632
5910
  return (DWORD)delta;
5633
5911
  }
5634
5912
 
5913
+ // Forward declarations -- these are defined after the IOCP poll/wait
5914
+ // functions but called by __yo_win_process_completion which comes first.
5915
+ static __yo_win_overlapped_t* __yo_win_alloc_overlapped(__yo_io_future_t* future, HANDLE handle, uint64_t offset);
5916
+ static inline void __yo_win_free_overlapped(__yo_win_overlapped_t* ov);
5917
+
5635
5918
  static void __yo_win_process_completion(__yo_win_overlapped_t* ov, DWORD bytes) {
5636
5919
  if (!ov) return;
5637
5920
 
5638
- if (ov->is_socket) {
5921
+ // The byte count is already in OVERLAPPED_ENTRY.dwNumberOfBytesTransferred
5922
+ // (passed in as the bytes argument) and the NTSTATUS is in
5923
+ // ov->overlapped.Internal. Calling WSAGetOverlappedResult /
5924
+ // GetOverlappedResult here would do redundant work -- those APIs
5925
+ // essentially return what IOCP already gave us. Use the OVERLAPPED
5926
+ // fields directly to save a per-completion syscall.
5927
+ NTSTATUS status = (NTSTATUS)ov->overlapped.Internal;
5928
+ if (status == 0) {
5929
+ ov->future->result = (int32_t)bytes;
5930
+ } else if (ov->is_socket) {
5931
+ // RtlNtStatusToDosError lives in ntdll; rather than dynamically loading
5932
+ // it, fall back to the same WSAGetOverlappedResult path on error so
5933
+ // we get the proper WSA errno mapping.
5639
5934
  DWORD flags = 0;
5640
5935
  DWORD transferred = bytes;
5641
5936
  BOOL ok = WSAGetOverlappedResult(ov->sock, &ov->overlapped, &transferred, FALSE, &flags);
@@ -5660,7 +5955,7 @@ static void __yo_win_process_completion(__yo_win_overlapped_t* ov, DWORD bytes)
5660
5955
  }
5661
5956
 
5662
5957
  __yo_io_wake_continuation(ov->future);
5663
- __yo_free(ov);
5958
+ __yo_win_free_overlapped(ov);
5664
5959
  }
5665
5960
 
5666
5961
  static int __yo_io_poll(void) {
@@ -5696,24 +5991,32 @@ static int __yo_io_wait(void) {
5696
5991
  }
5697
5992
  if (__yo_pending_io_count == 0) return 0;
5698
5993
 
5699
- DWORD bytes = 0;
5700
- ULONG_PTR key = 0;
5701
- OVERLAPPED* ov = NULL;
5702
5994
  DWORD timeout_ms = __yo_win_timer_next_timeout(__yo_win_now_ms());
5703
5995
  if (__yo_active_watch_count > 0 && (timeout_ms == INFINITE || timeout_ms > 50)) {
5704
5996
  timeout_ms = 50;
5705
5997
  }
5706
- BOOL ok = GetQueuedCompletionStatus(__yo_io_iocp, &bytes, &key, &ov, timeout_ms);
5707
- if (!ok && GetLastError() == WAIT_TIMEOUT) {
5708
- return __yo_win_timer_process_due(__yo_win_now_ms()) + __yo_poll_and_fs_event_tick();
5709
- }
5998
+
5999
+ // Use the batched Ex variant so that when the wait wakes up due to one
6000
+ // ready completion we drain ALL ready completions in a single syscall,
6001
+ // rather than taking N trips through the event loop for N completions
6002
+ // ready at the same time. This is the Windows analogue of Linux's
6003
+ // io_uring_submit_and_wait + peek_batch_cqe pattern and matters a lot
6004
+ // for HTTP-style workloads where many sockets become ready together.
6005
+ OVERLAPPED_ENTRY entries[64];
6006
+ ULONG count = 0;
6007
+ BOOL ok = GetQueuedCompletionStatusEx(__yo_io_iocp, entries, 64, &count, timeout_ms, FALSE);
5710
6008
  if (!ok) {
5711
6009
  return __yo_win_timer_process_due(__yo_win_now_ms()) + __yo_poll_and_fs_event_tick();
5712
6010
  }
5713
- if (!ov) return __yo_poll_and_fs_event_tick();
5714
6011
 
5715
- __yo_win_process_completion((__yo_win_overlapped_t*)ov, bytes);
5716
- return 1 + __yo_win_timer_process_due(__yo_win_now_ms()) + __yo_poll_and_fs_event_tick();
6012
+ int processed = 0;
6013
+ for (ULONG i = 0; i < count; i++) {
6014
+ if (!entries[i].lpOverlapped) continue;
6015
+ __yo_win_process_completion((__yo_win_overlapped_t*)entries[i].lpOverlapped,
6016
+ entries[i].dwNumberOfBytesTransferred);
6017
+ processed++;
6018
+ }
6019
+ return processed + __yo_win_timer_process_due(__yo_win_now_ms()) + __yo_poll_and_fs_event_tick();
5717
6020
  }
5718
6021
 
5719
6022
 
@@ -5753,8 +6056,14 @@ static void __yo_win_fd_unmark_append(int fd) {
5753
6056
  static void __yo_win_cleanup_dir_state(int32_t fd);
5754
6057
 
5755
6058
  static __yo_win_overlapped_t* __yo_win_alloc_overlapped(__yo_io_future_t* future, HANDLE handle, uint64_t offset) {
5756
- __yo_win_overlapped_t* ov = (__yo_win_overlapped_t*)__yo_malloc(sizeof(__yo_win_overlapped_t));
5757
- if (!ov) return NULL;
6059
+ __yo_win_overlapped_t* ov;
6060
+ if (__yo_win_ov_free_list) {
6061
+ ov = __yo_win_ov_free_list;
6062
+ __yo_win_ov_free_list = *(__yo_win_overlapped_t**)ov;
6063
+ } else {
6064
+ ov = (__yo_win_overlapped_t*)__yo_malloc(sizeof(__yo_win_overlapped_t));
6065
+ if (!ov) return NULL;
6066
+ }
5758
6067
  memset(ov, 0, sizeof(__yo_win_overlapped_t));
5759
6068
  ov->future = future;
5760
6069
  ov->handle = handle;
@@ -5765,6 +6074,18 @@ static __yo_win_overlapped_t* __yo_win_alloc_overlapped(__yo_io_future_t* future
5765
6074
  return ov;
5766
6075
  }
5767
6076
 
6077
+ // Return an overlapped struct to the per-thread free list. Used by the
6078
+ // IOCP completion path AND by the synchronous-completion paths in
6079
+ // recv/send (where FILE_SKIP_COMPLETION_PORT_ON_SUCCESS means no IOCP
6080
+ // packet will arrive) and by error paths. The first sizeof(void*) bytes
6081
+ // of the struct are repurposed for the free-list pointer; alloc re-zeroes
6082
+ // the entire struct so this is safe.
6083
+ static inline void __yo_win_free_overlapped(__yo_win_overlapped_t* ov) {
6084
+ if (!ov) return;
6085
+ *(__yo_win_overlapped_t**)ov = __yo_win_ov_free_list;
6086
+ __yo_win_ov_free_list = ov;
6087
+ }
6088
+
5768
6089
  static DWORD __yo_win_access_flags(int32_t flags) {
5769
6090
  if ((flags & O_RDWR) == O_RDWR) return GENERIC_READ | GENERIC_WRITE;
5770
6091
  if (flags & O_WRONLY) return GENERIC_WRITE;
@@ -5827,13 +6148,13 @@ static __yo_io_future_t* __yo_async_read_start(int32_t fd, void* buffer, uint32_
5827
6148
  DWORD bytes_transferred = 0;
5828
6149
  GetOverlappedResult(handle, &ov->overlapped, &bytes_transferred, FALSE);
5829
6150
  future->result = (int32_t)bytes_transferred;
5830
- __yo_free(ov);
6151
+ __yo_win_free_overlapped(ov);
5831
6152
  atomic_store(&future->state, -1);
5832
6153
  return future;
5833
6154
  }
5834
6155
  DWORD err = GetLastError();
5835
6156
  if (err != ERROR_IO_PENDING) {
5836
- __yo_free(ov);
6157
+ __yo_win_free_overlapped(ov);
5837
6158
  if (err == ERROR_HANDLE_EOF) {
5838
6159
  future->result = 0;
5839
6160
  } else {
@@ -5902,13 +6223,13 @@ static __yo_io_future_t* __yo_async_write_start(int32_t fd, const void* buffer,
5902
6223
  DWORD bytes_transferred = 0;
5903
6224
  GetOverlappedResult(handle, &ov->overlapped, &bytes_transferred, FALSE);
5904
6225
  future->result = (int32_t)bytes_transferred;
5905
- __yo_free(ov);
6226
+ __yo_win_free_overlapped(ov);
5906
6227
  atomic_store(&future->state, -1);
5907
6228
  return future;
5908
6229
  }
5909
6230
  DWORD err = GetLastError();
5910
6231
  if (err != ERROR_IO_PENDING) {
5911
- __yo_free(ov);
6232
+ __yo_win_free_overlapped(ov);
5912
6233
  future->result = -__yo_win_error_to_errno(err);
5913
6234
  atomic_store(&future->state, -1);
5914
6235
  return future;
@@ -6627,7 +6948,7 @@ static __yo_io_future_t* __yo_async_send_start(int32_t sockfd, const void* buf,
6627
6948
  // no IOCP packet will be posted, so complete the future immediately.
6628
6949
  future->result = (int32_t)sent;
6629
6950
  atomic_init(&future->state, -1);
6630
- __yo_free(ov);
6951
+ __yo_win_free_overlapped(ov);
6631
6952
  return future;
6632
6953
  }
6633
6954
  if (result == SOCKET_ERROR && WSAGetLastError() == WSA_IO_PENDING) {
@@ -6638,7 +6959,7 @@ static __yo_io_future_t* __yo_async_send_start(int32_t sockfd, const void* buf,
6638
6959
 
6639
6960
  future->result = -(int32_t)WSAGetLastError();
6640
6961
  atomic_init(&future->state, -1);
6641
- __yo_free(ov);
6962
+ __yo_win_free_overlapped(ov);
6642
6963
  return future;
6643
6964
  }
6644
6965
 
@@ -6676,7 +6997,7 @@ static __yo_io_future_t* __yo_async_recv_start(int32_t sockfd, void* buf, size_t
6676
6997
  // no IOCP packet will be posted, so complete the future immediately.
6677
6998
  future->result = (int32_t)received;
6678
6999
  atomic_init(&future->state, -1);
6679
- __yo_free(ov);
7000
+ __yo_win_free_overlapped(ov);
6680
7001
  return future;
6681
7002
  }
6682
7003
  if (result == SOCKET_ERROR && WSAGetLastError() == WSA_IO_PENDING) {
@@ -6687,7 +7008,7 @@ static __yo_io_future_t* __yo_async_recv_start(int32_t sockfd, void* buf, size_t
6687
7008
 
6688
7009
  future->result = -(int32_t)WSAGetLastError();
6689
7010
  atomic_init(&future->state, -1);
6690
- __yo_free(ov);
7011
+ __yo_win_free_overlapped(ov);
6691
7012
  return future;
6692
7013
  }
6693
7014