@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/cjs/index.cjs +680 -359
- package/out/cjs/yo-cli.cjs +684 -363
- package/out/cjs/yo-lsp.cjs +709 -388
- package/out/esm/index.mjs +403 -82
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
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)
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
1214
|
-
ASYNC_DEBUG("[IO] Polled %d kqueue completions\\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
|
|
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)
|
|
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
|
-
|
|
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
|
|
1412
|
+
|
|
1413
|
+
return processed;
|
|
1241
1414
|
}
|
|
1242
1415
|
|
|
1243
|
-
//
|
|
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
|
-
|
|
1246
|
-
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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
|
-
|
|
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(¶ms, 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, ¶ms);
|
|
2772
|
+
if (ret < 0) {
|
|
2773
|
+
// Retry without any flags for older kernels
|
|
2774
|
+
memset(¶ms, 0, sizeof(params));
|
|
2775
|
+
ret = io_uring_queue_init_params(__YO_IO_RING_ENTRIES, &__yo_io_ring, ¶ms);
|
|
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
|
|
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
|
-
//
|
|
2599
|
-
// The
|
|
2600
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
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
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
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
|
-
|
|
5716
|
-
|
|
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
|
|
5757
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7011
|
+
__yo_win_free_overlapped(ov);
|
|
6691
7012
|
return future;
|
|
6692
7013
|
}
|
|
6693
7014
|
|