Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/CanadaHonk/porffor. An ahead-of-time JavaScript compiler porffor.dev
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504import { T, K, FX, KNames, N_KIND, N_TYPE, N_FX, N_A, N_B, N_C, FN_ASYNC, FN_GENERATOR, FN_ASYNC_GENERATOR, CONVERT_RANGE_KNOWN, CONVERT_SIGNED} from './ir.js';import { TYPES, TYPE_NAMES, ITERATOR_TYPES } from './types.js';import { ieee754_binary64 } from './encoding.js';
// C type per IR value typeconst CT = [];CT[T.none] = 'void';CT[T.f64] = 'f64';CT[T.i32] = 'i32';CT[T.u32] = 'u32';CT[T.i64] = 'i64';CT[T.u64] = 'u64';CT[T.jsval] = 'jsval';CT[T.ptr] = 'u32';
// C precedence (higher binds tighter)const P_COMMA = 1, P_TERNARY = 3, P_LOR = 4, P_LAND = 5, P_BOR = 6, P_BXOR = 7, P_BAND = 8, P_EQ = 9, P_REL = 10, P_SHIFT = 11, P_ADD = 12, P_MUL = 13, P_UNARY = 14, P_CAST = 15, P_POSTFIX = 16, P_PRIM = 17;
const BIN_PREC = { '*': P_MUL, '/': P_MUL, '%': P_MUL, '+': P_ADD, '-': P_ADD, '<<': P_SHIFT, '>>': P_SHIFT, '<': P_REL, '<=': P_REL, '>': P_REL, '>=': P_REL, '==': P_EQ, '!=': P_EQ, '&': P_BAND, '^': P_BXOR, '|': P_BOR, '&&': P_LAND, '||': P_LOR};
const cReservedNames = new Set([ 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline', 'int', 'long', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while', '_Alignas', '_Alignof', '_Atomic', '_BitInt', '_Bool', '_Complex', '_Generic', '_Imaginary', '_Noreturn', '_Static_assert', '_Thread_local', 'alignas', 'alignof', 'constexpr', 'nullptr', 'static_assert', 'thread_local', 'typeof_unqual', '__alignof', '__alignof__', '__nullptr', '__typeof_unqual', '__typeof_unqual__', 'asm', 'typeof', 'main', '_return', 'porf_fr', 'porf_frs', 'i8', 'u8', 'i16', 'u16', 'i32', 'u32', 'i64', 'u64', 'f32', 'f64', 'jsval', 'NULL', 'NAN', 'INFINITY', 'stdin', 'stdout', 'stderr', 'FILE', 'EOF', 'printf', 'fprintf', 'putchar', 'exit', 'abort', 'atexit', 'getenv', 'calloc', 'malloc', 'realloc', 'free', 'memcpy', 'memmove', 'memset', 'strlen', 'log', 'read', 'write', 'close', 'signal', 'time', // unistd / posix 'fork', 'sleep', 'usleep', 'sync', '_exit', 'pipe', 'dup', 'dup2', 'pause', 'alarm', 'getpid', 'getppid', 'open', 'link', 'unlink', 'access', 'kill', 'raise', // stdlib 'random', 'srandom', 'rand', 'srand', 'system', 'abs', 'labs', 'div', 'ldiv', 'qsort', 'bsearch', 'atoi', 'atol', 'atof', 'gets', 'remove', 'rename', // math (libm) 'log2', 'log10', 'log1p', 'pow', 'sqrt', 'cbrt', 'exp', 'exp2', 'expm1', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2', 'sinh', 'cosh', 'tanh', 'fabs', 'floor', 'ceil', 'round', 'trunc', 'fmod', 'hypot', 'remainder', 'clock', 'times', 'gmtime', 'localtime', 'HUGE', 'HUGE_VAL', 'HUGE_VALF', 'HUGE_VALL', 'MAXFLOAT', 'FP_NAN', 'FP_INFINITE', 'FP_ZERO', 'FP_NORMAL', 'FP_SUBNORMAL', 'M_E', 'M_LOG2E', 'M_LOG10E', 'M_LN2', 'M_LN10', 'M_PI', 'M_PI_2', 'M_PI_4', 'M_1_PI', 'M_2_PI', 'M_2_SQRTPI', 'M_SQRT2', 'M_SQRT1_2', 'errno', // string.h / strings.h (index is the big one: legacy strchr alias) 'index', 'rindex', 'bcopy', 'bzero', 'bcmp', 'ffs', 'ffsl', 'ffsll', 'fls', 'flsl', 'flsll', 'strcasecmp', 'strncasecmp', 'strcpy', 'strncpy', 'strcat', 'strncat', 'strcmp', 'strncmp', 'strchr', 'strrchr', 'strstr', 'strtok', 'strdup', 'strndup', 'strerror', 'strspn', 'strcspn', 'strpbrk', 'strcoll', 'strxfrm', 'strsep', 'stpcpy', 'stpncpy', 'strnlen', 'strlcpy', 'strlcat', 'memchr', 'memcmp', 'memccpy', 'swab', // stdio 'puts', 'fputs', 'fgets', 'fgetc', 'fputc', 'getc', 'putc', 'getchar', 'scanf', 'sscanf', 'fscanf', 'snprintf', 'sprintf', 'vsnprintf', 'vsprintf', 'vprintf', 'vfprintf', 'fopen', 'freopen', 'fclose', 'fread', 'fwrite', 'fseek', 'fseeko', 'ftell', 'ftello', 'rewind', 'perror', 'tmpfile', 'tmpnam', 'setbuf', 'setvbuf', 'fflush', 'ungetc', 'feof', 'ferror', 'clearerr', 'fileno', 'fdopen', 'popen', 'pclose', // setjmp / errno / predefined macros 'setjmp', 'longjmp', '_setjmp', '_longjmp', 'sigsetjmp', 'siglongjmp', 'errno', 'bool', 'true', 'false', 'unix', 'linux', // stdlib 'strtol', 'strtoul', 'strtoll', 'strtoull', 'strtod', 'strtof', 'strtold', 'mblen', 'mbtowc', 'wctomb', 'mbstowcs', 'wcstombs', 'realpath', 'mkstemp', 'mkdtemp', 'mktemp', 'setenv', 'unsetenv', 'putenv', 'posix_memalign', 'aligned_alloc', 'arc4random', 'valloc', 'alloca', // math (libm) continued 'acosh', 'asinh', 'atanh', 'erf', 'erfc', 'tgamma', 'lgamma', 'fmax', 'fmin', 'fma', 'fdim', 'copysign', 'nearbyint', 'rint', 'lrint', 'llrint', 'lround', 'llround', 'frexp', 'ldexp', 'modf', 'scalbn', 'scalbln', 'ilogb', 'logb', 'nan', 'nanf', 'nextafter', 'nexttoward', 'remquo', 'j0', 'j1', 'jn', 'y0', 'y1', 'yn', 'gamma', 'drem', 'finite', 'significand', // signal 'sigaction', 'sigaddset', 'sigdelset', 'sigemptyset', 'sigfillset', 'sigismember', 'sigprocmask', 'sigsuspend', 'sigpending', 'sigwait', 'killpg', 'psignal', // unistd / posix continued 'lseek', 'chdir', 'fchdir', 'getcwd', 'isatty', 'ttyname', 'execv', 'execve', 'execvp', 'execl', 'execlp', 'execle', 'getuid', 'geteuid', 'getgid', 'getegid', 'setuid', 'setgid', 'seteuid', 'setegid', 'getpgrp', 'setpgid', 'setsid', 'getsid', 'truncate', 'ftruncate', 'rmdir', 'chown', 'fchown', 'lchown', 'readlink', 'symlink', 'nice', 'crypt', 'encrypt', 'brk', 'sbrk', 'gethostname', 'sethostname', 'getlogin', 'fsync', 'fdatasync', 'pread', 'pwrite', 'environ', 'getopt', 'optarg', 'optind', 'opterr', 'optopt', 'confstr', 'pathconf', 'fpathconf', 'sysconf', 'chroot', 'vfork', 'daemon', 'setgroups', 'getgroups', // dirent 'opendir', 'readdir', 'closedir', 'rewinddir', 'seekdir', 'telldir', 'scandir', 'alphasort', 'dirfd', 'fdopendir', // sys/mman, sys/stat, sys/wait 'mmap', 'munmap', 'mprotect', 'madvise', 'msync', 'mlock', 'munlock', 'mlockall', 'munlockall', 'mincore', 'shm_open', 'shm_unlink', 'stat', 'fstat', 'lstat', 'fstatat', 'chmod', 'fchmod', 'fchmodat', 'mkdir', 'mkdirat', 'mkfifo', 'mknod', 'umask', 'futimens', 'utimensat', 'wait', 'waitpid', 'wait3', 'wait4', // time.h continued 'mktime', 'ctime', 'asctime', 'strftime', 'strptime', 'difftime', 'timegm', 'timelocal', 'tzset', 'daylight', 'timezone', 'tzname', 'nanosleep', 'clock_gettime', 'clock_settime', 'clock_getres', 'ctime_r', 'asctime_r', 'gmtime_r', 'localtime_r', 'gettimeofday']);
// inlining these has little perf benefit and significantly increases binary sizeconst NEVER_INLINE = new Set([ '__Porffor_object_get_ic', '__Porffor_object_get_icMiss', '__Porffor_object_get_withHash', '__Porffor_array_holeGet', '__ecma262_ToNumeric', '__RegExp_prototype_exec']);
const sanitizeMemo = new Map();const sanitizeUsed = new Set();export const sanitize = str => { const memod = sanitizeMemo.get(str); if (memod != null) return memod;
let out = ''; for (let i = 0; i < str.length; i++) { const code = str.charCodeAt(i); const ok = (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95; if (ok) out += String.fromCharCode(code); else out += '_' + code.toString(16); } if (out.length === 0) out = 'anon'; if (out[0] >= '0' && out[0] <= '9') out = '_' + out; // keep prefixing: one '_' can itself collide (exit -> _exit) while (cReservedNames.has(out) || sanitizeUsed.has(out)) out = '_' + out; sanitizeUsed.add(out); sanitizeMemo.set(str, out); return out;};
// jsval encoding constants (must match runtime header below)const JV_PATTERN = 0xFFF8000000000000n;const jvConstBits = (typeId, payload) => JV_PATTERN | (BigInt(typeId & 0xFF) << 43n) | BigInt(payload >>> 0);const JV_ZERO_BITS = jvConstBits(TYPES.number, 0);
// lz4 block format, chained-hash lazy matching (~lz4hc ratio, ~25% smaller than greedy).// used by --compress-data to shrink the static data image in the binaryconst lz4Compress = src => { const n = src.length; const out = new Uint8Array(n + ((n / 255) | 0) + 16); const head = new Uint32Array(1 << 16); const prev = new Uint32Array(n); const read32 = i => src[i] | (src[i + 1] << 8) | (src[i + 2] << 16) | (src[i + 3] << 24); let op = 0, anchor = 0, i = 0, ins = 0, mLen = 0, mPos = 0; const mflimit = n - 12;
const emitSeq = (litLen, offset, mlen) => { out[op++] = (Math.min(litLen, 15) << 4) | (offset === 0 ? 0 : Math.min(mlen - 4, 15)); if (litLen >= 15) { let rest = litLen - 15; while (rest >= 255) { out[op++] = 255; rest -= 255; } out[op++] = rest; } for (let k = 0; k < litLen; k++) out[op++] = src[anchor + k]; if (offset === 0) return; out[op++] = offset & 0xff; out[op++] = offset >> 8; if (mlen - 4 >= 15) { let rest = mlen - 4 - 15; while (rest >= 255) { out[op++] = 255; rest -= 255; } out[op++] = rest; } };
const insertTo = limit => { while (ins < limit) { const h = (Math.imul(read32(ins), 2654435761) >>> 16) & 0xffff; prev[ins] = head[h]; head[h] = ins + 1; ins++; } };
const findMatch = at => { const maxLen = n - 5 - at; const minPos = at > 0xffff ? at - 0xffff : 0; let cand = head[(Math.imul(read32(at), 2654435761) >>> 16) & 0xffff] - 1; let depth = 64; mLen = 3; while (cand >= minPos && depth > 0) { depth--; if (src[cand + mLen] === src[at + mLen]) { let len = 0; while (len < maxLen && src[cand + len] === src[at + len]) len++; if (len > mLen) { mLen = len; mPos = cand; } } cand = prev[cand] - 1; } };
while (i <= mflimit) { insertTo(i); findMatch(i); if (mLen < 4) { i++; continue; } let len = mLen, pos = mPos; while (i + 1 <= mflimit) { // lazy: prefer a longer match starting one byte later insertTo(i + 1); findMatch(i + 1); if (mLen > len) { i++; len = mLen; pos = mPos; } else break; } emitSeq(i - anchor, i - pos, len); i += len; anchor = i; } emitSeq(n - anchor, 0, 0); return { out, len: op };};
const LZ4_DECODE = `static void porf_lz4_decode(const u8* src, u32 slen, u8* dst) { const u8* send = src + slen; while (src < send) { u32 token = *src++; u32 len = token >> 4; if (len == 15) { u32 b; do { b = *src++; len += b; } while (b == 255); } memcpy(dst, src, len); dst += len; src += len; if (src >= send) break; u32 off = src[0] | ((u32)src[1] << 8); src += 2; len = (token & 15) + 4; if (len == 19) { u32 b; do { b = *src++; len += b; } while (b == 255); } const u8* m = dst - off; while (len--) *dst++ = *m++; }}
`;
const f64Lit = value => { if (Number.isNaN(value)) return 'NAN'; if (value === Infinity) return 'INFINITY'; if (value === -Infinity) return '-INFINITY'; if (value === 0) return 1 / value === -Infinity ? '-0.0' : '0.0';
const str = value.toString(); if (Number.isInteger(value) && !/[eE]/.test(str)) return str.includes('.') ? str : str + '.0';
const bytes = ieee754_binary64(value); let hex = ''; for (let i = 7; i >= 0; i--) hex += bytes[i].toString(16).padStart(2, '0'); return `porf_bits_to_f64(0x${hex}ull)`;};
export default ({ funcs, data = [], dataUnits = [], globals = [], entry = null, prefs = {}, usedTypes = null, units = null, bigints = true }) => { // split: one C file per unit sharing a header, link-time constants as externs const split = !!prefs.split; const st = split ? '' : 'static ';
const funcByName = new Map(); for (const f of funcs) if (f) funcByName.set(f.name, f); const proxyCall = funcByName.get('__Porffor_proxy_call'); const funcOf = ref => typeof ref === 'number' ? funcs[ref] : funcByName.get(ref);
// contiguous function indices per unit in source order: fnbase_<unit> + local const unitOf = f => f.internal ? 'builtins' : f.unit ?? 'main'; const funcsByUnit = new Map(); for (const f of funcs) { if (!f) continue; if (!funcsByUnit.has(unitOf(f))) funcsByUnit.set(unitOf(f), []); funcsByUnit.get(unitOf(f)).push(f); } const unitOrder = [ 'builtins', ...(units ?? []).map(u => u.id) ]; for (const u of funcsByUnit.keys()) if (u !== 'main' && !unitOrder.includes(u)) unitOrder.push(u); for (const u of dataUnits) if (u !== 'main' && !unitOrder.includes(u)) unitOrder.push(u); unitOrder.push('main');
const linkFuncs = [], fnBase = {}, linkIdx = []; for (const u of unitOrder) { fnBase[u] = linkFuncs.length; const unitFuncs = funcsByUnit.get(u) ?? []; unitFuncs.sort((a, b) => (a.start ?? 1e9) - (b.start ?? 1e9) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0) || a.index - b.index); for (const f of unitFuncs) { linkIdx[f.index] = linkFuncs.length; linkFuncs.push(f); } }
const syms = [], symsUsed = Object.create(null); const fnSym = f => { let sym = syms[f.index]; if (sym) return sym; sym = `p_${sanitize(unitOf(f))}_${sanitize(String(f.name))}`; while (symsUsed[sym]) sym += '_'; symsUsed[sym] = true; syms[f.index] = sym; return sym; }; const nativeFetchFuncSym = name => { const f = funcByName.get(name); if (!f) throw new Error(`missing native fetch function ${name}`); return fnSym(f); };
// flags are derived here (only consumer), no stored func.flags. coroFlags = FN_* kind, fnFlags byte: // bits 0-2 coroutine kind, 3 callable (has return type), 4 constructor, 5 generator init suspension, 6 sloppy ordinary function const FN_CORO_INIT = 1 << 5; const coroKind = f => f?.async && f?.generator ? FN_ASYNC_GENERATOR : (f?.async ? FN_ASYNC : 0) | (f?.generator ? FN_GENERATOR : 0); const coroFlags = f => coroKind(f) | (f?.coroInit ? FN_CORO_INIT : 0); const isCoro = f => !!(f && (f.async || f.generator)); const needsCoro = f => !!(f && (f.generator || (f.async && f.hasAwait))); const isSyncAsync = f => !!(f && f.async && !f.generator && !needsCoro(f)); const fnFlags = f => f ? (coroFlags(f) | (f.returnType != null ? 1 << 3 : 0) | (f.constr ? 1 << 4 : 0) | (f.constr && !f.strict && !f.internal ? 1 << 6 : 0)) : 0; const jsArg = n => n[N_TYPE] === T.jsval ? rx(n, P_COMMA) : `porf_box_num(${rx(n, P_COMMA)})`; const packArg = n => `porf_pack(${jsArg(n)})`;
// dynamic calls with few args pass them in registers to a per-func thunk const DYN_REGS = 4; const hasParam = (f, name) => f.params.some(p => p.name === name); const dynRegThunk = f => f.indirect && coroKind(f) === 0 && !hasParam(f, '#rest') && !hasParam(f, '#allargs'); const dynRegs = (n = DYN_REGS) => Array.from({ length: n }, (_, j) => `jsval a${j}`); const dynCallParams = n => [ 'jsval fn', 'jsval thisv', ...dynRegs(n) ].join(', '); const dynThunkParams = [ 'u64 ra', 'jsval thisv', ...dynRegs() ].join(', '); let usesDynReg = false;
let usesMath = false; let usesCoro = false; let usesSyncAsync = false; const gcEnabled = prefs.gc !== false; for (const f of funcs) { if (needsCoro(f)) usesCoro = true; if (isSyncAsync(f)) usesSyncAsync = true; } const promiseResolveFunc = funcByName.get('__Porffor_promise_resolve'); const settleAsyncResult = promiseResolveFunc ? (value, promise) => `(void)${fnSym(promiseResolveFunc)}(${value}, ${promise});` : (value, promise) => `porf_promise_settle_direct(${promise}, ${value}, 1);`;
// static layout: one data block per unit, then function records, then name strings const align8 = x => (x + 7) & ~7; const dataOffsets = [], dbase = {}; let off = 16; // 0 reserved (null), small pad for (const u of unitOrder) { dbase[u] = off; for (let i = 0; i < data.length; i++) { if (dataUnits[i] !== u) continue; dataOffsets[i] = off; off += align8(data[i].length); } } const fnrecBase = off; off += linkFuncs.length * 8; const builtinName = name => { const accessor = name.match(/\$([gs]et)$/)?.[1]; if (accessor) name = name.slice(0, -4); const proto = name.indexOf('_prototype_'); const owner = name.indexOf('_', 2); name = proto !== -1 ? name.slice(proto + 11) : owner !== -1 ? name.slice(owner + 1) : name.slice(2); if (name.startsWith('Symbol_')) name = `[Symbol.${name.slice(7)}]`; return accessor ? `${accessor} ${name}` : name; };
// empty names reuse offset 0 (0-length bytestring in the null region) const fnNameSegs = []; const fnNameOff = []; for (const f of linkFuncs) { const name = f.jsName ?? (f.name.startsWith('__') ? builtinName(f.name) : f.name.split('#')[0]); if (name.length === 0) { fnNameOff.push(0); continue; } const bytes = [ name.length & 0xff, (name.length >>> 8) & 0xff, (name.length >>> 16) & 0xff, (name.length >>> 24) & 0xff ]; for (let k = 0; k < name.length; k++) bytes.push(name.charCodeAt(k) & 0xff); fnNameOff.push(off); fnNameSegs.push({ off, bytes }); off += align8(bytes.length); } const staticEnd = off;
// per-unit output and what it references (externs and prototypes in split mode) const unitParts = Object.create(null); const partsOf = u => unitParts[u] ??= { out: [], chunks: [], protos: [], globals: Object.create(null), fnbases: Object.create(null), dbases: Object.create(null) }; let cur = partsOf('main'); let emitBuf = null; const emit = s => { if (emitBuf) { emitBuf.push(s); return; } cur.out.push(s); if (cur.out.length === 4096) { cur.chunks.push(cur.out.join('')); cur.out.length = 0; } };
const fnIdxExpr = f => { // tree-shaken func captured by a closure: traps if ever called if (!f) return '0xffffffffu'; if (!split) return `${linkIdx[f.index]}u`; const u = unitOf(f); cur.fnbases[u] = true; return `(porf_fnbase_${sanitize(u)} + ${linkIdx[f.index] - fnBase[u]}u)`; };
let depth = 1; const ind = () => '\t'.repeat(depth);
// break/continue lower to plain C when targeting the innermost breakable, else goto, // labels are only emitted when goto'd const loopStack = []; const breakStack = []; // try depth at each label so a break or continue leaving a try body can unwind porf_try_depth const labelTry = new Map(); let activeTryDepth = 0; let usedLabels = new Set();
const paren = (s, p, need) => p < need ? `(${s})` : s;
const isNode = node => Array.isArray(node) && typeof node[0] === 'number' && KNames[node[0]] !== undefined && node.length === 6;
// a naked break here would bind to the enclosing C construct (loops/switches rebind it: don't descend) const hasNakedBreak = node => { if (!Array.isArray(node)) return false; if (isNode(node)) { const k = node[N_KIND]; if (k === K.Break) return !node[N_A]; if (k === K.Loop || k === K.Switch || k === K.TypeSwitch) return false; return hasNakedBreak(node[N_A]) || hasNakedBreak(node[N_B]) || hasNakedBreak(node[N_C]); } return node.some(hasNakedBreak); };
const hasRawC = node => { if (!Array.isArray(node)) return false; if (isNode(node)) { if (node[N_KIND] === K.RawC) return true; return hasRawC(node[N_A]) || hasRawC(node[N_B]) || hasRawC(node[N_C]); } return node.some(hasRawC); };
// code after these in a list is unreachable const TERMINATOR_KINDS = new Set([ K.Return, K.Throw, K.ThrowNew, K.Unreachable, K.Break, K.Continue ]);
const renderExpr = node => { switch (node[N_KIND]) { case K.Const: { const t = node[N_TYPE], v = node[N_A]; if (t === T.jsval) return [`porf_box_num(${f64Lit(v)})`, P_POSTFIX]; // number jsval = its f64 bits if (t === T.f64) return [f64Lit(v), v < 0 ? P_UNARY : P_PRIM]; if (t === T.i64) return [`${v}ll`, v < 0 ? P_UNARY : P_PRIM]; if (t === T.u64) return [`${v}ull`, P_PRIM]; if (t === T.u32 || t === T.ptr) return [`${v >>> 0}u`, P_PRIM]; return [String(v | 0), v < 0 ? P_UNARY : P_PRIM]; }
case K.JvConst: return [`porf_box((f64)${node[N_B] >>> 0}u, ${node[N_A]})`, P_POSTFIX];
case K.DataRef: { const id = node[N_A]; if (!split) return [`${dataOffsets[id]}u`, P_PRIM]; const u = dataUnits[id]; cur.dbases[u] = true; return [`(porf_dbase_${sanitize(u)} + ${dataOffsets[id] - dbase[u]}u)`, P_PRIM]; }
case K.FuncIdx: return [fnIdxExpr(funcOf(node[N_A])), P_PRIM]; case K.FuncRec: { const f = funcOf(node[N_A]); return [split ? `(porf_fnrecs + ${fnIdxExpr(f)} * 8u)` : `${fnrecBase + linkIdx[f.index] * 8}u`, P_PRIM]; }
case K.Global: cur.globals[node[N_A]] = true; case K.Local: return [sanitize(node[N_A]), P_PRIM];
case K.Bin: { const op = node[N_A], t = node[N_B][N_TYPE] === T.none ? node[N_TYPE] : node[N_B][N_TYPE]; // special spellings if (op === 'rotl' || op === 'rotr') { const fn = (t === T.i64 || t === T.u64) ? `porf_${op}64` : `porf_${op}32`; return [`${fn}(${rx(node[N_B], P_COMMA)}, ${rx(node[N_C], P_COMMA)})`, P_POSTFIX]; } if (op === 'min' || op === 'max' || op === 'copysign') { usesMath = true; const fn = op === 'copysign' ? 'copysign' : `porf_f64_${op}`; return [`${fn}(${rx(node[N_B], P_COMMA)}, ${rx(node[N_C], P_COMMA)})`, P_POSTFIX]; } if (op === '%' && t === T.f64) { usesMath = true; return [`fmod(${rx(node[N_B], P_COMMA)}, ${rx(node[N_C], P_COMMA)})`, P_POSTFIX]; } if ((op === '==' || op === '!=') && (node[N_B][N_TYPE] === T.jsval || node[N_C][N_TYPE] === T.jsval)) { const eq = `porf_jv_eq(${rx(node[N_B], P_COMMA)}, ${rx(node[N_C], P_COMMA)})`; return [op === '==' ? eq : `!${eq}`, op === '==' ? P_POSTFIX : P_UNARY]; } const prec = BIN_PREC[op]; const ct = CT[node[N_B][N_TYPE]] ?? CT[node[N_TYPE]]; let l = rx(node[N_B], prec); let r = rx(node[N_C], prec + 1); if (op === '<<' || op === '>>') { if (node[N_B][N_KIND] === K.Bin && BIN_PREC[node[N_B][N_A]] === P_ADD) l = `(${l})`; if (node[N_C][N_KIND] === K.Bin && BIN_PREC[node[N_C][N_A]] === P_ADD) r = `(${r})`; } // shifts: C UB on overshift; IR guarantees masked shift via codegen return [`${l} ${op} ${r}`, prec]; }
case K.Un: { const op = node[N_A], v = node[N_B]; switch (op) { case 'neg': return [`-${rx(v, P_CAST)}`, P_UNARY]; case '!': return [`!${rx(v, P_UNARY)}`, P_UNARY]; case '~': return [`~${rx(v, P_UNARY)}`, P_UNARY]; case 'abs': usesMath = true; return [`fabs(${rx(v, P_COMMA)})`, P_POSTFIX]; case 'floor': case 'ceil': case 'trunc': case 'sqrt': usesMath = true; return [`${op}(${rx(v, P_COMMA)})`, P_POSTFIX]; case 'round': usesMath = true; return [`porf_round(${rx(v, P_COMMA)})`, P_POSTFIX]; case 'nearest': usesMath = true; return [`porf_nearest(${rx(v, P_COMMA)})`, P_POSTFIX]; case 'clz': return [`porf_clz32(${rx(v, P_COMMA)})`, P_POSTFIX]; case 'ctz': return [`porf_ctz32(${rx(v, P_COMMA)})`, P_POSTFIX]; case 'popcnt': return [`__builtin_popcount(${rx(v, P_COMMA)})`, P_POSTFIX]; } throw new Error(`render: unknown unary op ${op}`); }
case K.Select: return [`${rx(node[N_A], P_LOR)} ? ${rx(node[N_B], P_TERNARY)} : ${rx(node[N_C], P_TERNARY)}`, P_TERNARY];
case K.Convert: { const to = node[N_TYPE], from = node[N_A], v = node[N_B], flags = node[N_C]; // f64 -> int without range knowledge: saturate (JS ToInt semantics live above this) if (from === T.f64 && (to === T.i32 || to === T.u32 || to === T.ptr) && !(flags & CONVERT_RANGE_KNOWN)) { return [`${to === T.i32 ? 'porf_f64_to_i32' : 'porf_f64_to_u32'}(${rx(v, P_COMMA)})`, P_POSTFIX]; } if (to === T.f64 && (from === T.u32 || from === T.ptr) && !(flags & CONVERT_SIGNED)) { return [`(f64)${rx(v, P_CAST)}`, P_CAST]; } return [`(${CT[to]})${rx(v, P_CAST)}`, P_CAST]; }
case K.Reinterpret: if (node[N_B] === 'bitsToF32') return [`porf_bits_to_f32(${rx(node[N_A], P_COMMA)})`, P_POSTFIX]; if (node[N_B] === 'f32ToBits') return [`porf_f32_to_bits(${rx(node[N_A], P_COMMA)})`, P_POSTFIX]; return [node[N_TYPE] === T.f64 ? `porf_bits_to_f64(${rx(node[N_A], P_COMMA)})` : `porf_f64_to_bits(${rx(node[N_A], P_COMMA)})`, P_POSTFIX];
case K.Canon: return [`porf_canon(${rx(node[N_A], P_COMMA)})`, P_POSTFIX];
case K.Box: { const v = node[N_A], tExpr = node[N_B]; const payload = v[N_TYPE] === T.jsval ? `(${rx(v, P_POSTFIX)}.val)` : rx(v, P_COMMA); if (tExpr[N_KIND] === K.Const) { const tid = tExpr[N_A]; if (tid === TYPES.number) return [`porf_box_num(${payload})`, P_POSTFIX]; return [`porf_box(${payload}, ${tid})`, P_POSTFIX]; } return [`porf_box(${payload}, ${rx(tExpr, P_COMMA)})`, P_POSTFIX]; } case K.JvType: return [`porf_jv_type(${rx(node[N_A], P_COMMA)})`, P_POSTFIX]; case K.JvNum: return [`(${rx(node[N_A], P_POSTFIX)}.val)`, P_POSTFIX]; case K.JvPtr: return [`(u32)${rx(node[N_A], P_POSTFIX)}.val`, P_CAST]; case K.JvBits: return [`porf_pack(${rx(node[N_A], P_COMMA)})`, P_POSTFIX]; case K.JvFromBits: return [`porf_unpack(${rx(node[N_A], P_COMMA)})`, P_POSTFIX]; case K.JvIsNum: return [`porf_jv_is_num(${rx(node[N_A], P_COMMA)})`, P_POSTFIX]; case K.Eq: return [`${node[N_A] === 2 ? 'porf_loose_eq_raw' : node[N_A] ? 'porf_strict_eq' : 'porf_loose_eq'}(${jsArg(node[N_B])}, ${jsArg(node[N_C])})`, P_POSTFIX]; case K.Add: return [`porf_add(${jsArg(node[N_A])}, ${jsArg(node[N_B])})`, P_POSTFIX]; case K.Cmp: return [`porf_cmp(${jsArg(node[N_A])}, ${jsArg(node[N_B])})`, P_POSTFIX]; case K.JvTruthy: return [`porf_truthy(${jsArg(node[N_A])})`, P_POSTFIX]; case K.JvFalsy: return [`porf_falsy(${jsArg(node[N_A])})`, P_POSTFIX]; case K.JvNullish: return [`porf_nullish(${jsArg(node[N_A])})`, P_POSTFIX];
case K.Load: { const ctype = node[N_A]; const [off, unaligned] = node[N_C]; const addr = `MEM + ${rx(node[N_B], P_ADD)}${off ? ` + ${off}u` : ''}`; if (unaligned) { // signed unaligned: load unsigned width, cast (u8/i8 are always aligned) if (ctype === 'i16') return [`(int16_t)porf_load_un_u16(${addr})`, P_CAST]; if (ctype === 'i32') return [`(i32)porf_load_un_u32(${addr})`, P_CAST]; if (ctype === 'i64') return [`(i64)porf_load_un_u64(${addr})`, P_CAST]; if (ctype === 'jsval') return [`porf_unpack(porf_load_un_u64(${addr}))`, P_POSTFIX]; return [`porf_load_un_${ctype}(${addr})`, P_POSTFIX]; } if (ctype === 'jsval') return [`porf_unpack(*(jsbits*)(${addr}))`, P_POSTFIX]; return [`*(${ctype === 'i8' ? 'int8_t' : ctype === 'i16' ? 'int16_t' : ctype}*)(${addr})`, P_UNARY]; }
case K.Call: { if (node[N_A] === '__Porffor_coroutine_resume' || node[N_A] === '__Porffor_coroutine_value') usesCoro = true; const f = funcOf(node[N_A]); if (f) cur.protos[f.index] = f; // direct call to a coroutine starts it instead of running the body: split args into the invocation shape if (f && isCoro(f)) { let callee = 'JV_UNDEFINED', env = '0', thisv = 'JV_UNDEFINED', newtv = 'JV_UNDEFINED'; const rawArgv = Array.isArray(node[N_C]) ? node[N_C] : null; const argv = rawArgv ?? []; node[N_B].forEach((a, i) => { const s = rx(a, P_COMMA), pn = f.params[i]?.name; if (pn === '#callee') callee = s; else if (pn === '#env') env = s; else if (pn === '#this') thisv = s; else if (pn === '#newtarget') newtv = s; else if (!rawArgv) argv.push(a); }); // static (envless) generators still know their function: instances get its .prototype if (callee === 'JV_UNDEFINED' && f.generator && !f.params.some(p => p.name === '#env')) callee = `porf_box((f64)(${split ? `porf_fnrecs + ${fnIdxExpr(f)} * 8u` : `${fnrecBase + linkIdx[f.index] * 8}u`}), ${TYPES.function})`; const argvArr = argv.length ? `(jsbits[]){ ${argv.map(a => packArg(a)).join(', ')} }` : '(jsbits[]){JV_UNDEFINED_BITS}'; return [needsCoro(f) ? `porf_coro_start(${coroFlags(f)}u, ${fnIdxExpr(f)}, ${callee}, ${env}, ${thisv}, ${newtv}, ${argv.length}, ${argvArr})` : `porf_async_call_sync(${fnIdxExpr(f)}, ${callee}, ${env}, ${thisv}, ${newtv}, ${argv.length}, ${argvArr})`, P_POSTFIX]; } const name = f ? fnSym(f) : sanitize(String(node[N_A])); const args = node[N_B].map(a => rx(a, P_COMMA)).join(', '); return [`${name}(${args})`, P_POSTFIX]; }
case K.CallDynamic: { const [args, newTarget, spreadArr] = node[N_C]; const newt = newTarget ? rx(newTarget, P_COMMA) : 'JV_UNDEFINED'; if (spreadArr) { return [`porf_call_dynamic_arr(${rx(node[N_A], P_COMMA)}, ${rx(node[N_B], P_COMMA)}, ${newt}, ${rx(spreadArr, P_COMMA)})`, P_POSTFIX]; } if (!newTarget && args.length <= DYN_REGS) { usesDynReg = true; return [`porf_call${args.length}(${[ node[N_A], node[N_B] ].map(a => rx(a, P_COMMA)).concat(args.map(jsArg)).join(', ')})`, P_POSTFIX]; } const argv = args.length === 0 ? '(jsbits[]){JV_UNDEFINED_BITS}' : `(jsbits[]){ ${args.map(packArg).join(', ')} }`; return [`porf_call_dynamic(${rx(node[N_A], P_COMMA)}, ${rx(node[N_B], P_COMMA)}, ${newt}, ${args.length}, ${argv})`, P_POSTFIX]; }
case K.Await: case K.Yield: throw new Error(`render: ${KNames[node[N_KIND]]} outside a coroutine body`);
case K.Alloc: return [`porf_alloc(${rx(node[N_A], P_COMMA)}, ${node[N_B]}u)`, P_POSTFIX];
case K.ArrGet: return [`porf_arr_get(${rx(node[N_A], P_COMMA)}, ${rx(node[N_B], P_COMMA)})`, P_POSTFIX]; case K.LenGet: return [`*(i32*)(MEM + ${rx(node[N_A], P_ADD)})`, P_UNARY];
default: throw new Error(`render: cannot render ${KNames[node[N_KIND]]} as expression`); } };
const rx = (node, need) => { const [code, prec] = renderExpr(node); return paren(code, prec, need); };
const renderStmts = stmts => { for (let i = 0; i < stmts.length; i++) { const s = stmts[i]; renderStmt(s); if (s != null && isNode(s) && TERMINATOR_KINDS.has(s[N_KIND]) && !stmts.slice(i + 1).some(hasRawC)) return; } };
const renderStmt = node => { if (node == null) return; switch (node[N_KIND]) { case K.Assign: if (coro && isSuspend(node[N_B])) return renderSuspend(node[N_A], node[N_B]); if (node[N_A][N_KIND] === K.Global) cur.globals[node[N_A][N_A]] = true; emit(`${ind()}${sanitize(node[N_A][N_A])} = ${rx(node[N_B], P_COMMA)};\n`); return;
case K.Store: { const ctype = node[N_A]; const [off, unaligned, value] = node[N_C]; const addr = `MEM + ${rx(node[N_B], P_ADD)}${off ? ` + ${off}u` : ''}`; if (unaligned) { const un = { i16: 'u16', i32: 'u32', i64: 'u64', jsval: 'u64' }[ctype] ?? ctype; emit(`${ind()}porf_store_un_${un}(${addr}, ${ctype === 'jsval' ? packArg(value) : rx(value, P_COMMA)});\n`); } else if (ctype === 'jsval') emit(`${ind()}*(jsbits*)(${addr}) = ${packArg(value)};\n`); else emit(`${ind()}*(${ctype === 'i8' ? 'int8_t' : ctype === 'i16' ? 'int16_t' : ctype}*)(${addr}) = ${rx(value, P_COMMA)};\n`); return; }
case K.MemCopy: { const [bytes, mayOverlap] = node[N_C]; emit(`${ind()}${mayOverlap ? 'memmove' : 'memcpy'}(MEM + ${rx(node[N_A], P_ADD)}, MEM + ${rx(node[N_B], P_ADD)}, ${rx(bytes, P_COMMA)});\n`); return; }
case K.MemFill: emit(`${ind()}memset(MEM + ${rx(node[N_A], P_ADD)}, ${rx(node[N_B], P_COMMA)}, ${rx(node[N_C], P_COMMA)});\n`); return;
case K.If: { emit(`${ind()}if (${rx(node[N_A], P_COMMA)}) {\n`); depth++; renderStmts(node[N_B]); depth--; if (node[N_C] && node[N_C].length) { emit(`${ind()}} else {\n`); depth++; renderStmts(node[N_C]); depth--; } emit(`${ind()}}\n`); return; }
case K.Loop: { const cond = node[N_A], update = node[N_B]; const [stmts, label] = node[N_C]; const updateC = update == null ? null : update[N_KIND] === K.Assign ? `${sanitize(update[N_A][N_A])} = ${rx(update[N_B], P_COMMA)}` : rx(update, P_COMMA); // resume through the header, a second loop entry would make the cfg irreducible const region = coro && hasSuspend(stmts) ? { id: coro.regions.length, cases: [] } : null; if (region) { coro.regions.push(region); emit(`${ind()}_porf_t${region.id}:\n`); emit(`${ind()}for (;; ${updateC ?? ''}) {\n`); emit(`${ind()}\tif (porf_fr->resume) switch (porf_fr->resume) {\x00${region.id}\x00}\n`); if (cond) emit(`${ind()}\telse if (!(${rx(cond, P_COMMA)})) break;\n`); coro.path.push(region); } else if (update) emit(`${ind()}for (; ${cond ? rx(cond, P_COMMA) : ''}; ${updateC}) {\n`); else if (cond) emit(`${ind()}while (${rx(cond, P_COMMA)}) {\n`); else emit(`${ind()}while (1) {\n`); loopStack.push(label); breakStack.push(label); if (label) labelTry.set(label, activeTryDepth); depth++; renderStmts(stmts); if (region) coro.path.pop(); if (label && usedLabels.has(label + '_c')) emit(`${ind()}${sanitize(label)}_c:;\n`); depth--; breakStack.pop(); loopStack.pop(); emit(`${ind()}}\n`); if (label && usedLabels.has(label + '_b')) emit(`${ind()}${sanitize(label)}_b:;\n`); return; }
case K.Break: { const target = labelTry.get(node[N_A]); if (activeTryDepth > target) emit(`${ind()}porf_try_depth -= ${activeTryDepth - target};\n`); if (node[N_A] && node[N_A] !== breakStack[breakStack.length - 1]) { usedLabels.add(node[N_A] + '_b'); emit(`${ind()}goto ${sanitize(node[N_A])}_b;\n`); } else emit(`${ind()}break;\n`); return; }
case K.Continue: { const target = labelTry.get(node[N_A]); if (activeTryDepth > target) emit(`${ind()}porf_try_depth -= ${activeTryDepth - target};\n`); if (node[N_A] && node[N_A] !== loopStack[loopStack.length - 1]) { usedLabels.add(node[N_A] + '_c'); emit(`${ind()}goto ${sanitize(node[N_A])}_c;\n`); } else emit(`${ind()}continue;\n`); return; }
case K.Block: { if (node[N_A].length === 0 && !node[N_B]) return; if (node[N_B]) labelTry.set(node[N_B], activeTryDepth); emit(`${ind()}{\n`); depth++; renderStmts(node[N_A]); depth--; emit(`${ind()}}\n`); if (node[N_B] && usedLabels.has(node[N_B] + '_b')) emit(`${ind()}${sanitize(node[N_B])}_b:;\n`); return; }
case K.Switch: case K.TypeSwitch: { const isType = node[N_KIND] === K.TypeSwitch; const subj = node[N_A]; const kept = []; for (const [values, stmts, fallthrough] of node[N_B]) { const vals = isType && usedTypes ? values.filter(v => usedTypes.has(v)) : values; if (vals.length !== 0) kept.push([ vals, stmts, fallthrough ]); } const def = node[N_C] && node[N_C].length ? node[N_C] : null;
// 0/1 surviving cases: if/else instead of switch. blocked by a naked break (must // bind to the switch) and re-evaluation hazards: dropping the subject needs it // effect-free, repeating it (multi-value condition) needs it trivial const subjTrivial = subj[N_KIND] === K.Local || subj[N_KIND] === K.Global || subj[N_KIND] === K.Const; if (kept.length <= 1 && !hasNakedBreak(kept.length ? kept[0][1] : null) && !hasNakedBreak(def) && (kept.length === 1 ? kept[0][0].length === 1 || subjTrivial : (subj[N_FX] & (FX.call | FX.writeMem | FX.writeLocal)) === 0)) { if (kept.length === 0) { if (def) renderStmts(def); return; } const [ vals, stmts, fallthrough ] = kept[0]; const subjS = () => isType && subj[N_TYPE] === T.jsval ? `porf_jv_type(${rx(subj, P_COMMA)})` : rx(subj, P_EQ + 1); emit(`${ind()}if (${vals.map(v => `${subjS()} == ${v}`).join(' || ')}) {\n`); depth++; renderStmts(stmts); depth--; if (def && !fallthrough) { emit(`${ind()}} else {\n`); depth++; renderStmts(def); depth--; } emit(`${ind()}}\n`); if (def && fallthrough) renderStmts(def); return; }
const subjCode = isType && subj[N_TYPE] === T.jsval ? `porf_jv_type(${rx(subj, P_COMMA)})` : rx(subj, P_COMMA); emit(`${ind()}switch (${subjCode}) {\n`); breakStack.push(null); depth++; for (const [vals, stmts, fallthrough] of kept) { emit(ind()); for (const v of vals) emit(`case ${v}: `); emit('{\n'); depth++; renderStmts(stmts); depth--; emit(`${ind()}}\n`); if (!fallthrough) emit(`${ind()}break;\n`); } if (def) { emit(`${ind()}default:\n${ind()}{\n`); depth++; renderStmts(def); depth--; emit(`${ind()}}\n`); } depth--; breakStack.pop(); emit(`${ind()}}\n`); return; }
case K.Return: if (activeTryDepth !== 0 && node[N_A]) { emit(`${ind()}{\n`); depth++; emit(`${ind()}${CT[node[N_A][N_TYPE]]} _return = ${rx(node[N_A], P_COMMA)};\n`); emit(`${ind()}porf_try_depth -= ${activeTryDepth};\n`); emit(`${ind()}return _return;\n`); depth--; emit(`${ind()}}\n`); return; } if (activeTryDepth !== 0) emit(`${ind()}porf_try_depth -= ${activeTryDepth};\n`); emit(node[N_A] ? `${ind()}return ${rx(node[N_A], P_COMMA)};\n` : `${ind()}return;\n`); return;
case K.Unreachable: emit(`${ind()}porf_unreachable(${node[N_A] ? JSON.stringify(String(node[N_A])) : '0'});\n`); return;
case K.Try: { // resume re-enters the try from the top to re-arm its jmp_buf const region = coro && hasSuspend(node[N_A]) ? { id: coro.regions.length, cases: [] } : null; emit(`${ind()}{\n`); depth++; if (region) { coro.regions.push(region); emit(`${ind()}_porf_t${region.id}:;\n`); } emit(`${ind()}porf_try_depth++;\n`); emit(`${ind()}if (_setjmp(porf_try_ensure()[porf_try_depth - 1]) == 0) {\n`); activeTryDepth++; depth++; if (region) { emit(`${ind()}if (porf_fr->resume) switch (porf_fr->resume) {\x00${region.id}\x00}\n`); coro.path.push(region); } renderStmts(node[N_A]); if (region) coro.path.pop(); activeTryDepth--; emit(`${ind()}porf_try_depth--;\n`); depth--; emit(`${ind()}} else {\n`); depth++; emit(`${ind()}porf_try_depth--;\n`); emit(`${ind()}${coro ? '' : 'jsval '}${sanitize(node[N_B])} = porf_exception;\n`); renderStmts(node[N_C]); depth--; emit(`${ind()}}\n`); depth--; emit(`${ind()}}\n`); return; }
case K.Throw: emit(`${ind()}porf_throw(${rx(node[N_A], P_COMMA)});\n`); return;
case K.ThrowNew: emit(`${ind()}porf_throw_new(${node[N_A]}, ${rx(node[N_B], P_COMMA)});\n`); return;
case K.GcBarrier: emit(`${ind()}porf_gc_barrier(${rx(node[N_A], P_COMMA)}, ${rx(node[N_B], P_COMMA)});\n`); return;
case K.ArrSet: emit(`${ind()}porf_arr_set(${rx(node[N_A], P_COMMA)}, ${rx(node[N_B], P_COMMA)}, ${jsArg(node[N_C])});\n`); return;
case K.ArrLenSet: emit(`${ind()}porf_arr_set_len(${rx(node[N_A], P_COMMA)}, ${rx(node[N_B], P_COMMA)});\n`); return;
case K.LenSet: emit(`${ind()}*(i32*)(MEM + ${rx(node[N_A], P_ADD)}) = ${rx(node[N_B], P_COMMA)};\n`); return;
case K.RawC: emit(`${ind()}${node[N_A]}${node[N_B] ? ';' : ''}\n`); return;
default: { if ((node[N_FX] & (FX.call | FX.writeMem | FX.writeLocal)) !== 0) { emit(`${ind()}${node[N_TYPE] !== T.none ? '(void)' : ''}${rx(node, node[N_TYPE] !== T.none ? P_CAST : P_COMMA)};\n`); } return; } } };
const invokeArgs = (f, v) => { const pre = [], args = []; let j = 0; for (const p of f.params) { if (p.name === '#env') { args.push(v.env); continue; } if (p.name === '#this') { args.push(v.thisv); continue; } if (p.name === '#newtarget') { args.push(v.newtv); continue; } if (p.name === '#callee') { args.push(v.callee); continue; } if (p.name === '#allargs') { pre.push(`u32 _aa = porf_arr_new(${v.argc}, ${v.argc} > 4 ? ${v.argc} : 4);`); pre.push(`for (i32 _k = 0; _k < ${v.argc}; _k++) porf_arr_set(_aa, (u32)_k, porf_unpack(${v.argv}[_k]));`); args.push(`porf_box((f64)_aa, ${TYPES.array})`); continue; } if (p.name === '#rest') { // pack remaining argv into an array (twin helpers; die at step 3) pre.push(`u32 _rest = porf_arr_new(0, ${v.argc} > ${j} ? ${v.argc} - ${j} : 4);`); pre.push(`for (i32 _k = ${j}; _k < ${v.argc}; _k++) (void)porf_arr_push(_rest, porf_unpack(${v.argv}[_k]));`); args.push(`porf_box((f64)_rest, ${TYPES.array})`); continue; } const src = v.arg(j); if (p.type === T.f64) args.push(`(${src}).val`); else if (p.type === T.i64 || p.type === T.u64) args.push(`(i64)(${src}).val`); else if (p.type === T.i32 || p.type === T.u32 || p.type === T.ptr) args.push(`(i32)(${src}).val`); else args.push(src); j++; } return { pre, args }; };
// stackless coroutines: suspend spills live locals to the frame and returns, resume reloads + gotos const isSuspend = n => n[N_KIND] === K.Await || n[N_KIND] === K.Yield; let suspendMemo = null; const hasSuspend = node => { if (!Array.isArray(node)) return false; if (!isNode(node)) { for (let i = 0; i < node.length; i++) if (hasSuspend(node[i])) return true; return false; } const k = node[N_KIND]; if ((node[N_FX] & FX.call) === 0 && k !== K.If && k !== K.Loop && k !== K.Block && k !== K.Switch && k !== K.TypeSwitch && k !== K.Try) return false; let r = suspendMemo.get(node); if (r === undefined) { r = k === K.Await || k === K.Yield || hasSuspend(node[N_A]) || hasSuspend(node[N_B]) || hasSuspend(node[N_C]); suspendMemo.set(node, r); } return r; };
const kidsOf = (v, out) => { if (!Array.isArray(v)) return; if (isNode(v)) out.push(v); else for (let i = 0; i < v.length; i++) kidsOf(v[i], out); }; const mapVal = (v, fn) => !Array.isArray(v) ? v : isNode(v) ? fn(v) : v.map(x => mapVal(x, fn)); const mapKids = (node, fn) => { const copy = node.slice(); for (let i = N_A; i <= N_C; i++) copy[i] = mapVal(node[i], fn); return copy; }; const isStable = n => { const k = n[N_KIND]; return k === K.Const || k === K.JvConst || k === K.DataRef || k === K.FuncIdx || k === K.FuncRec || k === K.Local; };
let coroTmps = null, coroTmpN = 0, coroLocals = null; const coroTmp = type => { let name; do name = `#coro${coroTmpN++}`; while (name in coroLocals); coroTmps[name] = type; coroLocals[name] = true; return [K.Local, type, FX.none, name, 0, 0]; }; const assignTo = (target, value) => [K.Assign, T.none, FX.writeLocal | value[N_FX], target, value, 0]; const suspendOf = (node, arg) => [node[N_KIND], T.jsval, FX.call, arg, node[N_B], 0];
const lowerExpr = (node, out) => { if (!hasSuspend(node)) return node; const k = node[N_KIND]; if (k === K.Await || k === K.Yield) { const arg = lowerExpr(node[N_A], out); const t = coroTmp(T.jsval); out.push(assignTo(t, suspendOf(node, arg))); return t; }
if (k === K.Select) { const cond = lowerExpr(node[N_A], out); const t = coroTmp(node[N_TYPE]); const a = [], b = []; a.push(assignTo(t, lowerExpr(node[N_B], a))); b.push(assignTo(t, lowerExpr(node[N_C], b))); out.push([K.If, T.none, cond[N_FX], cond, a, b]); return t; } if (k === K.Bin && (node[N_A] === '&&' || node[N_A] === '||')) { const l = lowerExpr(node[N_B], out); const t = coroTmp(T.i32); const rest = []; const r = lowerExpr(node[N_C], rest); rest.push(assignTo(t, [K.Bin, T.i32, r[N_FX], '!=', r, [K.Const, r[N_TYPE], FX.none, 0, 0, 0]])); const and = node[N_A] === '&&'; out.push(assignTo(t, [K.Const, T.i32, FX.none, and ? 0 : 1, 0, 0])); out.push([K.If, T.none, l[N_FX], and ? l : [K.Un, T.i32, l[N_FX], '!', l, 0], rest, null]); return t; }
// spill operands evaluated before the last suspend const kids = []; kidsOf(node[N_A], kids); kidsOf(node[N_B], kids); kidsOf(node[N_C], kids); let last = -1; for (let i = 0; i < kids.length; i++) if (hasSuspend(kids[i])) last = i; let at = 0; return mapKids(node, kid => { const i = at++; if (i > last) return kid; const x = lowerExpr(kid, out); if (i === last || isStable(x)) return x; const t = coroTmp(x[N_TYPE]); out.push(assignTo(t, x)); return t; }); };
const lowerStmts = stmts => { const out = []; for (let i = 0; i < stmts.length; i++) lowerStmt(stmts[i], out); return out; }; const lowerStmt = (s, out) => { if (s == null || !hasSuspend(s)) { out.push(s); return; } switch (s[N_KIND]) { case K.If: { const cond = lowerExpr(s[N_A], out); out.push([K.If, T.none, s[N_FX], cond, lowerStmts(s[N_B]), s[N_C] ? lowerStmts(s[N_C]) : null]); return; } case K.Block: out.push([K.Block, T.none, s[N_FX], lowerStmts(s[N_A]), s[N_B], 0]); return; case K.Try: out.push([K.Try, T.none, s[N_FX], lowerStmts(s[N_A]), s[N_B], lowerStmts(s[N_C])]); return; case K.Switch: case K.TypeSwitch: { const subj = lowerExpr(s[N_A], out); const cases = s[N_B].map(c => { const x = c.slice(); x[1] = lowerStmts(c[1]); return x; }); out.push([s[N_KIND], T.none, s[N_FX], subj, cases, s[N_C] ? lowerStmts(s[N_C]) : null]); return; } case K.Loop: { const cond = s[N_A], update = s[N_B]; const [stmts, label] = s[N_C]; if (!hasSuspend(cond) && !hasSuspend(update)) { out.push([K.Loop, T.none, s[N_FX], cond, update, [lowerStmts(stmts), label]]); return; } // suspend in the header: test and update move into the body const first = coroTmp(T.i32); out.push(assignTo(first, [K.Const, T.i32, FX.none, 1, 0, 0])); const body = []; if (update) { const u = []; lowerStmt(update, u); body.push([K.If, T.none, FX.none, [K.Un, T.i32, FX.none, '!', first, 0], u, null]); } body.push(assignTo(first, [K.Const, T.i32, FX.none, 0, 0, 0])); if (cond) { const c = lowerExpr(cond, body); body.push([K.If, T.none, c[N_FX], [K.Un, T.i32, c[N_FX], '!', c, 0], [ [K.Break, T.none, FX.none, label, 0, 0] ], null]); } for (const x of lowerStmts(stmts)) body.push(x); out.push([K.Loop, T.none, FX.none, null, null, [body, label]]); return; } case K.Assign: { const v = s[N_B]; if (isSuspend(v) && s[N_A][N_KIND] === K.Local) { out.push(assignTo(s[N_A], suspendOf(v, lowerExpr(v[N_A], out)))); return; } out.push(assignTo(s[N_A], lowerExpr(v, out))); return; } default: { const x = lowerExpr(s, out); if (x[N_KIND] !== K.Local) out.push(x); return; } } }; const collectCatchNames = node => { if (!Array.isArray(node)) return; if (isNode(node)) { const k = node[N_KIND]; if (k === K.Try) coroLocals[node[N_B]] ??= T.jsval; if (k === K.If || k === K.Loop || k === K.Block || k === K.Switch || k === K.TypeSwitch || k === K.Try) { collectCatchNames(node[N_A]); collectCatchNames(node[N_B]); collectCatchNames(node[N_C]); } return; } for (let i = 0; i < node.length; i++) collectCatchNames(node[i]); };
// locals live across some suspend, null = all (RawC) const coroLive = body => { const saved = new Set(); let all = false; const reads = (node, set) => { if (!Array.isArray(node)) return; if (isNode(node)) { const k = node[N_KIND]; if (k === K.Local) { set.add(node[N_A]); return; } if (k === K.RawC) all = true; reads(node[N_A], set); reads(node[N_B], set); reads(node[N_C], set); return; } for (let i = 0; i < node.length; i++) reads(node[i], set); }; const union = (a, b) => { const out = new Set(a); for (const x of b) out.add(x); return out; }; const same = (a, b) => { if (a.size !== b.size) return false; for (const x of a) if (!b.has(x)) return false; return true; }; const withLabel = (map, label, set) => { if (!label) return map; const out = new Map(map); out.set(label, set); return out; };
const list = (stmts, out, ctx) => { let live = out; for (let i = stmts.length - 1; i >= 0; i--) live = stmt(stmts[i], live, ctx); return live; }; const stmt = (s, out, ctx) => { if (s == null) return out; switch (s[N_KIND]) { case K.Assign: { const live = new Set(out); if (s[N_A][N_KIND] === K.Local) live.delete(s[N_A][N_A]); if (isSuspend(s[N_B])) { for (const x of live) saved.add(x); for (const x of ctx.extra) saved.add(x); } reads(s[N_B], live); return live; } case K.If: { const live = union(list(s[N_B], out, ctx), s[N_C] ? list(s[N_C], out, ctx) : out); reads(s[N_A], live); return live; } case K.Block: return list(s[N_A], out, { ...ctx, brk: withLabel(ctx.brk, s[N_B], out) }); case K.Loop: { const cond = s[N_A], update = s[N_B]; const [body, label] = s[N_C]; let head = new Set(cond ? out : []); if (cond) reads(cond, head); for (;;) { const cont = update ? stmt(update, head, ctx) : head; const inner = { ...ctx, brk: withLabel(ctx.brk, label, out), cont: withLabel(ctx.cont, label, cont), innerBrk: out, innerCont: cont }; const next = union(cond ? out : [], list(body, cont, inner)); if (cond) reads(cond, next); if (same(next, head)) return head; head = next; } } case K.Switch: case K.TypeSwitch: { const inner = { ...ctx, innerBrk: out }; const def = s[N_C] && s[N_C].length ? s[N_C] : null; // fallthrough may skip dropped cases: flow into everything after let after = def ? list(def, out, inner) : out; const live = new Set(after); after = union(after, out); for (let i = s[N_B].length - 1; i >= 0; i--) { const c = s[N_B][i]; const caseIn = list(c[1], c[2] ? after : out, inner); for (const x of caseIn) live.add(x); after = union(after, caseIn); } reads(s[N_A], live); return live; } case K.Try: { const catchIn = new Set(list(s[N_C], out, ctx)); catchIn.delete(s[N_B]); return union(list(s[N_A], out, { ...ctx, extra: union(ctx.extra, catchIn) }), catchIn); } case K.Break: { const to = s[N_A] ? ctx.brk.get(s[N_A]) : ctx.innerBrk; if (to == null) all = true; return to ?? out; } case K.Continue: { const to = s[N_A] ? ctx.cont.get(s[N_A]) : ctx.innerCont; if (to == null) all = true; return to ?? out; } case K.Return: { const live = new Set(); reads(s[N_A], live); return live; } case K.Throw: case K.ThrowNew: { const live = new Set(ctx.extra); reads(s[N_A], live); reads(s[N_B], live); return live; } case K.Unreachable: return new Set(); default: { const live = new Set(out); reads(s, live); return live; } } };
list(body, new Set(), { brk: new Map(), cont: new Map(), innerBrk: null, innerCont: null, extra: new Set() }); return all ? null : saved; };
// regions: the function, then each loop/try holding a suspend. path: regions enclosing the current stmt let coro = null; const coroFrameBytes = []; const renderSuspend = (target, node) => { const k = ++coro.points; const path = coro.path; path[path.length - 1].cases.push(`case ${k}: goto _porf_r${k};`); for (let i = 0; i < path.length - 1; i++) path[i].cases.push(`case ${k}: goto _porf_t${path[i + 1].id};`);
const t = sanitize(target[N_A]); const leave = `porf_fr->resume = ${k};${activeTryDepth ? ` porf_try_depth -= ${activeTryDepth};` : ''} goto _porf_save;`; emit(`${ind()}porf_fr->channel = ${rx(node[N_A], P_COMMA)};${node[N_KIND] === K.Await ? ' porf_fr->awaiting = 1;' : ''} ${leave}\n`); emit(`${ind()}_porf_r${k}: ${t} = porf_coro_received(porf_fr, ${node[N_B]});\n`); };
const zeroReturn = t => t === T.none ? 'return;' : t === T.jsval ? 'return JV_UNDEFINED;' : 'return 0;';
const renderCoroFunc = f => { const sym = fnSym(f); suspendMemo = new Map(); coroTmps = Object.create(null); coroTmpN = 0; coroLocals = Object.create(null); for (const p of f.params) coroLocals[p.name] = true; for (const name in f.locals) coroLocals[name] = true; collectCatchNames(f.body); const body = lowerStmts(f.body);
const live = coroLive(body); const locals = [], frame = []; const seen = new Set(); const add = (name, t) => { if (seen.has(name) || t === T.none) return; seen.add(name); locals.push([ sanitize(name), t ]); if (live == null || live.has(name)) frame.push([ sanitize(name), t ]); }; for (const p of f.params) add(p.name, p.type); for (const name in f.locals) add(name, f.locals[name].type); for (const name in coroLocals) if (typeof coroLocals[name] === 'number') add(name, coroLocals[name]); for (const name in coroTmps) add(name, coroTmps[name]);
const ctSize = t => t === T.jsval ? 16 : (t === T.f64 || t === T.i64 || t === T.u64) ? 8 : 4; frame.sort((a, b) => ctSize(b[1]) - ctSize(a[1])); const frameBytes = Math.max(8, (frame.reduce((n, [ , t ]) => n + ctSize(t), 0) + 7) & ~7); coroFrameBytes[f.index] = frameBytes;
const frameT = `porf_frame_${sym}`; coro = { regions: [ { id: 0, cases: [] } ], path: [], points: 0 }; coro.path.push(coro.regions[0]); emitBuf = []; emit(`typedef struct {${frame.map(([n, t]) => ` ${CT[t]} ${n};`).join('') || ' u64 _;'} } ${frameT};\n`); emit(`_Static_assert(sizeof(${frameT}) <= ${frameBytes}u, "coroutine frame layout");\n`); emit(`${NEVER_INLINE.has(f.name) ? 'PORF_NOINLINE ' : ''}${CT[f.retType]} ${sym}(porf_coro_call* porf_fr) {\n`); depth = 1; activeTryDepth = 0; loopStack.length = 0; usedLabels = new Set(); for (const [n, t] of locals) emit(` ${CT[t]} ${n}${t === T.jsval ? ' = JV_UNDEFINED' : ' = 0'};\n`);
const { pre, args } = invokeArgs(f, { env: 'porf_fr->env', thisv: 'porf_fr->thisv', newtv: 'porf_fr->newtv', callee: 'porf_fr->callee', argc: 'porf_fr->argc', argv: 'porf_coro_argv(porf_fr)', arg: j => `porf_unpack(porf_fr->argc > ${j} ? porf_coro_argv(porf_fr)[${j}] : JV_UNDEFINED_BITS)` }); emit(` if (porf_fr->resume == 0) {\n`); for (const x of pre) emit(` ${x}\n`); f.params.forEach((p, i) => emit(` ${sanitize(p.name)} = ${args[i]};\n`)); emit(` } else {\n`); emit(` const ${frameT}* porf_frs = (const ${frameT}*)porf_coro_frame(porf_fr);\n`); for (const [n] of frame) emit(` ${n} = porf_frs->${n};\n`); emit(` switch (porf_fr->resume) {\x000\x00default: porf_unreachable("bad coroutine resume point");}\n`); emit(` }\n`);
renderStmts(body);
emit(` ${zeroReturn(f.retType)}\n`); emit(`_porf_save:;\n`); emit(` ${frameT}* porf_frs = (${frameT}*)porf_coro_frame(porf_fr);\n`); for (const [n] of frame) emit(` porf_frs->${n} = ${n};\n`); emit(` porf_coro_touch(porf_fr);\n`); emit(` ${zeroReturn(f.retType)}\n`); emit(`}\n\n`);
const text = emitBuf.join(''); const regions = coro.regions; emitBuf = coro = null; emit(text.replace(/\x00(\d+)\x00/g, (_, id) => regions[id].cases.join(' ') + ' ')); suspendMemo = coroTmps = coroLocals = null; };
const renderFunc = f => { cur = partsOf(unitOf(f)); if (needsCoro(f)) return renderCoroFunc(f); const ret = CT[f.retType]; const params = f.params.map(p => `${CT[p.type]} ${sanitize(p.name)}`).join(', '); emit(`${f.ast?._module ? 'PORF_ONCE ' : NEVER_INLINE.has(f.name) ? 'PORF_NOINLINE ' : ''}${ret} ${fnSym(f)}(${params || 'void'}) {\n`); depth = 1; activeTryDepth = 0; loopStack.length = 0; usedLabels = new Set(); const paramNames = new Set(f.params.map(p => p.name)); for (const name in f.locals) { if (paramNames.has(name)) continue; const t = f.locals[name].type; emit(` ${CT[t]} ${sanitize(name)}${t === T.jsval ? ' = JV_UNDEFINED' : ' = 0'};\n`); } renderStmts(f.body); emit(`}\n\n`); };
for (const f of linkFuncs) renderFunc(f);
// runtime prelude: porf.h + porf_runtime.c in split mode const runtimeRefs = []; const prelude = []; const toStr = funcs.find(x => x && x.name === '__ecma262_ToString' && x.body); if (toStr) runtimeRefs.push(toStr); const iterClose = funcs.find(x => x && x.name === '__Porffor_iterator_close' && x.body); if (iterClose) runtimeRefs.push(iterClose); if (promiseResolveFunc) runtimeRefs.push(promiseResolveFunc); prelude.push(RUNTIME_HEAD(prefs, toStr ? fnSym(toStr) : null, iterClose ? fnSym(iterClose) : null, bigints)); if (usesCoro || usesSyncAsync) prelude.push(PROMISE_DIRECT_RUNTIME()); if (usesCoro) prelude.push(CORO_RUNTIME());
// link unit head: static data image, globals, gc roots, per-function tables const link = []; if (data.length > 0 || fnNameSegs.length > 0) { // static data is constant bytes at fixed offsets: one contiguous image (holes stay // zero) init'd by a single memcpy, emitted as string literals (~1 char per ascii byte) const imageBase = 16; const image = new Uint8Array(staticEnd - imageBase); const writeBytes = (off, bytes) => { off -= imageBase; for (let k = 0; k < bytes.length; k++) image[off + k] = bytes[k]; }; const writeU32 = (off, v) => writeBytes(off, [ v & 0xff, (v >>> 8) & 0xff, (v >>> 16) & 0xff, (v >>> 24) & 0xff ]); const writeU64 = (off, bits) => { off -= imageBase; for (let k = 0; k < 8; k++) image[off + k] = Number((bits >> BigInt(k * 8)) & 0xFFn); };
for (let i = 0; i < linkFuncs.length; i++) writeU32(fnrecBase + i * 8, i); for (const { off, bytes } of fnNameSegs) writeBytes(off, bytes); for (let i = 0; i < data.length; i++) { const seg = data[i]; const off = dataOffsets[i]; if (seg.staticArray) { const items = seg.staticArray; writeU32(off, items.length); writeU32(off + 4, off + 16); writeU32(off + 8, items.length); for (let j = 0; j < items.length; j++) { const it = items[j]; const dst = off + 16 + j * 8; if (it.num !== undefined) { if (it.num === 0 && 1 / it.num === Infinity) writeU64(dst, JV_ZERO_BITS); else writeBytes(dst, ieee754_binary64(it.num)); } else if (it.str !== undefined) { writeU64(dst, jvConstBits(TYPES.bytestring, dataOffsets[it.str])); } else { writeU64(dst, jvConstBits(it.jvType, it.payload)); } } continue; } writeBytes(off, seg); }
let blob = image, blobLen = image.length; let init = `static void porf_data_init(void) {\n memcpy(MEM + ${imageBase}, porf_data, ${image.length}u);\n}\n\n`; if (prefs.compressData && image.length > 0) { const compressed = lz4Compress(image); if (compressed.len < image.length) { blob = compressed.out; blobLen = compressed.len; init = LZ4_DECODE + `static void porf_data_init(void) {\n porf_lz4_decode(porf_data, ${blobLen}u, MEM + ${imageBase});\n}\n\n`; } }
// printable ascii stays literal (except " \ ?, dodging trigraphs), the rest fixed-width // octal so a following digit can't extend the escape const esc = []; for (let b = 0; b < 256; b++) { esc.push(b >= 0x20 && b <= 0x7e && b !== 0x22 && b !== 0x3f && b !== 0x5c ? String.fromCharCode(b) : '\\' + b.toString(8).padStart(3, '0')); }
const lines = []; let parts = [], len = 0; for (let i = 0; i < blobLen; i++) { const e = esc[blob[i]]; parts.push(e); len += e.length; if (len >= 4000) { lines.push('"' + parts.join('') + '"'); parts = []; len = 0; } } if (parts.length > 0 || lines.length === 0) lines.push('"' + parts.join('') + '"');
link.push(`static const u8 porf_data[] =\n${lines.join('\n')};\n${init}`); } else { link.push('static void porf_data_init(void) {}\n\n'); }
const proto = f => `${CT[f.retType]} ${fnSym(f)}(${needsCoro(f) ? 'porf_coro_call*' : f.params.map(p => CT[p.type]).join(', ') || 'void'});\n`; const linkProtos = [ `${st}jsval porf_call_dynamic(jsval fn, jsval thisv, jsval newtv, i32 argc, jsbits* argv);\n`, `${st}jsval porf_call_dynamic_arr(jsval fn, jsval thisv, jsval newtv, jsval arr);\n` ]; if (usesDynReg) for (let n = 0; n <= DYN_REGS; n++) linkProtos.push(`${split ? '' : 'static inline '}jsval porf_call${n}(${dynCallParams(n)});\n`); if (usesSyncAsync) linkProtos.push(`${st}jsval porf_async_call_sync(u32 idx, jsval callee, u32 env, jsval thisv, jsval newtv, i32 argc, jsbits* argv);\n`); if (usesCoro) { // coroutine entry points called from user code / builtins above their definitions linkProtos.push(`${st}jsval porf_coro_start(u8 flags, u32 idx, jsval callee, u32 env, jsval thisv, jsval newtv, i32 argc, jsbits* argv);\n`); linkProtos.push(`${st}i32 __Porffor_coroutine_resume(jsval gen, jsval value, i32 mode);\n`); linkProtos.push(`${st}jsval __Porffor_coroutine_value(jsval gen);\n`); linkProtos.push(`${st}i32 __Porffor_coroutine_raw(jsval gen);\n`); linkProtos.push(`${st}i32 __Porffor_coroutine_awaiting(jsval gen);\n`); linkProtos.push(`${st}void __Porffor_coroutine_setRaw(i32 raw);\n`); linkProtos.push(`${st}jsval __Porffor_coroutine_callee(jsval gen);\n`); } if (!split) { for (const f of linkFuncs) link.push(proto(f)); link.push(...linkProtos); }
// module globals (top-level JS bindings) for (const g of globals) link.push(`${st}${CT[g.type]} ${sanitize(g.name)}${g.type === T.jsval ? ` = {0.0, ${TYPES.undefined}}` : ''};\n`); link.push('\n'); if (gcEnabled) { const markGlobalRootLines = []; const markGlobalRawLines = []; for (const g of globals) { const name = sanitize(g.name); if (g.type === T.jsval) markGlobalRootLines.push(` porf_gc_mark_js(${name}.val, ${name}.type);`); else if (g.type === T.ptr || (g.type === T.i32 && /(?:underlyingStore|underlyingBuckets|__Porffor_regex_cache)$/.test(g.name))) { if (/underlyingStore$/.test(g.name)) { const buckets = sanitize(g.name.replace(/underlyingStore$/, 'underlyingBuckets')); const bucketsCap = sanitize(g.name.replace(/underlyingStore$/, 'underlyingBucketsCap')); markGlobalRawLines.push(` if (porf_gc_mark_underlying_store((i32)${name})) { ${buckets} = 0; ${bucketsCap} = 0; }`); } else if (/underlyingBuckets$/.test(g.name)) markGlobalRawLines.push(` porf_gc_mark_raw((i32)${name});`); else if (/__Porffor_regex_cache$/.test(g.name)) markGlobalRawLines.push(` porf_gc_mark_regex_cache((i32)${name});`); else if (/getptr_/.test(g.name)) { const builtinName = g.name.slice(g.name.lastIndexOf('getptr_') + 'getptr_'.length); markGlobalRootLines.push(` if (${name} != 0) porf_gc_mark_js((f64)${name}, ${funcByName.has(builtinName) ? TYPES.function : TYPES.object});`); } else markGlobalRawLines.push(` porf_gc_mark_raw((i32)${name});`); } } link.push(`${st}void porf_gc_mark_global_roots(void) {\n${markGlobalRootLines.join('\n') || ' (void)0;'}\n}\n\n`); link.push(`${st}void porf_gc_mark_global_raw_roots(void) {\n${markGlobalRawLines.join('\n') || ' (void)0;'}\n}\n\n`); link.push(usesCoro ? `${st}void porf_gc_mark_coro_roots(void) {\n for (porf_coro_call* c = porf_coro_cur; c; c = c->parent) {\n if (c->heap) porf_gc_mark_coro(c->heap);\n else porf_coro_gc_scan(c);\n }\n}\n\n${st}void porf_gc_mark_coro(u32 body) {\n if (body != 0 && porf_gc_mark_body((i32)body)) porf_coro_gc_scan((porf_coro_call*)(MEM + body));\n}\n\n${st}void porf_gc_scan_coro(i32 body) {\n porf_coro_gc_scan((porf_coro_call*)(MEM + body));\n}\n\n` : `${st}void porf_gc_mark_coro_roots(void) {}\n${st}void porf_gc_mark_coro(u32 body) { (void)body; }\n${st}void porf_gc_scan_coro(i32 body) { (void)body; }\n\n`); }
// per-function metadata tables, emitted before bodies so __Porffor_funcLut_* can read them. // porf_fnflags: bits 0-2 coroutine dispatch (masked off in porf_call_dynamic), bit 3 callable, // bit 4 constructor, funcLut.flags recovers legacy callable|constr<<1 (| sloppy<<3) via flags >> 3 link.push(`${st}const u8 porf_fnflags[] = { ${linkFuncs.map(fnFlags).join(', ') || '0'} };\n`); if (usesSyncAsync) link.push(`${st}const u8 porf_fnneeds_coro[] = { ${linkFuncs.map(f => needsCoro(f) ? 1 : 0).join(', ') || '0'} };\n`); link.push(`${st}const u16 porf_fnlen[] = { ${linkFuncs.map(f => f.jsLength ?? 0).join(', ') || '0'} };\n`); link.push(`${st}const u32 porf_fnname[] = { ${fnNameOff.join(', ') || '0'} };\n`); link.push(`const u32 porf_static_end = ${staticEnd}u;\n`); if (split) { link.push(`const u32 porf_fnrecs = ${fnrecBase}u;\n`); for (const u of unitOrder) link.push(`const u32 porf_fnbase_${sanitize(u)} = ${fnBase[u]}u;\nconst u32 porf_dbase_${sanitize(u)} = ${dbase[u]}u;\n`); } link.push('\n');
cur = partsOf('main'); for (const f of linkFuncs) cur.protos[f.index] = f;
const returnBoxed = (f, call) => f.retType === T.none ? `${call}; return JV_UNDEFINED;` : f.retType === T.f64 ? `return porf_box_num(${call});` : f.retType === T.jsval ? `return ${call};` : `return porf_box_num((f64)${call});`;
// adapt a uniform dynamic-call ABI to f's signature const dynInvokeCall = (f, v) => { const { pre, args } = invokeArgs(f, v); const ret = returnBoxed(f, `${fnSym(f)}(${args.join(', ')})`); return pre.length ? `{ ${pre.join(' ')} ${ret} }` : ret; };
if (usesDynReg) { const regsVoid = dynRegs().map((_, j) => `(void)a${j};`).join(' '); emit(`${st}jsval porf_thk_slow(${dynThunkParams}) { jsbits argv[${DYN_REGS}] = { ${dynRegs().map((_, j) => `porf_pack(a${j})`).join(', ')} }; return porf_call_dynamic(porf_box((f64)(u32)ra, ${TYPES.function}), thisv, JV_UNDEFINED, (i32)(ra >> 32), argv);}${st}jsval porf_thk_bad(${dynThunkParams}) { (void)ra; (void)thisv; ${regsVoid} porf_unreachable("uncompiled function"); return JV_UNDEFINED;}`); const table = []; for (let i = 0; i < linkFuncs.length; i++) { const f = linkFuncs[i]; if (!dynRegThunk(f)) { table.push(f.indirect || needsCoro(f) || isSyncAsync(f) ? 'porf_thk_slow' : 'porf_thk_bad'); continue; } const body = dynInvokeCall(f, { env: '(*(u32*)(MEM + (u32)ra + 4))', thisv: 'thisv', callee: `porf_box((f64)(u32)ra, ${TYPES.function})`, newtv: 'JV_UNDEFINED', arg: j => j < DYN_REGS ? `a${j}` : 'JV_UNDEFINED' }); emit(`${st}jsval porf_thk_${i}(${dynThunkParams}) { (void)ra; (void)thisv; ${regsVoid} ${body} }\n`); table.push(`porf_thk_${i}`); } emit(`${st}jsval (*const porf_thk[${linkFuncs.length || 1}])(${dynThunkParams}) = { ${table.join(', ') || 'porf_thk_bad'} };\n\n`);
for (let n = 0; n <= DYN_REGS; n++) { const regs = Array.from({ length: DYN_REGS }, (_, j) => j < n ? `a${j}` : 'JV_UNDEFINED'); emit(`${split ? '' : 'static inline '}jsval porf_call${n}(${dynCallParams(n)}) { if (porf_jv_type(fn) != ${TYPES.function}) { ${proxyCall ? `if (porf_jv_type(fn) == ${TYPES.proxy}) { jsbits argv[${n || 1}] = { ${n ? regs.slice(0, n).map(x => `porf_pack(${x})`).join(', ') : '0'} }; return porf_call_dynamic(fn, thisv, JV_UNDEFINED, ${n}, argv); }` : ''} porf_throw_new(${TYPES.typeerror}, 0); } const u32 rec = (u32)fn.val; const u32 idx = *(u32*)(MEM + rec); if (idx >= ${linkFuncs.length}u) porf_unreachable("bad function index"); return porf_thk[idx]((u64)rec | ((u64)${n} << 32), thisv, ${regs.join(', ')});}`); } }
// fn values are records [fnIdx u32][env u32], funcs a thunk fully serves fall through to it emit(`${st}jsval porf_invoke(u32 idx, jsval callee, u32 env, jsval thisv, jsval newtv, i32 argc, jsbits* argv) {\n`); emit(' (void)callee; (void)env; (void)thisv; (void)newtv; (void)argc; (void)argv;\n'); emit(' switch (idx) {\n'); for (let i = 0; i < linkFuncs.length; i++) { const f = linkFuncs[i]; if (needsCoro(f) || (!f.indirect && !isSyncAsync(f))) continue; if (usesDynReg && dynRegThunk(f) && !hasParam(f, '#newtarget') && f.params.filter(p => p.name[0] !== '#').length <= DYN_REGS) continue; emit(` case ${i}: ${dynInvokeCall(f, { env: 'env', thisv: 'thisv', callee: 'callee', newtv: 'newtv', argc: 'argc', argv: 'argv', arg: j => `porf_unpack(argc > ${j} ? argv[${j}] : JV_UNDEFINED_BITS)` })}\n`); } emit(' }\n'); if (usesDynReg) emit(` return porf_thk[idx]((u64)(u32)callee.val | ((u64)(u32)argc << 32), thisv, ${dynRegs().map((_, j) => `argc > ${j} ? porf_unpack(argv[${j}]) : JV_UNDEFINED`).join(', ')});\n}\n`); else emit(' porf_unreachable("uncompiled function");\n return JV_UNDEFINED;\n}\n');
if (usesCoro) { emit(`${st}jsval porf_coro_dispatch(porf_coro_call* call) {\n switch (call->idx) {\n`); for (let i = 0; i < linkFuncs.length; i++) { const f = linkFuncs[i]; if (needsCoro(f)) emit(` case ${i}: ${returnBoxed(f, `${fnSym(f)}(call)`)}\n`); } emit(' }\n porf_unreachable("uncompiled coroutine");\n return JV_UNDEFINED;\n}\n'); emit(`${st}u32 porf_coro_frame_bytes(u32 idx) {\n switch (idx) {\n`); for (let i = 0; i < linkFuncs.length; i++) { const f = linkFuncs[i]; if (needsCoro(f)) emit(` case ${i}: return ${coroFrameBytes[f.index]}u;\n`); } emit(' }\n return 0;\n}\n'); }
if (usesCoro || usesSyncAsync) { emit(`${st}jsval porf_promise_settled(jsval value, i32 state) { const u32 p = porf_alloc(PORF_PROMISE_SIZE, ${TYPES.promise}); *(jsbits*)(MEM + p + PORF_PROMISE_RESULT) = porf_pack(value); *(u32*)(MEM + p + PORF_PROMISE_FULFILL_HEAD) = 0; *(u32*)(MEM + p + PORF_PROMISE_FULFILL_TAIL) = 0; *(u32*)(MEM + p + PORF_PROMISE_REJECT_HEAD) = 0; *(u32*)(MEM + p + PORF_PROMISE_REJECT_TAIL) = 0; *(jsbits*)(MEM + p + PORF_PROMISE_PAYLOAD) = JV_UNDEFINED_BITS; *(u8*)(MEM + p + PORF_PROMISE_STATE) = (u8)state; *(u8*)(MEM + p + PORF_PROMISE_FLAGS) = 0; *(u8*)(MEM + p + PORF_PROMISE_HANDLED) = 0; return porf_box((f64)p, ${TYPES.promise});}
${st}jsval porf_promise_rejected(jsval value) { return porf_promise_settled(value, 2);}`); }
if (usesSyncAsync) { emit(`${st}jsval porf_async_call_sync(u32 idx, jsval callee, u32 env, jsval thisv, jsval newtv, i32 argc, jsbits* argv) { const i32 try_idx = porf_try_depth++; if (_setjmp(porf_try_ensure()[try_idx]) == 0) { const jsval result = porf_invoke(idx, callee, env, thisv, newtv, argc, argv); porf_try_depth = try_idx; const jsval out_promise = porf_promise_settled(JV_UNDEFINED, 0); ${settleAsyncResult('result', 'out_promise')} return out_promise; }
porf_try_depth = try_idx; return porf_promise_rejected(porf_exception);}`); }
if (usesCoro) { emit(` static u32 porf_arr_new_typed(i32 len, i32 cap, i32 type) { if (cap < len) cap = len; if (cap < 4) cap = 4; const u32 a = porf_alloc(16 + ((u32)cap << 3), type); PORF_ARR_LEN(a) = len; PORF_ARR_ENT(a) = a + 16; PORF_ARR_CAP(a) = cap; PORF_ARR_UNDERLYING(a) = 0; memset(MEM + PORF_ARR_ENT(a), 0, (size_t)cap << 3); return a; }
static void porf_promise_run_coro_reaction_coro(u32 reaction) { porf_coro_call* call = (porf_coro_call*)(MEM + (u32)*(u64*)(MEM + reaction + PORF_REACTION_HANDLER)); const jsval out_promise = porf_unpack(*(jsbits*)(MEM + reaction + PORF_REACTION_OUT_PROMISE)); const jsval value = porf_unpack(*(jsbits*)(MEM + reaction + PORF_REACTION_VALUE)); const i32 is_throw = (i32)*(u32*)(MEM + reaction + PORF_REACTION_PAYLOAD);
const i32 try_idx = porf_try_depth++; if (_setjmp(porf_try_ensure()[try_idx]) == 0) { const i32 done = porf_coro_call_step(call, value, is_throw); porf_try_depth = try_idx; if (done) { const jsval result = call->result; ${settleAsyncResult('result', 'out_promise')} return; }
porf_promise_attach_coro(call->channel, call, out_promise); return; }
porf_try_depth = try_idx; porf_coro_abort(call); porf_promise_settle_direct(out_promise, porf_exception, 2);}
static porf_coro_call* porf_coro_unbox(jsval gen) { const i32 type = porf_jv_type(gen); if (type != ${TYPES.__porffor_generator} && type != ${TYPES.__porffor_asyncgenerator}) { porf_throw_new(${TYPES.typeerror}, 0); } return (porf_coro_call*)(MEM + (u32)gen.val);}
// TS-facing coroutine mechanism (generator.ts + for-of build the iterator protocol and// the { value, done } result on top of these). mode: 0 = next, 1 = throw the value at the// suspend point, 2 = return (force completion with the value). returns 1 once done.${st}i32 __Porffor_coroutine_resume(jsval gen, jsval value, i32 mode) { porf_coro_call* call = porf_coro_unbox(gen); if (mode == 2 && call->state == 2) { call->result = value; porf_coro_touch(call); value = PORF_CORO_RETURN; mode = 1; } if (call->state == 1) porf_throw_new(${TYPES.typeerror}, 0); if (mode != 0 ? call->state != 2 : call->state == 3) { porf_coro_finish(call); call->result = mode == 2 ? value : JV_UNDEFINED; porf_coro_touch(call); if (mode == 1) porf_throw(value); return 1; }
// an exception escaping the body finishes it, re-raise to the caller const i32 try_idx = porf_try_depth++; if (_setjmp(porf_try_ensure()[try_idx]) == 0) { const i32 done = porf_coro_call_step(call, value, mode == 1); porf_try_depth = try_idx; return done; }
porf_try_depth = try_idx; porf_coro_abort(call); if (porf_jv_eq(porf_exception, PORF_CORO_RETURN)) return 1; porf_throw(porf_exception);}
// the value the coroutine just produced: its final return value once done, otherwise the// most recently yielded value${st}jsval __Porffor_coroutine_value(jsval gen) { porf_coro_call* call = porf_coro_unbox(gen); return call->state == 3 ? call->result : call->channel;}
${st}i32 __Porffor_coroutine_raw(jsval gen) { return porf_coro_unbox(gen)->raw;}
${st}i32 __Porffor_coroutine_awaiting(jsval gen) { return porf_coro_unbox(gen)->awaiting;}
${st}void __Porffor_coroutine_setRaw(i32 raw) { porf_coro_cur->raw = raw;}
${st}jsval __Porffor_coroutine_callee(jsval gen) { return porf_coro_unbox(gen)->callee;}
${st}jsval porf_coro_start(u8 flags, u32 idx, jsval callee, u32 env, jsval thisv, jsval newtv, i32 argc, jsbits* argv) { porf_promise_run_coro_reaction_impl = porf_promise_run_coro_reaction_coro; const u32 frame = porf_coro_frame_bytes(idx); const u8 kind = flags & 7u;
if (kind == ${FN_GENERATOR} || kind == ${FN_ASYNC_GENERATOR}) { const u32 type = kind == ${FN_GENERATOR} ? ${TYPES.__porffor_generator}u : ${TYPES.__porffor_asyncgenerator}u; porf_coro_call* call = porf_coro_alloc(type, frame, argc); porf_coro_init(call, call->heap, frame, idx, callee, env, thisv, newtv, argc, argv); if (flags & ${FN_CORO_INIT}u) { const i32 try_idx = porf_try_depth++; if (_setjmp(porf_try_ensure()[try_idx]) == 0) { (void)porf_coro_call_step(call, JV_UNDEFINED, 0); porf_try_depth = try_idx; } else { porf_try_depth = try_idx; porf_coro_abort(call); porf_throw(porf_exception); } } return porf_box((f64)call->heap, type); }
// record on the C stack until it first suspends const u32 bytes = PORF_CORO_BYTES(frame, argc); porf_coro_call* call = bytes <= 1024u ? (porf_coro_call*)__builtin_alloca(bytes) : porf_coro_alloc(PORF_GC_KIND_CORO, frame, argc); porf_coro_init(call, bytes <= 1024u ? 0 : call->heap, frame, idx, callee, env, thisv, newtv, argc, argv); const jsval out_promise = porf_promise_pending(); const i32 try_idx = porf_try_depth++; if (_setjmp(porf_try_ensure()[try_idx]) == 0) { const i32 done = porf_coro_call_step(call, JV_UNDEFINED, 0); porf_try_depth = try_idx; if (done) { jsval result = call->result; ${settleAsyncResult('result', 'out_promise')} } else { porf_coro_call* heap = porf_coro_promote(call); porf_promise_attach_coro(heap->channel, heap, out_promise); } return out_promise; }
porf_try_depth = try_idx; porf_coro_abort(call); porf_promise_settle_direct(out_promise, porf_exception, 2); return out_promise;}
`); } emit(`${st}jsval porf_call_dynamic(jsval fn, jsval thisv, jsval newtv, i32 argc, jsbits* argv) { if (porf_jv_type(fn) != ${TYPES.function}) { ${proxyCall ? `// __Porffor_proxy_call(proxy, this, newTarget, ...args) if (porf_jv_type(fn) == ${TYPES.proxy}) { jsbits nargv[argc + 3]; nargv[0] = porf_pack(fn); nargv[1] = porf_pack(thisv); nargv[2] = porf_pack(newtv); if (argc > 0) memcpy(nargv + 3, argv, (size_t)argc * sizeof(jsbits)); return porf_invoke(${linkIdx[proxyCall.index]}u, JV_UNDEFINED, 0, JV_UNDEFINED, JV_UNDEFINED, argc + 3, nargv); }` : ''} porf_throw_new(${TYPES.typeerror}, 0); } const u32 rec = (u32)fn.val; const u32 idx = *(u32*)(MEM + rec); const u32 env = *(u32*)(MEM + rec + 4); if (idx >= ${linkFuncs.length}u) porf_unreachable("bad function index"); const u8 flags = porf_fnflags[idx] & (7u | ${FN_CORO_INIT}u); const u8 kind = flags & 7u; if (!porf_jv_eq(newtv, JV_UNDEFINED) && kind != 0) porf_throw_new(${TYPES.typeerror}, 0); ${usesCoro || usesSyncAsync ? `if (kind != 0) { ${usesSyncAsync ? `if (kind == ${FN_ASYNC}u && !porf_fnneeds_coro[idx]) return porf_async_call_sync(idx, fn, env, thisv, newtv, argc, argv);` : ''} ${usesCoro ? 'return porf_coro_start(flags, idx, fn, env, thisv, newtv, argc, argv);' : 'porf_unreachable("bad coroutine dispatch");'} }` : ''} return porf_invoke(idx, fn, env, thisv, newtv, argc, argv);}// spread calls use array iteration semantics: holes become present undefined values.${st}jsval porf_call_dynamic_arr(jsval fn, jsval thisv, jsval newtv, jsval arr) { const u32 a = (u32)arr.val; const i32 argc = PORF_ARR_LEN(a); jsbits* argv = (jsbits*)(MEM + PORF_ARR_ENT(a)); for (i32 i = 0; i < argc; i++) { if (argv[i] == 0) { jsbits* dense = argc > 0 ? (jsbits*)malloc((size_t)argc * sizeof(jsbits)) : NULL; for (i32 j = 0; j < argc; j++) dense[j] = argv[j] == 0 ? JV_UNDEFINED_BITS : argv[j]; const jsval out = porf_call_dynamic(fn, thisv, newtv, argc, dense); free(dense); return out; } } return porf_call_dynamic(fn, thisv, newtv, argc, argv);}\n`);
if (prefs.nativeFetch) { const main = funcByName.get(entry); if (!main) throw new Error('missing native fetch entry function'); const promiseRunJobs = funcByName.get('__Porffor_promise_runJobs'); const drainPromiseJobs = promiseRunJobs ? `(void)${fnSym(promiseRunJobs)}(${promiseRunJobs.params.length === 0 ? '' : 'JV_UNDEFINED, JV_UNDEFINED'});` : '(void)0;';
emit(`${prefs.eventLoop ? `void porf_native_fetch_timer_start(u32 id, i32 delay_ms, i32 repeat);void porf_native_fetch_timer_clear(u32 id);` : ''}void porf_native_fetch_response_complete(void* pending, f64 value, i32 type, i32 is_reject);
typedef struct { u32 id; jsval callback; jsval args; i32 callback_root; i32 args_root; i32 repeat; i32 active;} porf_native_timer_record;
static porf_native_timer_record* porf_native_timers = NULL;static u32 porf_native_timers_len = 0;static u32 porf_native_timers_cap = 0;static u32 porf_native_next_timer_id = 1;
static porf_native_timer_record* porf_native_find_timer(u32 id) { for (u32 i = 0; i < porf_native_timers_len; i++) { if (porf_native_timers[i].active && porf_native_timers[i].id == id) return porf_native_timers + i; } return NULL;}
static void porf_native_release_timer(porf_native_timer_record* timer) { if (!timer || !timer->active) return; timer->active = 0; if (timer->callback_root >= 0) porf_gc_native_root_remove(timer->callback_root); if (timer->args_root >= 0) porf_gc_native_root_remove(timer->args_root); timer->callback_root = -1; timer->args_root = -1; timer->callback = JV_UNDEFINED; timer->args = JV_UNDEFINED;}
${st}u32 porf_native_fetch_set_timer(jsval callback, jsval args, jsval delay_value, i32 repeat) { if (callback.type != ${TYPES.function}) porf_throw_new(${TYPES.typeerror}, 0); f64 delay_ms = delay_value.type == ${TYPES.number} ? delay_value.val : 0; if (delay_ms != delay_ms || delay_ms < 0) delay_ms = 0; if (delay_ms > 2147483647.0) delay_ms = 2147483647.0;
if (porf_native_timers_len == porf_native_timers_cap) { const u32 new_cap = porf_native_timers_cap == 0 ? 16u : porf_native_timers_cap * 2u; porf_native_timer_record* grown = realloc(porf_native_timers, (size_t)new_cap * sizeof(*grown)); if (!grown) abort(); for (u32 i = porf_native_timers_cap; i < new_cap; i++) { grown[i].id = 0; grown[i].callback = JV_UNDEFINED; grown[i].args = JV_UNDEFINED; grown[i].callback_root = -1; grown[i].args_root = -1; grown[i].repeat = 0; grown[i].active = 0; } porf_native_timers = grown; porf_native_timers_cap = new_cap; }
porf_native_timer_record* timer = NULL; for (u32 i = 0; i < porf_native_timers_len; i++) { if (!porf_native_timers[i].active) { timer = porf_native_timers + i; break; } } if (!timer) timer = porf_native_timers + porf_native_timers_len++;
u32 id = porf_native_next_timer_id++; if (id == 0) id = porf_native_next_timer_id++; timer->id = id; timer->callback = callback; timer->args = args; timer->callback_root = porf_gc_native_root_add(callback.val, callback.type); timer->args_root = porf_gc_native_root_add(args.val, args.type); timer->repeat = repeat != 0; timer->active = 1;${prefs.eventLoop ? ' porf_native_fetch_timer_start(id, (i32)delay_ms, timer->repeat ? (i32)delay_ms : 0);' : ' // event loop disabled (no --event-loop): timer never fires'} return id;}
${st}void porf_native_fetch_clear_timer(jsval timer_value) { u32 id = timer_value.type == ${TYPES.number} ? (u32)timer_value.val : (u32)timer_value.val; porf_native_timer_record* timer = porf_native_find_timer(id); if (!timer) return; porf_native_release_timer(timer);${prefs.eventLoop ? ' porf_native_fetch_timer_clear(id);' : ''}}
void porf_native_fetch_fire_timer(u32 id) { porf_native_timer_record* timer = porf_native_find_timer(id); if (!timer) return; const jsval callback = timer->callback; const jsval args = timer->args; const i32 repeat = timer->repeat; if (!repeat) porf_native_release_timer(timer); (void)porf_call_dynamic_arr(callback, JV_UNDEFINED, JV_UNDEFINED, args); ${drainPromiseJobs}}
static u32 porf_native_fetch_new_response_reaction(void* pending, i32 is_throw) { const u32 reaction = porf_alloc(PORF_REACTION_SIZE, 0); *(u64*)(MEM + reaction + PORF_REACTION_HANDLER) = (u64)(uintptr_t)pending; *(jsbits*)(MEM + reaction + PORF_REACTION_OUT_PROMISE) = JV_UNDEFINED_BITS; *(jsbits*)(MEM + reaction + PORF_REACTION_VALUE) = JV_UNDEFINED_BITS; *(u32*)(MEM + reaction + PORF_REACTION_NEXT) = 0; *(u32*)(MEM + reaction + PORF_REACTION_PAYLOAD) = (u32)is_throw; *(u8*)(MEM + reaction + PORF_REACTION_KIND) = 12; *(u8*)(MEM + reaction + PORF_REACTION_FLAGS) = 0; porf_gc_barrier(reaction, PORF_GC_KIND_PROMISE_REACTION); return reaction;}
static void porf_native_fetch_append_response_reaction(u32 promise, u32 reaction, i32 reject) { const u32 head_off = reject ? PORF_PROMISE_REJECT_HEAD : PORF_PROMISE_FULFILL_HEAD; const u32 tail_off = reject ? PORF_PROMISE_REJECT_TAIL : PORF_PROMISE_FULFILL_TAIL; const u32 tail = *(u32*)(MEM + promise + tail_off); if (tail == 0) { *(u32*)(MEM + promise + head_off) = reaction; } else { *(u32*)(MEM + tail + PORF_REACTION_NEXT) = reaction; porf_gc_barrier(tail, PORF_GC_KIND_PROMISE_REACTION); } *(u32*)(MEM + promise + tail_off) = reaction; porf_gc_barrier(promise, ${TYPES.promise});}
void porf_native_fetch_attach_response(jsval promise, void* pending, u32* fulfill_out, u32* reject_out) { const u32 p = (u32)promise.val; const u32 fulfill = porf_native_fetch_new_response_reaction(pending, 0); const u32 reject = porf_native_fetch_new_response_reaction(pending, 1); porf_native_fetch_append_response_reaction(p, fulfill, 0); porf_native_fetch_append_response_reaction(p, reject, 1); if (fulfill_out) *fulfill_out = fulfill; if (reject_out) *reject_out = reject;}
void porf_native_fetch_cancel_response_reaction(u32 reaction) { if (reaction == 0) return; *(u64*)(MEM + reaction + PORF_REACTION_HANDLER) = 0;}
static void porf_native_fetch_run_response_reaction_c(u32 reaction) { void* pending = (void*)(uintptr_t)*(u64*)(MEM + reaction + PORF_REACTION_HANDLER); const jsval value = porf_unpack(*(jsbits*)(MEM + reaction + PORF_REACTION_VALUE)); const i32 is_throw = (i32)*(u32*)(MEM + reaction + PORF_REACTION_PAYLOAD); porf_native_fetch_response_complete(pending, value.val, value.type, is_throw);}
int porf_native_fetch_is_pending_promise(jsval value) { return value.type == ${TYPES.promise} && *(u8*)(MEM + (u32)value.val + PORF_PROMISE_STATE) == 0;}
int porf_native_fetch_promise_state(jsval value) { if (value.type != ${TYPES.promise}) return -1; return *(u8*)(MEM + (u32)value.val + PORF_PROMISE_STATE);}
jsval porf_native_fetch_promise_result(jsval value) { return porf_unpack(*(jsbits*)(MEM + (u32)value.val + PORF_PROMISE_RESULT));}
u32 porf_native_fetch_alloc(u32 bytes, u32 type) { return porf_alloc(bytes, type);}
u32 porf_native_fetch_alloc_bytestring(const char* input, size_t len) { const u32 ptr = porf_bstr_new((u32)len); if (len > 0) memcpy(MEM + ptr + 4, input, len); return ptr;}
void porf_native_fetch_runtime_init(void) {#ifdef _WIN32 fprintf(stderr, "Porffor native fetch server is not yet implemented on Windows\\n"); exit(1);#else signal(SIGPIPE, SIG_IGN); porf_init(0, NULL); porf_data_init(); porf_native_fetch_run_response_reaction_impl = porf_native_fetch_run_response_reaction_c; ${gcEnabled ? 'volatile int porf_stack_anchor = 0;\n porf_c_stack_top = (void*)&porf_stack_anchor;\n ' : ''}${fnSym(main)}();#endif}
void porf_native_fetch_collect_normal(void) {${gcEnabled ? ` if (porf_heap_base == 0) return; porf_gc_collect_idle();` : ' (void)0;'}}
void porf_native_fetch_collect_normal_from(void* stack_top) {${gcEnabled ? ` if (porf_heap_base == 0) return; void* prev_stack_top = porf_c_stack_top; porf_c_stack_top = stack_top; porf_gc_collect_idle(); porf_c_stack_top = prev_stack_top;` : ' (void)stack_top;'}}
int porf_native_fetch_should_collect(void) {${gcEnabled ? ' return porf_heap_base != 0 && (porf_gc_should_collect_for(0) || porf_gc_idle_minor_due());' : ' return 0;'}}
jsval porf_native_fetch_call_handler(f64 method_value, i32 method_type, f64 url_value, i32 url_type, f64 headers_value, i32 headers_type, f64 body_value, i32 body_type) { return ${nativeFetchFuncSym('__Porffor_fetch_native_handle')}(JV_UNDEFINED, JV_UNDEFINED, (jsval){method_value, method_type}, (jsval){url_value, url_type}, (jsval){headers_value, headers_type}, (jsval){body_value, body_type});}
void porf_native_fetch_drain_microtasks(void) { ${drainPromiseJobs}}
void porf_native_fetch_finalize_response(f64 response_value, i32 response_type, NativeFetchResponseParts* out) { NativeFetchResponseParts* prev = porf_native_fetch_response_parts_out; porf_native_fetch_response_parts_out = out; *out = (NativeFetchResponseParts){200, JV_UNDEFINED, JV_UNDEFINED}; (void)${nativeFetchFuncSym('__porffor_native_fetch_response_finalize')}(JV_UNDEFINED, JV_UNDEFINED, (jsval){response_value, response_type}); porf_native_fetch_response_parts_out = prev;}
f64 porf_native_fetch_get_port(void) { return __porffor_native_fetch_port.val;}
int porf_native_fetch_read_value(jsval value, const char** out_buf, size_t* out_len, char** out_owned) { if (!out_buf || !out_len || !out_owned) return -1;
*out_buf = NULL; *out_len = 0; *out_owned = NULL;
if (value.type == ${TYPES.bytestring}) { const u32 ptr = (u32)value.val; *out_buf = (const char*)(MEM + ptr + 4); *out_len = (size_t)*(u32*)(MEM + ptr); return 0; }
if (value.type == ${TYPES.string}) { const u32 ptr = (u32)value.val; const size_t len = (size_t)*(u32*)(MEM + ptr); const u16* chars = (const u16*)(MEM + ptr + 4); char* utf8 = malloc(len * 3 + 1); if (!utf8) return -1;
size_t out_len_local = 0; for (size_t i = 0; i < len; i++) { u16 c = chars[i]; if (c < 0x80) { utf8[out_len_local++] = (char)c; } else if (c < 0x800) { utf8[out_len_local++] = (char)(0xc0 | (c >> 6)); utf8[out_len_local++] = (char)(0x80 | (c & 0x3f)); } else { utf8[out_len_local++] = (char)(0xe0 | (c >> 12)); utf8[out_len_local++] = (char)(0x80 | ((c >> 6) & 0x3f)); utf8[out_len_local++] = (char)(0x80 | (c & 0x3f)); } }
utf8[out_len_local] = '\\0'; *out_buf = utf8; *out_len = out_len_local; *out_owned = utf8; return 0; }
return -1;}`); }
if (entry && !prefs.nativeFetch) { emit(`int main(int argc, char** argv) {\n porf_init(argc, argv);\n porf_data_init();\n ${gcEnabled ? 'volatile int porf_stack_anchor = 0;\n porf_c_stack_top = (void*)&porf_stack_anchor;\n ' : ''}${fnSym(funcByName.get(entry))}();\n return 0;\n}\n`); }
if (usesMath) prelude.splice(1, 0, '#include <math.h>\n'); if (prefs.rawHead) prelude.push(prefs.rawHead + '\n');
const globalTypes = Object.create(null); for (const g of globals) globalTypes[g.name] = g.type; const unitText = (u, parts) => { const text = []; if (split) { text.push('#include "porf.h"\n'); for (const x of Object.keys(parts.fnbases).sort()) text.push(`extern const u32 porf_fnbase_${sanitize(x)};\n`); for (const x of Object.keys(parts.dbases).sort()) text.push(`extern const u32 porf_dbase_${sanitize(x)};\n`); for (const x of Object.keys(parts.globals).sort()) text.push(`extern ${CT[globalTypes[x]]} ${sanitize(x)};\n`); for (const f of parts.protos.filter(Boolean).sort((a, b) => linkIdx[a.index] - linkIdx[b.index])) text.push(proto(f)); text.push('\n'); if (u === 'main') text.push(link.join('')); } return text.concat(parts.chunks, parts.out).join(''); };
if (!split) { const text = prelude.concat(link); for (const u of unitOrder) if (unitParts[u]) text.push(unitText(u, unitParts[u])); const c = text.join(''); if (!prefs.nativeFetch) return c; return { c, nativeFetch: true }; }
const rt = splitRuntime(prelude.join('')); const header = rt.header + `extern const u8 porf_fnflags[];\n${usesSyncAsync ? 'extern const u8 porf_fnneeds_coro[];\n' : ''}extern const u16 porf_fnlen[];\nextern const u32 porf_fnname[];\nextern const u32 porf_fnrecs;\n` + linkProtos.join(''); const unitName = u => { const name = units?.find(x => x.id === u)?.name; return name ? name.replace(/^[/]/, '').replace(/[^\w.-]/g, '_') + '.' + u + '.c' : `porf_${sanitize(u)}.c`; }; const files = [ { name: 'porf.h', c: header }, { name: 'porf_runtime.c', c: '#include "porf.h"\n' + runtimeRefs.map(proto).join('') + rt.impl } ]; for (const u of unitOrder) if (unitParts[u]) files.push({ name: unitName(u), c: unitText(u, unitParts[u]) }); return { files, nativeFetch: !!prefs.nativeFetch };};
// split runtime C into header declarations and implementationconst splitRuntime = text => { const header = [], impl = []; const n = text.length; let i = 0; while (i < n) { while (i < n && (text[i] === ' ' || text[i] === '\t' || text[i] === '\n')) i++; if (i >= n) break; const start = i;
if (text[i] === '#') { let end = text.indexOf('\n', i); if (end === -1) end = n; while (text[end - 1] === '\\') end = text.indexOf('\n', end + 1); header.push(text.slice(start, end)); impl.push(text.slice(start, end)); i = end; continue; } if (text.startsWith('//', i)) { const end = text.indexOf('\n', i); header.push(text.slice(start, end)); i = end; continue; }
let depth = 0, braceAt = -1, eqAt = -1, parenAt = -1; for (; i < n; i++) { const c = text[i]; if (c === '"' || c === "'") { for (i++; text[i] !== c; i++) if (text[i] === '\\') i++; continue; } if (c === '/' && text[i + 1] === '/') { i = text.indexOf('\n', i); continue; } if (c === '/' && text[i + 1] === '*') { i = text.indexOf('*/', i) + 1; continue; } if (depth === 0 && c === '\n' && text[i + 1] === '#') break; if (depth === 0 && braceAt === -1) { if (c === '=' && eqAt === -1 && text[i + 1] !== '=') eqAt = i; if (c === '(' && eqAt === -1 && parenAt === -1) parenAt = i; } if (c === '{') { if (braceAt === -1) braceAt = i; depth++; } else if (c === '}') { depth--; // a type or initializer runs on to its `;`, a function body ends here if (depth === 0 && eqAt === -1 && !/^(?:typedef|struct|union|enum)\b/.test(text.slice(start, braceAt))) { i++; break; } } else if (c === ';' && depth === 0) { i++; break; } } const chunk = text.slice(start, i); const spec = chunk.slice(0, Math.min(...[ parenAt, eqAt, braceAt, i ].filter(x => x !== -1)) - start); const isType = /^(?:typedef|struct|union|enum)\b/.test(chunk); const isFunc = !isType && parenAt !== -1 && braceAt !== -1 && eqAt === -1; const isProto = !isType && parenAt !== -1 && braceAt === -1 && eqAt === -1 && !/\(\*\w+\)/.test(chunk); const unstatic = str => str.replace(/\bstatic\s+/, '');
if (isType || /^extern\b/.test(chunk) || (isFunc && /\binline\b/.test(spec)) || /^static\s+const\b/.test(chunk)) header.push(chunk); else if (isFunc) { header.push(unstatic(chunk.slice(0, braceAt - start)).trim() + ';'); impl.push(unstatic(chunk)); } else if (isProto) header.push(/\binline\b/.test(chunk) ? chunk : unstatic(chunk)); else { header.push('extern ' + unstatic(eqAt === -1 ? chunk.slice(0, -1) : chunk.slice(0, eqAt - start)).trim() + ';'); impl.push(unstatic(chunk)); } } return { header: header.join('\n') + '\n', impl: impl.join('\n\n') + '\n' };};
const PORF_BUMP_ALLOC = () => { const st = 'static '; const sti = 'static inline '; return `// fixed-address reserve; commit-on-demand; NEVER moves. 32-bit offsets.static u32 porf_heap_base = 0;static u32 porf_heap_cur = 0;static u32 porf_heap_committed = 0;
static void porf_commit(u32 end) { if (end <= porf_heap_committed) return; u32 want = (end + (1u << 20)) & ~((1u << 20) - 1); if (!PORF_CAN_DECOMMIT || mprotect(MEM, want, PROT_READ | PROT_WRITE) != 0) { fprintf(stderr, "porffor: out of memory (commit %u)\\n", want); exit(1); } porf_heap_committed = want;}
static void porf_arena_init(void) { void* got = mmap(PORF_ARENA_HINT, PORF_ARENA_RESERVE, PORF_MMAP_RESERVE_PROT, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (got == MAP_FAILED) { fprintf(stderr, "porffor: failed to reserve arena\\n"); exit(1); } porf_mem = (u8*)got; porf_heap_base = (porf_static_end + 4095u) & ~4095u; porf_heap_cur = porf_heap_base + 8; porf_heap_committed = PORF_CAN_DECOMMIT ? 0u : (u32)PORF_ARENA_RESERVE; porf_commit(porf_heap_base + 65536u);}
${st}u32 porf_alloc_slow(u32 bytes);${sti}u32 porf_alloc(u32 bytes, u32 typeId) { const u32 size = (bytes + 7u) & ~7u; const u32 p = porf_heap_cur; const u32 next = p + size; (void)typeId; if (next <= porf_heap_committed) { porf_heap_cur = next; return p; } return porf_alloc_slow(size);}${st}u32 porf_alloc_slow(u32 size) { porf_commit(porf_heap_cur + size); const u32 p = porf_heap_cur; porf_heap_cur = p + size; return p;}
${st}void porf_gc_barrier_impl(u32 p, i32 type) { (void)p; (void)type; }${sti}int porf_gc_type_can_reference(i32 type) { (void)type; return 0; }static inline u32 porf_gc_barrier_ptr_u32(u32 p) { return p; }static inline u32 porf_gc_barrier_ptr_i32(i32 p) { return (u32)p; }static inline u32 porf_gc_barrier_ptr_jsval(jsval v) { return (u32)v.val; }#define porf_gc_barrier(p, type) porf_gc_barrier_impl(_Generic((p), jsval: porf_gc_barrier_ptr_jsval, i32: porf_gc_barrier_ptr_i32, default: porf_gc_barrier_ptr_u32)(p), (type))static void* porf_c_stack_top = NULL;${st}i32 porf_gc_native_root_add(f64 value, i32 type) { (void)value; (void)type; return -1; }${st}void porf_gc_native_root_remove(i32 slot) { (void)slot; }${st}void porf_gc_collect(int minor) { (void)minor; }
`;};
const PORF_GC_ALLOC = prefs => { const st = 'static '; const sti = 'static inline ';
// size is implicit from the page so objects need no headers const classes = []; for (let s = 16; s <= 256; s += 16) classes.push(s); for (let s = 288; s <= 512; s += 32) classes.push(s); for (let s = 576; s <= 1024; s += 64) classes.push(s); for (let s = 1152; s <= 2048; s += 128) classes.push(s); for (let s = 2304; s <= 4096; s += 256) classes.push(s); for (let s = 4608; s <= 8192; s += 512) classes.push(s); const lut = []; let ci = 0; for (let g = 0; g <= 1024; g++) { while (classes[ci] < g * 8) ci++; lut.push(ci); } // use multi-page chunks when they reduce tail waste const chunkPages = classes.map(c => { let best = 1, bw = (8192 - Math.floor(8192 / c) * c) / 8192; for (let n = 2; n <= 6; n++) { const waste = (n * 8192 - Math.floor(n * 8192 / c) * c) / (n * 8192); if (waste < bw - 0.005) { bw = waste; best = n; } } return best; }); const minorsEnabled = !prefs.nativeFetch;
return `static u32 porf_heap_base = 0;static u32 porf_heap_top = 0;static u64 porf_heap_committed = 0;
#define PORF_GC_SPAGE 8192u#define PORF_GC_SPAGE_MASK 8191u#define PORF_GC_SPAGE_SHIFT 13#define PORF_GC_NPAGES (1u << 19)#define PORF_GC_NCLASSES ${classes.length}#define PORF_GC_MAX_SMALL 8192u#define PORF_GC_NURSERY_BYTES ${Math.min(((parseInt(prefs.gcNursery) || 32) * 1024 * 1024), 256 * 1024 * 1024)}u
static const u16 porf_gc_cls_size[PORF_GC_NCLASSES] = { ${classes.join(', ')} };static const u8 porf_gc_cls_pages[PORF_GC_NCLASSES] = { ${chunkPages.join(', ')} };static const u16 porf_gc_cls_slots[PORF_GC_NCLASSES] = { ${classes.map((c, i) => Math.floor(chunkPages[i] * 8192 / c)).join(', ')} };static const u8 porf_gc_cls_lut[1025] = { ${lut.join(',')} };
// kind bytes: 0 conservative, 1..195 type IDs, 248+ internal#define PORF_GC_KIND_CORO 247u#define PORF_GC_KIND_OBJECT_ENTRIES 249u#define PORF_GC_KIND_ARRAY_ENTRIES 250u#define PORF_GC_KIND_FUNCTION 251u#define PORF_GC_KIND_UNDERLYING_STORE 252u#define PORF_GC_KIND_REGEX_CACHE 253u#define PORF_GC_KIND_LEAF 254u
#define PORF_GC_PK_SMALL 1u#define PORF_GC_PK_SPAN 2u#define PORF_GC_PK_TAIL 3u
#define PORF_GC_PF_TOUCHED 1u#define PORF_GC_PF_PARTIAL 4u#define PORF_GC_PF_TAIL 8u
struct porf_gc_page { u16 cls; u8 flags; u8 cidx; u16 cursor; u16 pad; u32 aux; // span page count, tail head index or next partial chunk plus 1};
static u8* porf_gc_kinds = NULL;// interleave metadata planes so each granule shares a cache linestatic u64* porf_gc_meta = NULL;#define PORF_GC_B_ALLOC 0u#define PORF_GC_B_MARK 1u#define PORF_GC_B_YOUNG 2u#define PORF_GC_B_AGED 3u#define porf_gc_widx(g) ((((size_t)(g) >> 9) << 5) | ((size_t)(((g) >> 6) & 7u) << 2))static u8* porf_gc_cards = NULL;static u8* porf_gc_page_kind = NULL;static struct porf_gc_page* porf_gc_pages = NULL;static u64 porf_gc_free_pages[PORF_GC_NPAGES / 64];static u32 porf_gc_free_page_cursor = 0;static u32 porf_gc_free_page_count = 0;
#define porf_gc_gran(p) ((u32)(p) >> 4)#define porf_gc_bit(plane, g) ((porf_gc_meta[porf_gc_widx(g) + (plane)] >> ((g) & 63u)) & 1ull)#define porf_gc_bit_set(plane, g) (porf_gc_meta[porf_gc_widx(g) + (plane)] |= 1ull << ((g) & 63u))#define porf_gc_bit_clear(plane, g) (porf_gc_meta[porf_gc_widx(g) + (plane)] &= ~(1ull << ((g) & 63u)))
struct porf_gc_window { u32 cur, end, lo; };static struct porf_gc_window porf_gc_active[PORF_GC_NCLASSES];static u32 porf_gc_partial[PORF_GC_NCLASSES];
static u64 porf_gc_allocation_debt = 0;static u64 porf_gc_last_live_bytes = 0;static u64 porf_gc_live_bytes = 0;static i64 porf_gc_window_bytes = 0;static i64 porf_gc_span_bytes = 0;static i64 porf_gc_claimed_since_full = 0;static i64 porf_gc_promoted_since_full = 0;static int porf_gc_minor_mode = 0;static int porf_gc_scan_young_seen = 0;
static const u64 porf_gc_allocation_debt_min = ${prefs.nativeFetch ? '64ull * 1024ull' : '1ull * 1024ull * 1024ull'};static const u64 porf_gc_allocation_debt_max = 256ull * 1024ull * 1024ull;
static u32* porf_gc_touched = NULL;static i32 porf_gc_touched_len = 0;static i32 porf_gc_touched_cap = 0;
static u32* porf_gc_weakmaps = NULL;static i32 porf_gc_weakmaps_len = 0;static i32 porf_gc_weakmaps_cap = 0;
struct porf_gc_mark_item { u32 body; i32 type; };static struct porf_gc_mark_item* porf_gc_mark_queue = NULL;static i32 porf_gc_mark_queue_len = 0;static i32 porf_gc_mark_queue_cap = 0;
struct porf_gc_boxed_mark { f64 value; i32 type; };static struct porf_gc_boxed_mark* porf_gc_boxed_marks = NULL;static i32 porf_gc_boxed_marks_len = 0;static i32 porf_gc_boxed_marks_cap = 0;
static u64* porf_gc_static_marks = NULL;static i32 porf_gc_static_marks_len = 0;static i32 porf_gc_static_marks_cap = 0;
${st}void porf_gc_collect(int minor);static void porf_gc_mark_js(f64 value, i32 type);static void porf_gc_mark_raw(i32 body);static int porf_gc_mark_underlying_store(i32 body);static u32 porf_gc_underlying_old = 0;static void porf_gc_mark_regex_cache(i32 cache);static void porf_gc_mark_coro(u32 body);static void porf_gc_scan_coro(i32 body);static void porf_gc_cons_scan_range(const u64* lo, const u64* hi);static void porf_gc_mark_global_roots(void);static void porf_gc_mark_global_raw_roots(void);static void porf_gc_mark_coro_roots(void);static void porf_gc_scan_kind_block(i32 body);static void porf_gc_scan_body(i32 body, i32 type);static void porf_gc_scan_object_entries_range(i32 entries, u32 from, u32 to);static u32 porf_gc_span_alloc(u32 bytes, u32 typeId);
static inline u32 porf_gc_align(u32 size) { return (size + 7u) & ~7u; }
static inline u8 porf_gc_kind_for_type(u32 typeId) { if (typeId == 0u) return (u8)PORF_GC_KIND_LEAF; return typeId <= 195u ? (u8)typeId : 0u;}
static void porf_commit(u64 end) { if (end <= porf_heap_committed) return; const u64 want = (end + (1ull << 20)) & ~((1ull << 20) - 1ull); if (want > PORF_ARENA_RESERVE) { fprintf(stderr, "porffor: out of memory (commit %llu)\\n", (unsigned long long)want); exit(1); } if (PORF_CAN_DECOMMIT && mprotect(MEM + porf_heap_committed, (size_t)(want - porf_heap_committed), PROT_READ | PROT_WRITE) != 0) { fprintf(stderr, "porffor: out of memory (commit %llu)\\n", (unsigned long long)want); exit(1); } porf_heap_committed = want;}
static void porf_arena_init(void) { void* got = mmap(PORF_ARENA_HINT, PORF_ARENA_RESERVE, PORF_MMAP_RESERVE_PROT, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (got == MAP_FAILED) { fprintf(stderr, "porffor: failed to reserve arena\\n"); exit(1); } porf_mem = (u8*)got; porf_heap_base = (porf_static_end + PORF_GC_SPAGE_MASK) & ~PORF_GC_SPAGE_MASK; porf_heap_top = porf_heap_base; porf_heap_committed = PORF_CAN_DECOMMIT ? 0ull : (u64)PORF_ARENA_RESERVE; porf_commit(porf_heap_base + 65536u);
const size_t kinds_bytes = (size_t)(PORF_ARENA_RESERVE >> 4); const size_t bits_bytes = (size_t)(PORF_ARENA_RESERVE >> 4 >> 3); const size_t cards_bytes = (size_t)(PORF_ARENA_RESERVE >> 9); const size_t pk_bytes = (size_t)PORF_GC_NPAGES; const size_t pm_bytes = (size_t)PORF_GC_NPAGES * sizeof(struct porf_gc_page); const size_t side_bytes = kinds_bytes + bits_bytes * 4 + cards_bytes + pk_bytes + pm_bytes; u8* side = (u8*)mmap(NULL, side_bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); if (side == MAP_FAILED) { fprintf(stderr, "porffor: failed to reserve gc metadata\\n"); exit(1); } porf_gc_kinds = side; side += kinds_bytes; porf_gc_meta = (u64*)side; side += bits_bytes * 4; porf_gc_cards = side; side += cards_bytes; porf_gc_page_kind = side; side += pk_bytes; porf_gc_pages = (struct porf_gc_page*)side;}
static inline int porf_gc_in_heap(i32 body) { return body > 0 && (u32)body >= porf_heap_base && (u32)body < porf_heap_top;}static inline int porf_gc_in_static(i32 body) { return body > 0 && (u32)body < porf_heap_base;}static inline int porf_gc_static_range(i32 ptr, u64 bytes) { return ptr > 0 && (u64)(u32)ptr + bytes <= (u64)porf_heap_base;}
// allocation bits are only set on object basesstatic inline int porf_gc_is_block_start(i32 body) { return body > 0 && porf_gc_bit(PORF_GC_B_ALLOC, porf_gc_gran(body)) != 0u;}static inline int porf_gc_is_young(i32 body) { return porf_gc_bit(PORF_GC_B_YOUNG, porf_gc_gran(body)) != 0u;}static inline u32 porf_gc_block_size(i32 body) { const u32 pg = (u32)body >> PORF_GC_SPAGE_SHIFT; const u8 k = porf_gc_page_kind[pg]; if (k == PORF_GC_PK_SMALL) return porf_gc_pages[pg].cls; if (k == PORF_GC_PK_SPAN) return porf_gc_pages[pg].aux << PORF_GC_SPAGE_SHIFT; return 0;}static inline u32 porf_gc_kind(i32 body) { if (!porf_gc_is_block_start(body)) return 0; return porf_gc_kinds[porf_gc_gran(body)];}// accept freshly bumped objects before allocation bits are publishedstatic inline u32 porf_gc_chunk_start(u32 pg) { const u32 head = (porf_gc_pages[pg].flags & PORF_GC_PF_TAIL) != 0u ? porf_gc_pages[pg].aux : pg; return head << PORF_GC_SPAGE_SHIFT;}
static inline void porf_gc_set_kind(i32 body, u32 kind) { if (porf_heap_base == 0 || body <= 0) return; const u32 pg = (u32)body >> PORF_GC_SPAGE_SHIFT; const u8 k = porf_gc_page_kind[pg]; if (k == PORF_GC_PK_SMALL) { if (((u32)body - porf_gc_chunk_start(pg)) % porf_gc_pages[pg].cls != 0u) return; } else if (k == PORF_GC_PK_SPAN) { if (((u32)body & PORF_GC_SPAGE_MASK) != 0u) return; } else return; porf_gc_kinds[porf_gc_gran(body)] = (u8)kind;}
static inline void porf_gc_set_marked_type(i32 body, i32 type) { porf_gc_bit_set(PORF_GC_B_MARK, porf_gc_gran(body)); porf_gc_kinds[porf_gc_gran(body)] = (u8)type;}static inline int porf_gc_has_marked_type(i32 body, i32 type) { if (!porf_gc_is_block_start(body)) return 0; const u32 g = porf_gc_gran(body); return porf_gc_bit(PORF_GC_B_MARK, g) != 0u && porf_gc_kinds[g] == (u8)type;}
static void porf_gc_touch_list(u32 pg) { if ((porf_gc_pages[pg].flags & PORF_GC_PF_TOUCHED) != 0u) return; porf_gc_pages[pg].flags |= PORF_GC_PF_TOUCHED; if (porf_gc_touched_len == porf_gc_touched_cap) { const i32 nc = porf_gc_touched_cap == 0 ? 1024 : porf_gc_touched_cap * 2; u32* grown = realloc(porf_gc_touched, (size_t)nc * sizeof(u32)); if (!grown) abort(); porf_gc_touched = grown; porf_gc_touched_cap = nc; } porf_gc_touched[porf_gc_touched_len++] = pg;}
static inline void porf_gc_free_page_release(u32 pg) { porf_gc_page_kind[pg] = 0; porf_gc_free_pages[pg >> 6] |= 1ull << (pg & 63u); porf_gc_free_page_count++; if (pg < porf_gc_free_page_cursor) porf_gc_free_page_cursor = pg;}
static u32 porf_gc_free_page_pop(void) { if (porf_gc_free_page_count == 0) return 0; u32 w = porf_gc_free_page_cursor >> 6; const u32 words = porf_heap_top >> PORF_GC_SPAGE_SHIFT >> 6; while (w <= words) { const u64 word = porf_gc_free_pages[w]; if (word != 0) { const u32 pg = (w << 6) + (u32)__builtin_ctzll(word); porf_gc_free_pages[w] &= word - 1; porf_gc_free_page_count--; porf_gc_free_page_cursor = pg + 1; return pg; } w++; } porf_gc_free_page_count = 0; return 0;}
static int porf_gc_full_due(i64 additional_claimed) { const u64 live_limit = porf_gc_last_live_bytes > ${prefs.nativeFetch ? '16777216ull' : '268435456ull'} ? porf_gc_last_live_bytes : ${prefs.nativeFetch ? '16777216ull' : '268435456ull'}; u64 promoted_limit = porf_gc_last_live_bytes / 2u; if (promoted_limit < ${prefs.nativeFetch ? '8388608ull' : '134217728ull'}) promoted_limit = ${prefs.nativeFetch ? '8388608ull' : '134217728ull'}; if (promoted_limit > ${prefs.nativeFetch ? '33554432ull' : '536870912ull'}) promoted_limit = ${prefs.nativeFetch ? '33554432ull' : '536870912ull'}; const i64 claimed = porf_gc_claimed_since_full + additional_claimed; return claimed > (i64)live_limit || porf_gc_promoted_since_full > (i64)promoted_limit || (porf_heap_top > 1610612736u && claimed > 67108864ll);}
static u32 porf_gc_run_cursor = 0;
// exact-size cache for common same-size churn#define PORF_GC_RUN_CACHE_MAX 16ustatic u32 porf_gc_run_cache[PORF_GC_RUN_CACHE_MAX + 1][8];static u8 porf_gc_run_cache_len[PORF_GC_RUN_CACHE_MAX + 1];
static void porf_gc_release_run(u32 lo, u32 npg) { for (u32 k = 0; k < npg; k++) porf_gc_page_kind[lo + k] = 0; if (npg >= 2u && npg <= PORF_GC_RUN_CACHE_MAX && porf_gc_run_cache_len[npg] < 8u) { porf_gc_run_cache[npg][porf_gc_run_cache_len[npg]++] = lo; return; } for (u32 k = 0; k < npg; k++) porf_gc_free_page_release(lo + k);}
static void porf_gc_run_cache_drain(void) { for (u32 n = 2; n <= PORF_GC_RUN_CACHE_MAX; n++) { while (porf_gc_run_cache_len[n] > 0) { const u32 lo = porf_gc_run_cache[n][--porf_gc_run_cache_len[n]]; for (u32 k = 0; k < n; k++) porf_gc_free_page_release(lo + k); } }}
static u32 porf_gc_pool_run(u32 npg) { if (npg == 1u) return porf_gc_free_page_pop(); if (npg <= PORF_GC_RUN_CACHE_MAX && porf_gc_run_cache_len[npg] > 0) return porf_gc_run_cache[npg][--porf_gc_run_cache_len[npg]]; if (porf_gc_free_page_count < npg) return 0; u32 lo = 0; const u32 base = porf_heap_base >> PORF_GC_SPAGE_SHIFT; const u32 top = porf_heap_top >> PORF_GC_SPAGE_SHIFT; u32 resume = porf_gc_run_cursor; if (resume < base || resume >= top) resume = base; for (int pass = 0; pass < 2 && lo == 0; pass++) { const u32 from = pass == 0 ? resume : base; const u32 until = pass == 0 ? top : resume; u32 run = 0, start = 0; for (u32 pg = from; pg < until; pg++) { if (run == 0 && (pg & 63u) == 0u && porf_gc_free_pages[pg >> 6] == 0ull) { pg += 63u; continue; } if ((porf_gc_free_pages[pg >> 6] & (1ull << (pg & 63u))) != 0u) { if (run == 0) start = pg; if (++run == npg) { lo = start; break; } } else run = 0; } } if (lo != 0) { for (u32 k = 0; k < npg; k++) { const u32 pg = lo + k; porf_gc_free_pages[pg >> 6] &= ~(1ull << (pg & 63u)); porf_gc_free_page_count--; } porf_gc_run_cursor = lo + npg; } return lo;}
static u32 porf_gc_claim_pages(u32 npg) { u32 lo = porf_gc_pool_run(npg); if (lo == 0) { if (porf_gc_full_due((i64)npg * (i64)PORF_GC_SPAGE)) { porf_gc_collect(0); lo = porf_gc_pool_run(npg); } if (lo == 0) { if ((u64)porf_heap_top + (u64)npg * PORF_GC_SPAGE >= PORF_ARENA_RESERVE) { porf_gc_collect(0); lo = porf_gc_pool_run(npg); if (lo == 0 && (u64)porf_heap_top + (u64)npg * PORF_GC_SPAGE >= PORF_ARENA_RESERVE) return 0; } if (lo == 0) { porf_commit((u64)porf_heap_top + (u64)npg * PORF_GC_SPAGE); lo = porf_heap_top >> PORF_GC_SPAGE_SHIFT; porf_heap_top += npg * PORF_GC_SPAGE; } } } porf_gc_claimed_since_full += (i64)npg * (i64)PORF_GC_SPAGE; porf_gc_allocation_debt += (u64)npg * PORF_GC_SPAGE; return lo;}
static int porf_gc_install_run(i32 ci, u32 pg) { struct porf_gc_window* w = &porf_gc_active[ci]; struct porf_gc_page* m = &porf_gc_pages[pg]; const u32 cls = porf_gc_cls_size[ci]; const u32 slots = porf_gc_cls_slots[ci]; const u32 base_addr = pg << PORF_GC_SPAGE_SHIFT; u32 i = m->cursor; while (i < slots && porf_gc_bit(PORF_GC_B_ALLOC, porf_gc_gran(base_addr + i * cls)) != 0u) i++; if (i >= slots) { m->cursor = (u16)slots; return 0; } u32 j = i + 1; while (j < slots && porf_gc_bit(PORF_GC_B_ALLOC, porf_gc_gran(base_addr + j * cls)) == 0u) j++; m->cursor = (u16)j; w->lo = w->cur = base_addr + i * cls; w->end = base_addr + j * cls; porf_gc_window_bytes += (i64)((j - i) * cls); porf_gc_touch_list(pg); return 1;}
static void porf_gc_publish_window(i32 ci) { struct porf_gc_window* w = &porf_gc_active[ci]; const u32 cls = porf_gc_cls_size[ci]; for (u32 b = w->lo; b < w->cur; b += cls) { const u32 g = porf_gc_gran(b); porf_gc_bit_set(PORF_GC_B_ALLOC, g); porf_gc_bit_set(PORF_GC_B_YOUNG, g); } w->lo = w->cur;}
static int porf_gc_refill_window(i32 ci) { struct porf_gc_window* w = &porf_gc_active[ci]; if (w->end != 0) { const u32 pg = porf_gc_chunk_start((w->end - 1u) >> PORF_GC_SPAGE_SHIFT) >> PORF_GC_SPAGE_SHIFT; porf_gc_publish_window(ci); w->cur = w->end = w->lo = 0; if (porf_gc_install_run(ci, pg)) return 1; } while (porf_gc_partial[ci] != 0) { const u32 pg = porf_gc_partial[ci] - 1u; struct porf_gc_page* m = &porf_gc_pages[pg]; porf_gc_partial[ci] = m->aux; m->aux = 0; if (porf_gc_page_kind[pg] != PORF_GC_PK_SMALL || m->cidx != (u8)ci || (m->flags & PORF_GC_PF_PARTIAL) == 0u) continue; m->flags &= (u8)~PORF_GC_PF_PARTIAL; if (porf_gc_install_run(ci, pg)) return 1; } const u32 npg = porf_gc_cls_pages[ci]; const u32 pg = porf_gc_claim_pages(npg); if (pg == 0) return 0; struct porf_gc_page* m = &porf_gc_pages[pg]; m->cls = porf_gc_cls_size[ci]; m->flags = 0; m->cidx = (u8)ci; m->cursor = (u16)porf_gc_cls_slots[ci]; m->aux = 0; porf_gc_page_kind[pg] = PORF_GC_PK_SMALL; for (u32 k = 1; k < npg; k++) { struct porf_gc_page* tm = &porf_gc_pages[pg + k]; tm->cls = porf_gc_cls_size[ci]; tm->flags = PORF_GC_PF_TAIL; tm->cidx = (u8)ci; tm->cursor = 0; tm->aux = pg; porf_gc_page_kind[pg + k] = PORF_GC_PK_SMALL; } w->lo = w->cur = pg << PORF_GC_SPAGE_SHIFT; w->end = (pg << PORF_GC_SPAGE_SHIFT) + (u32)porf_gc_cls_slots[ci] * porf_gc_cls_size[ci]; porf_gc_window_bytes += (i64)(w->end - w->cur); porf_gc_touch_list(pg); return 1;}
${st}u32 porf_alloc_slow(u32 bytes, u32 typeId);${sti}u32 porf_alloc(u32 bytes, u32 typeId) { if (bytes <= PORF_GC_MAX_SMALL) { const u32 ci = porf_gc_cls_lut[(bytes + 7u) >> 3]; struct porf_gc_window* w = &porf_gc_active[ci]; const u32 cur = w->cur; if (cur < w->end) { w->cur = cur + porf_gc_cls_size[ci]; porf_gc_kinds[cur >> 4] = porf_gc_kind_for_type(typeId); if (typeId == 0u) memset(MEM + cur, 0, porf_gc_cls_size[ci]); return cur; } } return porf_alloc_slow(bytes, typeId);}
static void porf_gc_minor(void);
${st}u32 porf_alloc_slow(u32 bytes, u32 typeId) { if (porf_heap_base == 0) { porf_arena_init(); return porf_alloc_slow(bytes, typeId); }${minorsEnabled ? ` if (porf_gc_window_bytes >= (i64)PORF_GC_NURSERY_BYTES || porf_gc_span_bytes >= 8388608ll) { porf_gc_window_bytes = 0; porf_gc_span_bytes = 0; porf_gc_minor(); }` : ''}\ if (bytes > PORF_GC_MAX_SMALL) return porf_gc_span_alloc(bytes, typeId); const u32 ci = porf_gc_cls_lut[(bytes + 7u) >> 3]; if (porf_gc_refill_window((i32)ci)) return porf_alloc(bytes, typeId); fprintf(stderr, "porffor: out of memory (gc heap limit; req=%u live=%lluMB heap_top=%u)\\n", bytes, (unsigned long long)(porf_gc_live_bytes / 1048576ull), porf_heap_top); abort();}
static u32 porf_gc_span_alloc(u32 bytes, u32 typeId) { const u32 npg = (bytes + PORF_GC_SPAGE_MASK) >> PORF_GC_SPAGE_SHIFT; const u32 lo = porf_gc_claim_pages(npg); if (lo == 0) { fprintf(stderr, "porffor: out of memory (span %u pages; heap_top=%u)\\n", npg, porf_heap_top); abort(); } porf_gc_span_bytes += (i64)npg * (i64)PORF_GC_SPAGE; porf_gc_page_kind[lo] = PORF_GC_PK_SPAN; porf_gc_pages[lo].cls = 0; porf_gc_pages[lo].flags = 0; porf_gc_pages[lo].cursor = 0; porf_gc_pages[lo].aux = npg; for (u32 k = 1; k < npg; k++) { porf_gc_page_kind[lo + k] = PORF_GC_PK_TAIL; porf_gc_pages[lo + k].aux = lo; } const u32 body = lo << PORF_GC_SPAGE_SHIFT; const u32 g = porf_gc_gran(body); porf_gc_bit_set(PORF_GC_B_ALLOC, g); porf_gc_bit_set(PORF_GC_B_YOUNG, g); porf_gc_bit_clear(PORF_GC_B_AGED, g); porf_gc_bit_clear(PORF_GC_B_MARK, g); porf_gc_kinds[g] = porf_gc_kind_for_type(typeId); if (typeId == 0u) memset(MEM + body, 0, (size_t)npg * PORF_GC_SPAGE); porf_gc_touch_list(lo); return body;}
${st}void porf_gc_barrier_reclassify(u32 p, i32 type) { const u32 pg = p >> PORF_GC_SPAGE_SHIFT; const u8 k = porf_gc_page_kind[pg]; if (k == PORF_GC_PK_SMALL) { if ((p - porf_gc_chunk_start(pg)) % porf_gc_pages[pg].cls != 0u) return; } else if (k == PORF_GC_PK_SPAN) { if ((p & PORF_GC_SPAGE_MASK) != 0u) return; } else return; porf_gc_kinds[p >> 4] = (u8)type;}${sti}void porf_gc_barrier_impl(u32 p, i32 type) { if (porf_heap_base == 0 || p == 0) return; porf_gc_cards[p >> 9] = 1; if (type <= 0) return; if (type > 195 && type < 248) return; if (porf_gc_kinds[p >> 4] == (u8)type) return; porf_gc_barrier_reclassify(p, type);}static inline u32 porf_gc_barrier_ptr_u32(u32 p) { return p; }static inline u32 porf_gc_barrier_ptr_i32(i32 p) { return (u32)p; }static inline u32 porf_gc_barrier_ptr_jsval(jsval v) { return (u32)v.val; }#define porf_gc_barrier(p, type) porf_gc_barrier_impl(_Generic((p), jsval: porf_gc_barrier_ptr_jsval, i32: porf_gc_barrier_ptr_i32, default: porf_gc_barrier_ptr_u32)(p), (type))
static void* porf_c_stack_top = NULL;
${sti}int porf_gc_type_can_reference(i32 type) { switch (type) { case ${TYPES.undefined}: case ${TYPES.number}: case ${TYPES.boolean}: case ${TYPES.numberobject}: case ${TYPES.booleanobject}: return 0; } return 1;}
static inline i32 porf_gc_value_body(f64 value, i32 type) { if (type == ${TYPES.bigint}) { if (value < 2251799813685248.0) return 0; value -= 2251799813685248.0; } return (i32)value;}
static int porf_gc_object_shape_valid(i32 body) { if (!porf_gc_is_block_start(body)) return 0; const u32 block_size = porf_gc_block_size(body); if (block_size < 16u) return 0; const u32 size = *(u16*)(MEM + body); const u32 capacity = *(u16*)(MEM + body + 2); if (size > capacity) return 0; const i32 entries = *(u32*)(MEM + body + 12); if (entries == 0) return size == 0; const u64 entry_bytes = (u64)capacity * 20ull; if (entries == body + 16) return 16ull + entry_bytes <= (u64)block_size; if (porf_gc_in_static(entries)) return porf_gc_static_range(entries, entry_bytes); if (!porf_gc_is_block_start(entries)) return 0; return entry_bytes <= (u64)porf_gc_block_size(entries);}
static int porf_gc_static_object_shape_valid(i32 body) { if (!porf_gc_static_range(body, 16ull)) return 0; const u32 size = *(u16*)(MEM + body); const u32 capacity = *(u16*)(MEM + body + 2); if (size > capacity) return 0; const i32 entries = *(u32*)(MEM + body + 12); if (entries == 0) return size == 0; const u64 entry_bytes = (u64)capacity * 20ull; if (entries == body + 16) return porf_gc_static_range(body, 16ull + entry_bytes); if (porf_gc_in_static(entries)) return porf_gc_static_range(entries, entry_bytes); if (!porf_gc_is_block_start(entries)) return 0; return entry_bytes <= (u64)porf_gc_block_size(entries);}
static int porf_gc_array_like_shape_valid(i32 body) { if (!porf_gc_is_block_start(body)) return 0; const u32 block_size = porf_gc_block_size(body); if (block_size < 16u) return 0; u32 len = *(u32*)(MEM + body); const i32 entries = *(u32*)(MEM + body + 4); const u32 capacity = *(u32*)(MEM + body + 8); if (len > capacity) len = capacity; const u64 bytes = (u64)len * 8ull; if (entries == body + 16) return 16ull + bytes <= (u64)block_size; if (porf_gc_in_static(entries)) return porf_gc_static_range(entries, bytes); if (!porf_gc_is_block_start(entries)) return 0; return bytes <= (u64)porf_gc_block_size(entries);}
static int porf_gc_static_array_like_shape_valid(i32 body) { if (!porf_gc_static_range(body, 16ull)) return 0; u32 len = *(u32*)(MEM + body); const i32 entries = *(u32*)(MEM + body + 4); const u32 capacity = *(u32*)(MEM + body + 8); if (len > capacity) len = capacity; const u64 bytes = (u64)len * 8ull; if (entries == body + 16) return porf_gc_static_range(body, 16ull + bytes); return porf_gc_static_range(entries, bytes);}
static void porf_gc_enqueue_mark(i32 body, i32 type) { if (porf_gc_mark_queue_len == porf_gc_mark_queue_cap) { const i32 new_cap = porf_gc_mark_queue_cap == 0 ? 4096 : porf_gc_mark_queue_cap * 2; struct porf_gc_mark_item* grown = realloc(porf_gc_mark_queue, (size_t)new_cap * sizeof(*grown)); if (!grown) abort(); porf_gc_mark_queue = grown; porf_gc_mark_queue_cap = new_cap; } porf_gc_mark_queue[porf_gc_mark_queue_len++] = (struct porf_gc_mark_item){ (u32)body, type };}
static int porf_gc_mark_body(i32 body) { if (body <= 0) return 0; const u32 g = porf_gc_gran(body); if (porf_gc_bit(PORF_GC_B_ALLOC, g) == 0u) return 0; if (porf_gc_minor_mode) { if (porf_gc_bit(PORF_GC_B_YOUNG, g) == 0u) return 0; porf_gc_scan_young_seen = 1; } if (porf_gc_bit(PORF_GC_B_MARK, g) != 0u) return 0; porf_gc_bit_set(PORF_GC_B_MARK, g); return 1;}
static void porf_gc_mark_raw(i32 body) { if (body <= 0) return; const u32 g = porf_gc_gran(body); if (porf_gc_bit(PORF_GC_B_ALLOC, g) == 0u) return; if (porf_gc_minor_mode && porf_gc_bit(PORF_GC_B_YOUNG, g) == 0u) return; porf_gc_bit_set(PORF_GC_B_MARK, g);}
static void porf_gc_mark_boxed_primitive(f64 value, i32 type) { for (i32 i = 0; i < porf_gc_boxed_marks_len; i++) if (porf_gc_boxed_marks[i].type == type && porf_gc_boxed_marks[i].value == value) return; if (porf_gc_boxed_marks_len == porf_gc_boxed_marks_cap) { const i32 new_cap = porf_gc_boxed_marks_cap == 0 ? 64 : porf_gc_boxed_marks_cap * 2; struct porf_gc_boxed_mark* grown = realloc(porf_gc_boxed_marks, (size_t)new_cap * sizeof(*grown)); if (!grown) abort(); porf_gc_boxed_marks = grown; porf_gc_boxed_marks_cap = new_cap; } porf_gc_boxed_marks[porf_gc_boxed_marks_len++] = (struct porf_gc_boxed_mark){ value, type };}static int porf_gc_is_marked_boxed_primitive(f64 value, i32 type) { for (i32 i = 0; i < porf_gc_boxed_marks_len; i++) if (porf_gc_boxed_marks[i].type == type && porf_gc_boxed_marks[i].value == value) return 1; return 0;}
static inline u64 porf_gc_static_mark_key(i32 body, i32 type) { return ((u64)(u32)body << 8) | (u64)(u32)(type & 0xff) | (1ull << 63);}static inline u64 porf_gc_static_mark_hash(u64 key) { key ^= key >> 33; key *= 0xff51afd7ed558ccdull; key ^= key >> 33; return key;}static int porf_gc_static_is_marked(i32 body, i32 type) { if (porf_gc_static_marks_cap == 0) return 0; const u64 key = porf_gc_static_mark_key(body, type); const u32 mask = (u32)porf_gc_static_marks_cap - 1u; u32 pos = (u32)porf_gc_static_mark_hash(key) & mask; while (porf_gc_static_marks[pos] != 0) { if (porf_gc_static_marks[pos] == key) return 1; pos = (pos + 1u) & mask; } return 0;}static void porf_gc_static_marks_grow(void) { const i32 old_cap = porf_gc_static_marks_cap; u64* old = porf_gc_static_marks; const i32 new_cap = old_cap == 0 ? 1024 : old_cap * 2; u64* grown = calloc((size_t)new_cap, sizeof(u64)); if (!grown) abort(); porf_gc_static_marks = grown; porf_gc_static_marks_cap = new_cap; if (old != NULL) { const u32 mask = (u32)new_cap - 1u; for (i32 i = 0; i < old_cap; i++) { const u64 key = old[i]; if (key == 0) continue; u32 pos = (u32)porf_gc_static_mark_hash(key) & mask; while (porf_gc_static_marks[pos] != 0) pos = (pos + 1u) & mask; porf_gc_static_marks[pos] = key; } free(old); }}static int porf_gc_mark_static(i32 body, i32 type) { if (porf_gc_static_is_marked(body, type)) return 0; if (porf_gc_static_marks_len * 2 >= porf_gc_static_marks_cap) porf_gc_static_marks_grow(); const u64 key = porf_gc_static_mark_key(body, type); const u32 mask = (u32)porf_gc_static_marks_cap - 1u; u32 pos = (u32)porf_gc_static_mark_hash(key) & mask; while (porf_gc_static_marks[pos] != 0) pos = (pos + 1u) & mask; porf_gc_static_marks[pos] = key; porf_gc_static_marks_len++; return 1;}
static int porf_gc_is_marked_js(f64 value, i32 type) { switch (type) { case ${TYPES.undefined}: case ${TYPES.number}: case ${TYPES.boolean}: return 1; case ${TYPES.numberobject}: case ${TYPES.booleanobject}: return porf_gc_is_marked_boxed_primitive(value, type); } const i32 body = porf_gc_value_body(value, type); if (body == 0) return 1; if (porf_gc_in_static(body)) return porf_gc_static_is_marked(body, type); const u32 g = porf_gc_gran(body); if (porf_gc_bit(PORF_GC_B_ALLOC, g) == 0u) return 0; if (porf_gc_minor_mode && porf_gc_bit(PORF_GC_B_YOUNG, g) == 0u) return 1; return porf_gc_bit(PORF_GC_B_MARK, g) != 0u;}
struct porf_gc_native_root { f64 value; i32 type;};static struct porf_gc_native_root* porf_gc_native_roots = NULL;static i32 porf_gc_native_roots_len = 0;static i32 porf_gc_native_roots_cap = 0;static i32* porf_gc_native_root_free_slots = NULL;static i32 porf_gc_native_root_free_slots_len = 0;static i32* porf_gc_native_root_active = NULL;static i32* porf_gc_native_root_active_pos = NULL;static i32 porf_gc_native_root_active_len = 0;i32 porf_gc_native_root_add(f64 value, i32 type) { if (porf_gc_native_root_free_slots_len == 0 && porf_gc_native_roots_len == porf_gc_native_roots_cap) { i32 new_cap = porf_gc_native_roots_cap == 0 ? 64 : porf_gc_native_roots_cap * 2; struct porf_gc_native_root* grown = realloc(porf_gc_native_roots, (size_t)new_cap * sizeof(*grown)); i32* grown_free_slots = realloc(porf_gc_native_root_free_slots, (size_t)new_cap * sizeof(*grown_free_slots)); i32* grown_active = realloc(porf_gc_native_root_active, (size_t)new_cap * sizeof(*grown_active)); i32* grown_active_pos = realloc(porf_gc_native_root_active_pos, (size_t)new_cap * sizeof(*grown_active_pos)); if (!grown || !grown_free_slots || !grown_active || !grown_active_pos) abort(); for (i32 i = porf_gc_native_roots_cap; i < new_cap; i++) { grown[i].type = ${TYPES.undefined}; grown_active_pos[i] = -1; } porf_gc_native_roots = grown; porf_gc_native_root_free_slots = grown_free_slots; porf_gc_native_root_active = grown_active; porf_gc_native_root_active_pos = grown_active_pos; porf_gc_native_roots_cap = new_cap; } i32 slot; if (porf_gc_native_root_free_slots_len > 0) slot = porf_gc_native_root_free_slots[--porf_gc_native_root_free_slots_len]; else slot = porf_gc_native_roots_len++; porf_gc_native_roots[slot].value = value; porf_gc_native_roots[slot].type = type; porf_gc_native_root_active_pos[slot] = porf_gc_native_root_active_len; porf_gc_native_root_active[porf_gc_native_root_active_len++] = slot; return slot;}
void porf_gc_native_root_remove(i32 slot) { if (slot < 0 || slot >= porf_gc_native_roots_len) return; if (porf_gc_native_roots[slot].type == ${TYPES.undefined}) return; porf_gc_native_roots[slot].value = 0; porf_gc_native_roots[slot].type = ${TYPES.undefined}; porf_gc_native_root_free_slots[porf_gc_native_root_free_slots_len++] = slot; const i32 pos = porf_gc_native_root_active_pos[slot]; const i32 last_slot = porf_gc_native_root_active[--porf_gc_native_root_active_len]; if (pos != porf_gc_native_root_active_len) { porf_gc_native_root_active[pos] = last_slot; porf_gc_native_root_active_pos[last_slot] = pos; } porf_gc_native_root_active_pos[slot] = -1;}
static void porf_gc_mark_native_roots(void) { for (i32 i = 0; i < porf_gc_native_root_active_len; i++) { const i32 slot = porf_gc_native_root_active[i]; porf_gc_mark_js(porf_gc_native_roots[slot].value, porf_gc_native_roots[slot].type); }}
static void porf_gc_mark_array_entries(i32 entries, u32 len) { for (u32 i = 0; i < len; i++) { const jsbits b = *(jsbits*)(MEM + entries + ((u64)i << 3)); if (b == 0) continue; const jsval v = porf_unpack(b); if (porf_gc_type_can_reference(v.type)) porf_gc_mark_js(v.val, v.type); }}
static void porf_gc_mark_array_like(i32 body) { u32 len = *(u32*)(MEM + body); i32 entries = *(u32*)(MEM + body + 4); const u32 capacity = *(u32*)(MEM + body + 8); if (len > capacity) len = capacity; if (entries == body + 16) { if (porf_gc_is_block_start(body)) { const u32 block_size = porf_gc_block_size(body); const u32 max_len = block_size >= 16u ? (block_size - 16u) / 8u : 0u; if (len > max_len) len = max_len; } else if (!porf_gc_static_range(body, 16ull + (u64)len * 8ull)) return; porf_gc_mark_array_entries(entries, len); return; } if (porf_gc_is_block_start(entries)) { const u32 max_len = porf_gc_block_size(entries) / 8u; if (len > max_len) len = max_len; porf_gc_mark_body(entries); porf_gc_set_kind(entries, PORF_GC_KIND_ARRAY_ENTRIES); porf_gc_mark_array_entries(entries, len); return; } if (porf_gc_in_static(entries)) { if (!porf_gc_static_range(entries, (u64)len * 8ull)) return; porf_gc_mark_array_entries(entries, len); }}
static void porf_gc_mark_jsbits(jsbits bits) { const jsval v = porf_unpack(bits); if (porf_gc_type_can_reference(v.type)) porf_gc_mark_js(v.val, v.type);}
static void porf_gc_mark_promise_reaction(u32 raw);
static void porf_gc_mark_promise_reaction_chain(u32 raw) { while (raw != 0) { const u32 next = *(u32*)(MEM + raw + PORF_REACTION_NEXT); porf_gc_mark_promise_reaction(raw); raw = next; }}
static void porf_gc_scan_promise_reaction(u32 raw) { const u8 kind = *(u8*)(MEM + raw + PORF_REACTION_KIND); if (kind == 11) porf_gc_mark_coro((u32)*(u64*)(MEM + raw + PORF_REACTION_HANDLER)); else if (kind != 12) porf_gc_mark_jsbits(*(jsbits*)(MEM + raw + PORF_REACTION_HANDLER)); porf_gc_mark_jsbits(*(jsbits*)(MEM + raw + PORF_REACTION_OUT_PROMISE)); porf_gc_mark_jsbits(*(jsbits*)(MEM + raw + PORF_REACTION_VALUE));}
static void porf_gc_mark_promise_reaction(u32 raw) { if (raw == 0 || !porf_gc_is_block_start((i32)raw)) return; if (!porf_gc_mark_body((i32)raw) && porf_gc_kind((i32)raw) == PORF_GC_KIND_PROMISE_REACTION) return; porf_gc_set_kind((i32)raw, PORF_GC_KIND_PROMISE_REACTION); porf_gc_scan_promise_reaction(raw);}
static void porf_gc_mark_promise_jobs(void) { for (u32 i = 0; i < porf_promise_job_len; i++) { porf_gc_mark_promise_reaction(porf_promise_job_queue[(porf_promise_job_head + i) & (porf_promise_job_cap - 1u)]); }}
static void porf_gc_mark_promise_body(i32 body) { porf_gc_mark_jsbits(*(jsbits*)(MEM + body + PORF_PROMISE_RESULT)); porf_gc_mark_promise_reaction_chain(*(u32*)(MEM + body + PORF_PROMISE_FULFILL_HEAD)); porf_gc_mark_promise_reaction_chain(*(u32*)(MEM + body + PORF_PROMISE_REJECT_HEAD)); porf_gc_mark_jsbits(*(jsbits*)(MEM + body + PORF_PROMISE_PAYLOAD));}
static int porf_gc_has_marked_array_like_type(i32 body) { return porf_gc_has_marked_type(body, ${TYPES.array});}static int porf_gc_has_marked_type_simple(i32 body, i32 type) { return porf_gc_has_marked_type(body, type);}
static int porf_gc_should_rescan_marked_body(i32 body, i32 type) { switch (type) { case ${TYPES.object}: return !porf_gc_has_marked_type(body, type) && porf_gc_object_shape_valid(body); case ${TYPES.array}: return !porf_gc_has_marked_array_like_type(body) && porf_gc_array_like_shape_valid(body); case ${TYPES.promise}: return !porf_gc_has_marked_type_simple(body, type); case ${TYPES.__porffor_closureenv}: case ${TYPES.function}: case ${TYPES.map}: case ${TYPES.set}: case ${TYPES.weakmap}: case ${TYPES.weakset}: case ${TYPES.__porffor_generator}: case ${TYPES.__porffor_asyncgenerator}: case ${TYPES.symbol}: case ${TYPES.weakref}: case ${TYPES.error}: case ${TYPES.aggregateerror}: case ${TYPES.typeerror}: case ${TYPES.referenceerror}: case ${TYPES.syntaxerror}: case ${TYPES.rangeerror}: case ${TYPES.evalerror}: case ${TYPES.urierror}: case ${TYPES.suppressederror}: case ${TYPES.disposablestack}: case ${TYPES.asyncdisposablestack}: case ${TYPES.finalizationregistry}: case ${TYPES.regexp}: case ${TYPES.dataview}: case ${TYPES.uint8clampedarray}: case ${TYPES.uint8array}: case ${TYPES.int8array}: case ${TYPES.uint16array}: case ${TYPES.int16array}: case ${TYPES.uint32array}: case ${TYPES.int32array}: case ${TYPES.float32array}: case ${TYPES.float64array}: case ${TYPES.bigint64array}: case ${TYPES.biguint64array}: return !porf_gc_has_marked_type(body, type); } return 0;}
static void porf_gc_scan_body(i32 body, i32 type) { switch (type) { case ${TYPES.__porffor_closureenv}: { const u32 parent = *(u32*)(MEM + body); if (parent != 0) porf_gc_mark_js((f64)parent, ${TYPES.__porffor_closureenv}); const u32 count = *(u32*)(MEM + body + 4); for (u32 i = 0; i < count; i++) { const u32 slot = body + 8u + i * 16u; const u8 tag = *(u8*)(MEM + slot + 8); if (porf_gc_type_can_reference(tag)) porf_gc_mark_js(*(f64*)(MEM + slot), tag); } break; } case ${TYPES.object}: { const i32 proto = *(u32*)(MEM + body + 8); const i32 proto_type = *(u8*)(MEM + body + 5); if (proto != 0 || proto_type != ${TYPES.undefined}) porf_gc_mark_js((f64)proto, proto_type); u32 size = *(u16*)(MEM + body); const i32 entries = *(u32*)(MEM + body + 12); if (entries != 0 && entries != body + 16) { porf_gc_mark_body(entries); porf_gc_set_kind(entries, PORF_GC_KIND_OBJECT_ENTRIES); if (porf_gc_is_block_start(entries)) { const u32 max_size = porf_gc_block_size(entries) / 20u; if (size > max_size) size = max_size; } } porf_gc_scan_object_entries_range(entries, 0, size); break; } case ${TYPES.array}: porf_gc_mark_array_like(body); break; case ${TYPES.promise}: porf_gc_mark_promise_body(body); break; case ${TYPES.__porffor_generator}: case ${TYPES.__porffor_asyncgenerator}: porf_gc_scan_coro(body); break; case ${TYPES.function}: { const i32 env = *(u32*)(MEM + body + 4); if (env != 0) porf_gc_mark_js((f64)env, ${TYPES.__porffor_closureenv}); break; } case ${TYPES.map}: case ${TYPES.set}: case ${TYPES.weakset}: { const i32 keys = *(u32*)(MEM + body); const i32 vals = *(u32*)(MEM + body + 4); const i32 buckets = *(u32*)(MEM + body + 8); if (keys != 0) porf_gc_mark_js((f64)keys, ${TYPES.array}); if (vals != 0) porf_gc_mark_js((f64)vals, ${TYPES.array}); if (buckets != 0) porf_gc_mark_body(buckets); break; } case ${TYPES.weakmap}: { for (i32 i = 0; i < porf_gc_weakmaps_len; i++) if (porf_gc_weakmaps[i] == (u32)body) goto weakmap_seen; if (porf_gc_weakmaps_len == porf_gc_weakmaps_cap) { const i32 new_cap = porf_gc_weakmaps_cap == 0 ? 64 : porf_gc_weakmaps_cap * 2; u32* grown = realloc(porf_gc_weakmaps, (size_t)new_cap * sizeof(*grown)); if (!grown) abort(); porf_gc_weakmaps = grown; porf_gc_weakmaps_cap = new_cap; } porf_gc_weakmaps[porf_gc_weakmaps_len++] = (u32)body;weakmap_seen: { const i32 keys = *(u32*)(MEM + body); const i32 vals = *(u32*)(MEM + body + 4); const i32 buckets = *(u32*)(MEM + body + 8); if (keys != 0) { porf_gc_mark_body(keys); porf_gc_set_marked_type(keys, ${TYPES.array}); } if (vals != 0) { porf_gc_mark_body(vals); porf_gc_set_marked_type(vals, ${TYPES.array}); } if (buckets != 0) porf_gc_mark_body(buckets); } break; } case ${TYPES.symbol}: case ${TYPES.weakref}: { const jsval v = porf_unpack(*(jsbits*)(MEM + body)); porf_gc_mark_js(v.val, v.type); break; } case ${TYPES.error}: case ${TYPES.aggregateerror}: case ${TYPES.typeerror}: case ${TYPES.referenceerror}: case ${TYPES.syntaxerror}: case ${TYPES.rangeerror}: case ${TYPES.evalerror}: case ${TYPES.urierror}: case ${TYPES.suppressederror}: case ${TYPES.disposablestack}: case ${TYPES.asyncdisposablestack}: case ${TYPES.finalizationregistry}: { const jsval v = porf_unpack(*(jsbits*)(MEM + body)); porf_gc_mark_js(v.val, v.type); break; } case ${TYPES.regexp}: { porf_gc_mark_js((f64)(*(u32*)(MEM + body)), ${TYPES.bytestring}); const i32 re_blob = *(u32*)(MEM + body + 12); if (re_blob != 0) porf_gc_mark_body(re_blob); const i32 re_names = *(u32*)(MEM + body + 16); if (re_names != 0) porf_gc_mark_js((f64)re_names, ${TYPES.array}); break; } case ${TYPES.dataview}: { const i32 ptr = *(u32*)(MEM + body + 4); const i32 byte_offset = *(u32*)(MEM + body + 8); const i32 buffer = ptr - byte_offset - 4; if (ptr != 0) porf_gc_mark_body(buffer); break; } case ${TYPES.uint8clampedarray}: case ${TYPES.uint8array}: case ${TYPES.int8array}: case ${TYPES.uint16array}: case ${TYPES.int16array}: case ${TYPES.uint32array}: case ${TYPES.int32array}: case ${TYPES.float32array}: case ${TYPES.float64array}: case ${TYPES.bigint64array}: case ${TYPES.biguint64array}: { const i32 ptr = *(u32*)(MEM + body + 4); if (ptr != 0) porf_gc_mark_body(ptr - *(u32*)(MEM + body + 8) - 4); break; } }}
static void porf_gc_mark_js(f64 value, i32 type) { // proxies ([ target, handler ]) and builtin iterators are arrays if (type == ${TYPES.proxy}${ITERATOR_TYPES.map(x => ` || type == ${x}`).join('')}) type = ${TYPES.array}; switch (type) { case ${TYPES.undefined}: case ${TYPES.number}: case ${TYPES.boolean}: return; case ${TYPES.numberobject}: case ${TYPES.booleanobject}: porf_gc_mark_boxed_primitive(value, type); return; } const i32 body = porf_gc_value_body(value, type); if (body == 0) return; // static strings hold no references and are never freed if ((u32)body < porf_heap_base && (type == ${TYPES.bytestring} || type == ${TYPES.string})) return; if (porf_gc_is_block_start(body)) { // skip header reads for old or already-marked blocks const u32 g = porf_gc_gran(body); if (porf_gc_minor_mode && porf_gc_bit(PORF_GC_B_YOUNG, g) == 0u) return; if (porf_gc_bit(PORF_GC_B_MARK, g) != 0u && porf_gc_kinds[g] == (u8)type) { porf_gc_scan_young_seen |= porf_gc_minor_mode; return; } if (type == ${TYPES.object} && !porf_gc_object_shape_valid(body)) return; if (!porf_gc_mark_body(body)) { if (porf_gc_should_rescan_marked_body(body, type)) { porf_gc_set_marked_type(body, type); porf_gc_enqueue_mark(body, type); } return; } porf_gc_set_marked_type(body, type); porf_gc_enqueue_mark(body, type); return; } if (!porf_gc_in_static(body)) return; switch (type) { case ${TYPES.object}: if (!porf_gc_static_object_shape_valid(body)) return; break; case ${TYPES.array}: if (!porf_gc_static_array_like_shape_valid(body)) return; break; case ${TYPES.promise}: return; case ${TYPES.__porffor_generator}: case ${TYPES.__porffor_asyncgenerator}: return; case ${TYPES.string}: case ${TYPES.bytestring}: case ${TYPES.function}: case ${TYPES.bigint}: case ${TYPES.symbol}: case ${TYPES.regexp}: case ${TYPES.date}: case ${TYPES.map}: case ${TYPES.set}: case ${TYPES.weakmap}: case ${TYPES.weakset}: case ${TYPES.weakref}: case ${TYPES.error}: case ${TYPES.aggregateerror}: case ${TYPES.typeerror}: case ${TYPES.referenceerror}: case ${TYPES.syntaxerror}: case ${TYPES.rangeerror}: case ${TYPES.evalerror}: case ${TYPES.urierror}: case ${TYPES.suppressederror}: case ${TYPES.disposablestack}: case ${TYPES.asyncdisposablestack}: case ${TYPES.finalizationregistry}: break; default: return; } if (!porf_gc_mark_static(body, type)) return; porf_gc_enqueue_mark(body, type);}
static i32 porf_gc_drain_mark_queue_budget(i32 budget) { i32 done = 0; while (porf_gc_mark_queue_len > 0 && done < budget) { const struct porf_gc_mark_item item = porf_gc_mark_queue[--porf_gc_mark_queue_len]; porf_gc_scan_body((i32)item.body, item.type); done++; } return done;}static void porf_gc_drain_mark_queue(void) { while (porf_gc_mark_queue_len > 0) porf_gc_drain_mark_queue_budget(4096);}
static void porf_gc_scan_object_entries_range(i32 entries, u32 from, u32 to) { for (u32 i = from; i < to; i++) { const i32 entry = entries + (i32)(i * 20u); const i32 key_type = *(u8*)(MEM + entry + 18); const u32 key = *(u32*)(MEM + entry + 4); if (key >= porf_heap_base && porf_gc_type_can_reference(key_type)) porf_gc_mark_js((f64)key, key_type); const u8 flags = *(u8*)(MEM + entry + 16); if ((flags & 1u) != 0u) { const u32 get = *(u32*)(MEM + entry + 8); const u32 set = *(u32*)(MEM + entry + 12); if (get != 0) porf_gc_mark_js((f64)get, ${TYPES.function}); if (set != 0) porf_gc_mark_js((f64)set, ${TYPES.function}); } else { const i32 value_type = *(u8*)(MEM + entry + 17); if (porf_gc_type_can_reference(value_type)) porf_gc_mark_js(porf_load_un_f64(MEM + entry + 8), value_type); } }}
static void porf_gc_scan_object_entries(i32 entries) { if (!porf_gc_is_block_start(entries)) return; porf_gc_scan_object_entries_range(entries, 0, porf_gc_block_size(entries) / 20u);}
static void porf_gc_scan_underlying_store(i32 body) { const u32 len = *(u32*)(MEM + body); for (u32 i = 0; i < len; i++) { const i32 base = body + 8 + (i32)(i * 16u); const jsval original = porf_unpack(*(jsbits*)(MEM + base)); porf_gc_mark_js(original.val, original.type); const i32 underlying = *(u32*)(MEM + base + 8); if (underlying != 0) porf_gc_mark_js((f64)underlying, ${TYPES.object}); }}
// minors skip the promoted prefix (entries are in creation order), returns whether entries were removedstatic int porf_gc_mark_underlying_store(i32 body) { if (!porf_gc_is_block_start(body)) return 1; porf_gc_mark_body(body); porf_gc_set_kind(body, PORF_GC_KIND_UNDERLYING_STORE);
const u32 len = *(u32*)(MEM + body); u32 start = 0; if (porf_gc_minor_mode) { u32 p = porf_gc_underlying_old <= len ? porf_gc_underlying_old : 0u; while (p < len) { const i32 underlying = *(u32*)(MEM + body + 8 + (i32)(p * 16u) + 8); if (underlying <= 0 || porf_gc_bit(PORF_GC_B_YOUNG, porf_gc_gran(underlying)) != 0u) break; p++; } porf_gc_underlying_old = p; start = p; } int changed; do { changed = 0; for (u32 i = start; i < len; i++) { const i32 base = body + 8 + (i32)(i * 16u); const jsval original = porf_unpack(*(jsbits*)(MEM + base)); const i32 underlying = *(u32*)(MEM + base + 8);
if (!porf_gc_in_static(porf_gc_value_body(original.val, original.type)) && !porf_gc_is_marked_js(original.val, original.type)) continue; if (underlying == 0) continue;
const int marked_before = porf_gc_is_marked_js((f64)(u32)underlying, ${TYPES.object}); porf_gc_mark_js((f64)(u32)underlying, ${TYPES.object}); if (!marked_before && porf_gc_is_marked_js((f64)(u32)underlying, ${TYPES.object})) changed = 1; }
if (changed) porf_gc_drain_mark_queue(); } while (changed);
u32 out = start; for (u32 i = start; i < len; i++) { const i32 base = body + 8 + (i32)(i * 16u); const jsval original = porf_unpack(*(jsbits*)(MEM + base)); if (!porf_gc_in_static(porf_gc_value_body(original.val, original.type)) && !porf_gc_is_marked_js(original.val, original.type)) continue;
if (out != i) { const i32 dst = body + 8 + (i32)(out * 16u); *(u64*)(MEM + dst) = *(u64*)(MEM + base); *(u32*)(MEM + dst + 8) = *(u32*)(MEM + base + 8); *(u32*)(MEM + dst + 12) = *(u32*)(MEM + base + 12); }
out++; }
*(u32*)(MEM + body) = out; // every survivor of a major collection is promoted by its sweep if (!porf_gc_minor_mode) porf_gc_underlying_old = out; return out != len;}
static void porf_gc_scan_regex_cache(i32 cache) { while (cache != 0 && porf_gc_is_block_start(cache)) { porf_gc_mark_js((f64)(*(u32*)(MEM + cache + 4)), ${TYPES.bytestring}); porf_gc_mark_js((f64)(*(u32*)(MEM + cache + 8)), ${TYPES.bytestring}); const i32 cache_blob = *(u32*)(MEM + cache + 12); if (cache_blob != 0) porf_gc_mark_body(cache_blob); const i32 cache_names = *(u32*)(MEM + cache + 16); if (cache_names != 0) porf_gc_mark_js((f64)cache_names, ${TYPES.array}); cache = *(u32*)(MEM + cache); }}
static void porf_gc_mark_regex_cache(i32 cache) { while (cache != 0) { if (!porf_gc_is_block_start(cache)) return; porf_gc_mark_body(cache); porf_gc_set_kind(cache, PORF_GC_KIND_REGEX_CACHE); porf_gc_scan_regex_cache(cache); cache = *(u32*)(MEM + cache); }}
static void porf_gc_scan_kind_block(i32 body) { if (!porf_gc_is_block_start(body)) return; const u32 kind = porf_gc_kind(body); switch (kind) { case PORF_GC_KIND_OBJECT_ENTRIES: porf_gc_scan_object_entries(body); break; case PORF_GC_KIND_ARRAY_ENTRIES: porf_gc_mark_array_entries(body, porf_gc_block_size(body) / 8u); break; case PORF_GC_KIND_FUNCTION: { const i32 env = *(u32*)(MEM + body + 4); if (env != 0) porf_gc_mark_js((f64)env, ${TYPES.__porffor_closureenv}); break; } case PORF_GC_KIND_UNDERLYING_STORE: porf_gc_scan_underlying_store(body); break; case PORF_GC_KIND_REGEX_CACHE: porf_gc_scan_regex_cache(body); break; case PORF_GC_KIND_PROMISE_REACTION: porf_gc_scan_promise_reaction((u32)body); break; case PORF_GC_KIND_CORO: porf_gc_scan_coro(body); break; default: if (kind >= 1u && kind <= 195u) porf_gc_scan_body(body, (i32)kind); break; }}
static u32 porf_gc_weakmap_hash(f64 key) { u32 hash = (u32)key; hash = hash >> 3; hash ^= hash >> 16; hash *= 0x7feb352d; hash ^= hash >> 15; return hash;}
static void porf_gc_weakmap_insert_bucket(i32 buckets, u32 capacity, f64 key, u32 index) { if (buckets == 0 || capacity == 0) return; u32 slot = porf_gc_weakmap_hash(key) & (capacity - 1u); while (1) { const i32 bucket_ptr = buckets + (i32)(slot * 4u); if (*(u32*)(MEM + bucket_ptr) == 0u) { *(u32*)(MEM + bucket_ptr) = index + 1u; return; } slot = (slot + 1u) & (capacity - 1u); }}
static i32 porf_gc_array_entries_shallow(i32 arr, u32* len_out) { if (arr == 0) { *len_out = 0; return 0; } u32 len = *(u32*)(MEM + arr); const i32 entries = *(u32*)(MEM + arr + 4); const u32 capacity = *(u32*)(MEM + arr + 8); if (len > capacity) len = capacity; if (entries != arr + 16) porf_gc_mark_body(entries); *len_out = len; return entries;}
static int porf_gc_mark_weakmap_values_once(void) { int changed = 0; for (i32 w = 0; w < porf_gc_weakmaps_len; w++) { const i32 body = (i32)porf_gc_weakmaps[w]; if (!porf_gc_is_block_start(body)) continue; const i32 keys = *(u32*)(MEM + body); const i32 vals = *(u32*)(MEM + body + 4); u32 keys_len = 0, vals_len = 0; const i32 keys_entries = porf_gc_array_entries_shallow(keys, &keys_len); const i32 vals_entries = porf_gc_array_entries_shallow(vals, &vals_len); const u32 len = keys_len < vals_len ? keys_len : vals_len; for (u32 i = 0; i < len; i++) { const jsbits key_bits = *(jsbits*)(MEM + keys_entries + ((u64)i << 3)); if (key_bits == 0xffffffffffffffffull) continue; const jsval key = porf_unpack(key_bits); if (!porf_gc_is_marked_js(key.val, key.type)) continue; const jsval val = porf_unpack(*(jsbits*)(MEM + vals_entries + ((u64)i << 3))); const int marked_before = porf_gc_is_marked_js(val.val, val.type); porf_gc_mark_js(val.val, val.type); if (!marked_before && porf_gc_is_marked_js(val.val, val.type)) changed = 1; } } return changed;}
static void porf_gc_sweep_weakmaps(void) { for (i32 w = 0; w < porf_gc_weakmaps_len; w++) { const i32 body = (i32)porf_gc_weakmaps[w]; if (!porf_gc_is_block_start(body)) continue; const i32 keys = *(u32*)(MEM + body); const i32 vals = *(u32*)(MEM + body + 4); const i32 buckets = *(u32*)(MEM + body + 8); const u32 capacity = *(u32*)(MEM + body + 12); u32 keys_len = 0, vals_len = 0; const i32 keys_entries = porf_gc_array_entries_shallow(keys, &keys_len); const i32 vals_entries = porf_gc_array_entries_shallow(vals, &vals_len); const u32 len = keys_len < vals_len ? keys_len : vals_len; if (buckets != 0 && capacity != 0) memset(MEM + buckets, 0, (size_t)capacity * 4u); for (u32 i = 0; i < len; i++) { const i32 key_ptr = keys_entries + (i32)((u64)i << 3); const jsbits key_bits = *(jsbits*)(MEM + key_ptr); if (key_bits == 0xffffffffffffffffull) continue; const jsval key = porf_unpack(key_bits); if (porf_gc_is_marked_js(key.val, key.type)) { porf_gc_weakmap_insert_bucket(buckets, capacity, key.val, i); continue; } *(jsbits*)(MEM + key_ptr) = 0xffffffffffffffffull; *(jsbits*)(MEM + vals_entries + ((u64)i << 3)) = JV_UNDEFINED_BITS; *(u32*)(MEM + body + 16) += 1u; } }}
static void porf_gc_process_weakmaps(void) { while (porf_gc_mark_weakmap_values_once()) porf_gc_drain_mark_queue(); porf_gc_sweep_weakmaps();}
static void porf_gc_cons_candidate(u32 c);static void porf_gc_cons_mark_block(i32 body) { if (!porf_gc_mark_body(body)) return; const u32 kind = porf_gc_kinds[porf_gc_gran(body)]; if (kind != 0u) { porf_gc_scan_kind_block(body); return; } if (porf_gc_object_shape_valid(body)) { porf_gc_enqueue_mark(body, ${TYPES.object}); return; } if (porf_gc_array_like_shape_valid(body)) { porf_gc_enqueue_mark(body, ${TYPES.array}); return; } const u32 size = porf_gc_block_size(body); for (u32 off = 0; off + 4u <= size; off += 4u) porf_gc_cons_candidate(*(u32*)(MEM + body + off)); for (u32 off = 0; off + 8u <= size; off += 8u) { const f64 d = *(f64*)(MEM + body + off); if (d > 0.0 && d < 4294967296.0) { const i64 iv = (i64)d; if ((f64)iv == d) porf_gc_cons_candidate((u32)(u64)iv); } }}static void porf_gc_cons_candidate(u32 c) { if (c < porf_heap_base || c >= porf_heap_top) return; const u32 pg = c >> PORF_GC_SPAGE_SHIFT; const u8 k = porf_gc_page_kind[pg]; u32 base; if (k == PORF_GC_PK_SMALL) { const u32 cls = porf_gc_pages[pg].cls; const u32 sa = porf_gc_chunk_start(pg); base = sa + ((c - sa) / cls) * cls; } else if (k == PORF_GC_PK_SPAN) { base = pg << PORF_GC_SPAGE_SHIFT; } else if (k == PORF_GC_PK_TAIL) { base = porf_gc_pages[pg].aux << PORF_GC_SPAGE_SHIFT; } else return; porf_gc_cons_mark_block((i32)base);}static void porf_gc_cons_scan_range(const u64* lo, const u64* hi) { for (const u64* w = lo; w < hi; w++) { const u64 v = *w; if (v == 0) continue; porf_gc_cons_candidate((u32)v); porf_gc_cons_candidate((u32)(v >> 32)); f64 d; memcpy(&d, w, 8); if (d > 0.0 && d < 4294967296.0) { const i64 iv = (i64)d; if ((f64)iv == d) porf_gc_cons_candidate((u32)(u64)iv); } }}
static void porf_gc_mark_cons_roots(void) { jmp_buf regs; if (_setjmp(regs) == 0) { porf_gc_cons_scan_range((const u64*)®s, (const u64*)((const char*)®s + sizeof(regs))); } volatile u64 anchor = 0; const u64* lo = (const u64*)(((uintptr_t)&anchor + 7) & ~(uintptr_t)7); const u64* hi = (const u64*)porf_c_stack_top; if (lo < hi) porf_gc_cons_scan_range(lo, hi); if (porf_try_depth > 0) { porf_gc_cons_scan_range((const u64*)porf_try_data, (const u64*)(porf_try_data + porf_try_depth)); } porf_gc_mark_promise_jobs(); porf_gc_mark_coro_roots(); porf_gc_drain_mark_queue();}
static void porf_gc_scan_card_object(u32 base, u32 from, u32 to) { const u8 kind = porf_gc_kinds[porf_gc_gran(base)]; if (kind == PORF_GC_KIND_ARRAY_ENTRIES) { const u32 bs = porf_gc_block_size((i32)base); u32 lo = from > base ? from : base; u32 hi = base + bs < to ? base + bs : to; lo = base + (((lo - base) >> 3) << 3); if (hi > lo) porf_gc_mark_array_entries((i32)lo, (hi - lo) / 8u); return; } if (kind == PORF_GC_KIND_OBJECT_ENTRIES) { const u32 capacity = porf_gc_block_size((i32)base) / 20u; u32 i0 = from > base ? (from - base) / 20u : 0u; u32 i1 = to > base ? (to - base + 19u) / 20u : 0u; if (i1 > capacity) i1 = capacity; if (i1 > i0) porf_gc_scan_object_entries_range((i32)base, i0, i1); return; } porf_gc_scan_kind_block((i32)base);}
static void porf_gc_scan_card(u32 card, int* young_base_present) { const u32 from = card << 9; const u32 to = from + 512u; const u32 pg = from >> PORF_GC_SPAGE_SHIFT; const u8 k = porf_gc_page_kind[pg]; if (k == PORF_GC_PK_SMALL) { const u32 cls = porf_gc_pages[pg].cls; const u32 sa = porf_gc_chunk_start(pg); u32 base = sa + ((from - sa) / cls) * cls; for (; base < to; base += cls) { const u32 g = porf_gc_gran(base); if (porf_gc_bit(PORF_GC_B_ALLOC, g) == 0u) continue; if (porf_gc_bit(PORF_GC_B_YOUNG, g) != 0u) { *young_base_present = 1; continue; } porf_gc_scan_card_object(base, from, to); } return; } u32 head; if (k == PORF_GC_PK_SPAN) head = pg; else if (k == PORF_GC_PK_TAIL) head = porf_gc_pages[pg].aux; else return; const u32 base = head << PORF_GC_SPAGE_SHIFT; const u32 g = porf_gc_gran(base); if (porf_gc_bit(PORF_GC_B_ALLOC, g) == 0u) return; if (porf_gc_bit(PORF_GC_B_YOUNG, g) != 0u) { *young_base_present = 1; return; } porf_gc_scan_card_object(base, from, to);}
static void porf_gc_scan_cards(void) { const u32 c_lo = porf_heap_base >> 9; const u32 c_hi = porf_heap_top >> 9; for (u32 cw = c_lo & ~7u; cw < c_hi; cw += 8u) { u64 w8; memcpy(&w8, porf_gc_cards + cw, 8); if (w8 == 0) continue; for (u32 j = 0; j < 8u; j++) { const u32 c = cw + j; if (c < c_lo || c >= c_hi || porf_gc_cards[c] == 0) continue; int young_base_present = 0; porf_gc_scan_young_seen = 0; porf_gc_scan_card(c, &young_base_present); porf_gc_drain_mark_queue(); porf_gc_cards[c] = (porf_gc_scan_young_seen || young_base_present) ? 1 : 0; } }}
static void porf_gc_partial_push(u32 pg) { struct porf_gc_page* m = &porf_gc_pages[pg]; if ((m->flags & PORF_GC_PF_PARTIAL) != 0u) return; m->flags |= PORF_GC_PF_PARTIAL; m->aux = porf_gc_partial[m->cidx]; porf_gc_partial[m->cidx] = pg + 1u;}
static void porf_gc_partial_remove(u32 pg) { struct porf_gc_page* m = &porf_gc_pages[pg]; u32* link = &porf_gc_partial[m->cidx]; while (*link != 0) { const u32 cur = *link - 1u; if (cur == pg) { *link = porf_gc_pages[cur].aux; m->aux = 0; m->flags &= (u8)~PORF_GC_PF_PARTIAL; return; } link = &porf_gc_pages[cur].aux; }}
// 0 freed, 1 live without young, 2 live with youngstatic int porf_gc_sweep_page_small(u32 pg, int minor, u64* promoted_bytes, u64* live_bytes) { struct porf_gc_page* m = &porf_gc_pages[pg]; const u32 cls = m->cls; const u32 npg = porf_gc_cls_pages[m->cidx]; u64* blk = porf_gc_meta + ((size_t)pg << 5); u32 live = 0, young_left = 0; for (u32 w = 0; w < npg * 8u; w++) { u64* grp = blk + ((size_t)w << 2); u64 a = grp[0], mk = grp[1], y = grp[2], g = grp[3]; const u64 dead = minor ? (y & ~mk) : (a & ~mk); a &= ~dead; u64 newy = 0; if (minor) { const u64 survy = y & mk; const u64 promote = survy & g; newy = survy & ~g; if (promote != 0ull) *promoted_bytes += (u64)__builtin_popcountll(promote) * cls; } grp[0] = a; grp[1] = 0; grp[2] = newy; grp[3] = newy; live += (u32)__builtin_popcountll(a); young_left += (u32)__builtin_popcountll(newy); } if (live == 0) { if ((m->flags & PORF_GC_PF_PARTIAL) != 0u) porf_gc_partial_remove(pg); m->flags = 0; m->cursor = 0; porf_gc_release_run(pg, npg); return 0; } *live_bytes += (u64)live * cls; m->cursor = 0; if (live < (u32)porf_gc_cls_slots[m->cidx]) porf_gc_partial_push(pg); return young_left > 0 ? 2 : 1;}
static int porf_gc_sweep_span(u32 pg, int minor, u64* promoted_bytes, u64* live_bytes) { struct porf_gc_page* m = &porf_gc_pages[pg]; const u32 npg = m->aux; const u32 body = pg << PORF_GC_SPAGE_SHIFT; const u32 g = porf_gc_gran(body); const int young = porf_gc_bit(PORF_GC_B_YOUNG, g) != 0u; const int marked = porf_gc_bit(PORF_GC_B_MARK, g) != 0u; if (minor && !young) { *live_bytes += (u64)npg * PORF_GC_SPAGE; return 1; } if (!marked) { porf_gc_bit_clear(PORF_GC_B_ALLOC, g); porf_gc_bit_clear(PORF_GC_B_YOUNG, g); porf_gc_bit_clear(PORF_GC_B_AGED, g); m->flags = 0; porf_gc_release_run(pg, npg); return 0; } porf_gc_bit_clear(PORF_GC_B_MARK, g); *live_bytes += (u64)npg * PORF_GC_SPAGE; if (!minor) { porf_gc_bit_clear(PORF_GC_B_YOUNG, g); porf_gc_bit_clear(PORF_GC_B_AGED, g); return 1; } if (porf_gc_bit(PORF_GC_B_AGED, g) != 0u) { porf_gc_bit_clear(PORF_GC_B_YOUNG, g); porf_gc_bit_clear(PORF_GC_B_AGED, g); *promoted_bytes += (u64)npg * PORF_GC_SPAGE; return 1; } porf_gc_bit_set(PORF_GC_B_AGED, g); return 2;}
static void porf_gc_madv_dontneed(u32 start, size_t len) {#if PORF_CAN_DECOMMIT && defined(MADV_DONTNEED) (void)madvise(MEM + start, len, MADV_DONTNEED);#elif PORF_CAN_DECOMMIT && defined(MADV_FREE) (void)madvise(MEM + start, len, MADV_FREE);#else (void)start; (void)len;#endif}
static void porf_gc_discard_range(u32 start, u32 end) { if (end <= start) return; porf_gc_madv_dontneed(start, (size_t)(end - start));}
static void porf_gc_retreat_heap_top(void) { while (porf_heap_top > porf_heap_base) { const u32 pg = (porf_heap_top >> PORF_GC_SPAGE_SHIFT) - 1u; if ((porf_gc_free_pages[pg >> 6] & (1ull << (pg & 63u))) == 0u) break; porf_gc_free_pages[pg >> 6] &= ~(1ull << (pg & 63u)); if (porf_gc_free_page_count > 0) porf_gc_free_page_count--; porf_heap_top -= PORF_GC_SPAGE; }}
static void porf_gc_discard_free_runs(void) { const u32 top = porf_heap_top >> PORF_GC_SPAGE_SHIFT; u32 run_start = 0, run_len = 0; for (u32 pg = porf_heap_base >> PORF_GC_SPAGE_SHIFT; pg < top; pg++) { if ((porf_gc_free_pages[pg >> 6] & (1ull << (pg & 63u))) != 0u) { if (run_len == 0) run_start = pg; run_len++; } else { if (run_len >= 32u) porf_gc_discard_range(run_start << PORF_GC_SPAGE_SHIFT, (run_start + run_len) << PORF_GC_SPAGE_SHIFT); run_len = 0; } } if (run_len >= 32u) porf_gc_discard_range(run_start << PORF_GC_SPAGE_SHIFT, (run_start + run_len) << PORF_GC_SPAGE_SHIFT);}
static void porf_gc_maybe_trim_memory(void) { const u32 trim_granule = 1u << 20; const u32 keep_slack = ${prefs.nativeFetch ? '0u' : '16u * 1024u * 1024u'};
u64 wanted64 = ((u64)porf_heap_top + keep_slack + trim_granule - 1ull) & ~((u64)trim_granule - 1ull); const u64 min_committed = ((u64)porf_heap_base + 65536ull + trim_granule - 1ull) & ~((u64)trim_granule - 1ull); if (wanted64 < min_committed) wanted64 = min_committed; if (wanted64 >= porf_heap_committed) return;
const u32 wanted = (u32)wanted64; const u64 trim_bytes64 = porf_heap_committed - wanted64; if (trim_bytes64 > SIZE_MAX) return; const size_t trim_bytes = (size_t)trim_bytes64; if (trim_bytes < trim_granule) return;
if (!PORF_CAN_DECOMMIT) return; // replacing the tail drops its pages but keeps the reservation if (mmap(MEM + wanted, trim_bytes, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) return; porf_heap_committed = wanted;}
static inline u64 porf_gc_allocation_debt_threshold(void) { u64 threshold = porf_gc_last_live_bytes / 2u; if (threshold < porf_gc_allocation_debt_min) return porf_gc_allocation_debt_min; if (threshold > porf_gc_allocation_debt_max) return porf_gc_allocation_debt_max; return threshold;}static inline int porf_gc_can_grow_for_request(u32 request_size) { if (porf_gc_last_live_bytes == 0) return 0; const u64 allocation_bytes = 8u + (u64)request_size; const u64 heap_slack = (u64)(PORF_ARENA_RESERVE - porf_heap_top); if (heap_slack <= 16ull * 1024ull * 1024ull + allocation_bytes) return 0; const u64 heap_bytes = porf_heap_top > porf_heap_base ? (u64)(porf_heap_top - porf_heap_base) : 0u; const u64 live_bytes = porf_gc_last_live_bytes; u64 grow_window = live_bytes + 64ull * 1024ull * 1024ull; if (grow_window < 1ull * 1024ull * 1024ull) grow_window = 1ull * 1024ull * 1024ull; return heap_bytes + allocation_bytes <= grow_window;}static inline int porf_gc_should_collect_for(u32 request_size) { const u64 threshold = porf_gc_allocation_debt_threshold(); if (porf_gc_allocation_debt < threshold) return 0; if (porf_gc_last_live_bytes == 0) return !porf_gc_can_grow_for_request(request_size); if (porf_gc_allocation_debt >= threshold * 4u) return 1; if (porf_gc_can_grow_for_request(request_size)) return 0; return 1;}
static void porf_gc_minor(void) { porf_gc_collect(1); if (porf_gc_full_due(0)) porf_gc_collect(0);}${prefs.nativeFetch ? `// full under the debt policy, otherwise minor after enough page claims${st}int porf_gc_idle_minor_due(void) { return porf_gc_window_bytes + porf_gc_span_bytes >= (1ll << 20);}${st}void porf_gc_collect_idle(void) { if (porf_gc_should_collect_for(0)) { porf_gc_collect(0); return; } if (porf_gc_idle_minor_due()) { porf_gc_window_bytes = 0; porf_gc_span_bytes = 0; porf_gc_minor(); }}` : ''}\
${st}void porf_gc_collect(int minor) { if (porf_heap_base == 0) return; for (i32 ci = 0; ci < PORF_GC_NCLASSES; ci++) porf_gc_publish_window(ci); porf_gc_minor_mode = minor; if (!minor) { porf_gc_claimed_since_full = 0; porf_gc_promoted_since_full = 0; const size_t w_lo = (size_t)(porf_heap_base >> 4 >> 6); const size_t w_hi = (size_t)(((porf_heap_top >> 4) + 63u) >> 6); for (size_t w = w_lo; w < w_hi; w++) porf_gc_meta[(((w >> 3) << 5) | ((w & 7u) << 2)) + PORF_GC_B_MARK] = 0; } porf_gc_allocation_debt = 0; porf_gc_static_marks_len = 0; if (porf_gc_static_marks != NULL) memset(porf_gc_static_marks, 0, (size_t)porf_gc_static_marks_cap * sizeof(*porf_gc_static_marks)); porf_gc_mark_queue_len = 0; porf_gc_weakmaps_len = 0; porf_gc_boxed_marks_len = 0; porf_gc_mark_native_roots(); porf_gc_mark_cons_roots(); porf_gc_mark_js(porf_exception.val, porf_exception.type); porf_gc_mark_js(porf_iter_open.val, porf_iter_open.type); porf_gc_mark_global_roots(); porf_gc_drain_mark_queue(); if (minor) porf_gc_scan_cards(); porf_gc_mark_global_raw_roots(); porf_gc_drain_mark_queue(); porf_gc_process_weakmaps(); porf_gc_drain_mark_queue();
u64 promoted = 0, live_bytes = 0; if (minor) { i32 out = 0; for (i32 i = 0; i < porf_gc_touched_len; i++) { const u32 pg = porf_gc_touched[i]; const u8 k = porf_gc_page_kind[pg]; int status = 0; if (k == PORF_GC_PK_SMALL) status = porf_gc_sweep_page_small(pg, 1, &promoted, &live_bytes); else if (k == PORF_GC_PK_SPAN) status = porf_gc_sweep_span(pg, 1, &promoted, &live_bytes); if (status == 2) { porf_gc_touched[out++] = pg; continue; } porf_gc_pages[pg].flags &= (u8)~PORF_GC_PF_TOUCHED; } porf_gc_touched_len = out; porf_gc_promoted_since_full += (i64)promoted; porf_gc_retreat_heap_top(); porf_gc_maybe_trim_memory(); } else { for (i32 i = 0; i < porf_gc_touched_len; i++) porf_gc_pages[porf_gc_touched[i]].flags &= (u8)~PORF_GC_PF_TOUCHED; porf_gc_touched_len = 0; for (i32 ci = 0; ci < PORF_GC_NCLASSES; ci++) { u32 pg1 = porf_gc_partial[ci]; while (pg1 != 0) { const u32 next = porf_gc_pages[pg1 - 1u].aux; porf_gc_pages[pg1 - 1u].flags &= (u8)~PORF_GC_PF_PARTIAL; pg1 = next; } porf_gc_partial[ci] = 0; } const u32 top = porf_heap_top >> PORF_GC_SPAGE_SHIFT; for (u32 pg = porf_heap_base >> PORF_GC_SPAGE_SHIFT; pg < top; pg++) { const u8 k = porf_gc_page_kind[pg]; if (k == PORF_GC_PK_SMALL) { if ((porf_gc_pages[pg].flags & PORF_GC_PF_TAIL) != 0u) continue; porf_gc_sweep_page_small(pg, 0, &promoted, &live_bytes); } else if (k == PORF_GC_PK_SPAN) porf_gc_sweep_span(pg, 0, &promoted, &live_bytes); } porf_gc_last_live_bytes = live_bytes; porf_gc_live_bytes = live_bytes; memset(porf_gc_cards + (porf_heap_base >> 9), 0, (size_t)((porf_heap_top - porf_heap_base) >> 9) + 1); porf_gc_run_cache_drain(); porf_gc_retreat_heap_top(); porf_gc_discard_free_runs(); porf_gc_maybe_trim_memory(); } memset(porf_gc_active, 0, sizeof(porf_gc_active)); porf_gc_minor_mode = 0;}`;};
// jsval encoding: f64 numbers are themselves, else 0xFFF8 (sign + quiet-NaN) << 48 |// type:8 << 43 | payload:32. hardware qNaN is 0x7FF8 (sign clear) so never collides,// sign-set NaNs from raw bytes are canonicalized at Float64Array/DataView reads (porf_canon)const RUNTIME_HEAD = (prefs, toStr = null, iterClose = null, bigints = true) => { const st = 'static '; const sti = 'static inline '; return `// generated by porffor ${globalThis.version}#include <stdint.h>#include <string.h>#include <stdio.h>#include <stdlib.h>#include <setjmp.h>#include <math.h>
#include <signal.h>#include <unistd.h>#include <dirent.h>#include <sys/mman.h>#include <sys/stat.h>#ifndef __wasi__#include <sys/wait.h>#endif#include <time.h>
${prefs.repl ? `static int porf_repl_output_enabled = 1;#define printf(...) (porf_repl_output_enabled ? fprintf(stdout, __VA_ARGS__) : 0)` : ''}
typedef uint8_t u8;typedef uint16_t u16;typedef int32_t i32;typedef uint32_t u32;typedef int64_t i64;typedef uint64_t u64;typedef float f32;typedef double f64;typedef struct jsval { f64 val; i32 type; } jsval;typedef u64 jsbits;
${prefs.nativeFetch ? `typedef struct NativeFetchResponseParts { i32 status; jsval body; jsval headers;} NativeFetchResponseParts;
static _Thread_local NativeFetchResponseParts* porf_native_fetch_response_parts_out = NULL;` : ''}
// arena base: reserved once at init, NEVER moves. a fixed constant VA is// not reliably free on macOS (per-process dyld/malloc randomization), and// x86-64 has no [imm64+reg] addressing so a constant folds to a register// materialization anyway - a once-set global compiles to the same code.// 0x400000000 is used as a hint for deterministic debugging when free.${prefs.nativeFetch ? '' : st}u8* porf_mem;#define MEM porf_mem#define PORF_NOINLINE __attribute__((noinline))// run-once code (module init, top level): optimize for size whatever -O the unit gets#if defined(__clang__)#define PORF_ONCE __attribute__((noinline, minsize))#elif defined(__GNUC__)#define PORF_ONCE __attribute__((noinline, optimize("Os")))#else#define PORF_ONCE#endif#define PORF_NORETURN __attribute__((cold, noinline, noreturn))#ifndef MAP_NORESERVE#define MAP_NORESERVE 0#endif#ifdef __wasi__// wasi's malloc-backed mmap ignores fixed hints and cannot change protections// or decommit, so use a small, fully committed arena#define PORF_ARENA_HINT NULL#define PORF_ARENA_RESERVE (1ull << 26)#define PORF_MMAP_RESERVE_PROT (PROT_READ | PROT_WRITE)#define PORF_CAN_DECOMMIT 0#else#define PORF_ARENA_HINT ((void*)0x400000000ull)#define PORF_ARENA_RESERVE (1ull << 32)#define PORF_MMAP_RESERVE_PROT PROT_NONE#define PORF_CAN_DECOMMIT 1#endifextern const u32 porf_static_end;#define PORF_GC_ENABLED ${prefs.gc === false ? 0 : 1}
#define JV_PATTERN 0xFFF8000000000000ull#define JV_TYPE_MASK 0x07F8000000000000ull#define JV_UNDEFINED_BITS (JV_PATTERN | ((u64)${TYPES.undefined} << 43))#define JV_UNDEFINED ((jsval){0.0, ${TYPES.undefined}})#define JV_ZERO_BITS (JV_PATTERN | ((u64)${TYPES.number} << 43))
#define PORF_PROMISE_RESULT 0#define PORF_PROMISE_FULFILL_HEAD 8#define PORF_PROMISE_FULFILL_TAIL 12#define PORF_PROMISE_REJECT_HEAD 16#define PORF_PROMISE_REJECT_TAIL 20#define PORF_PROMISE_PAYLOAD 24#define PORF_PROMISE_STATE 32#define PORF_PROMISE_FLAGS 33#define PORF_PROMISE_HANDLED 34#define PORF_PROMISE_SIZE 40
#define PORF_REACTION_HANDLER 0#define PORF_REACTION_OUT_PROMISE 8#define PORF_REACTION_VALUE 16#define PORF_REACTION_NEXT 24#define PORF_REACTION_PAYLOAD 28#define PORF_REACTION_KIND 32#define PORF_REACTION_FLAGS 33#define PORF_REACTION_SIZE 40
#define PORF_GC_KIND_PROMISE_REACTION 248u
static inline f64 porf_bits_to_f64_bits(u64 b) { f64 d; memcpy(&d, &b, 8); return d; }static inline u64 porf_f64_to_bits(f64 d) { u64 b; memcpy(&b, &d, 8); return b; }static inline f64 porf_bits_to_f32(u32 b) { f32 f; memcpy(&f, &b, 4); return (f64)f; }static inline u32 porf_f32_to_bits(f64 d) { f32 f = (f32)d; u32 b; memcpy(&b, &f, 4); return b; }static inline f64 porf_jsval_to_f64(jsval v) { return v.val; }#define porf_bits_to_f64(x) _Generic((x), jsval: porf_jsval_to_f64, default: porf_bits_to_f64_bits)(x)
static inline int porf_jv_is_num(jsval v) { return v.type == ${TYPES.number}; }static inline i32 porf_jv_type(jsval v) { return v.type; }static inline jsval porf_box_num(f64 d) { return (jsval){d, ${TYPES.number}}; }static inline jsval porf_box(f64 payload, i32 type) { return type == ${TYPES.number} ? porf_box_num(payload) : (jsval){payload, type};}static u32 porf_bigint_heapify(f64 d);static inline jsbits porf_pack(jsval v) { if (v.type == ${TYPES.number}) { const jsbits b = porf_f64_to_bits(v.val); // negative quiet NaNs collide with the boxed encoding: canonicalize return (b & JV_PATTERN) == JV_PATTERN ? 0x7FF8000000000000ull : b; } if (v.type == ${TYPES.bigint} && !(v.val >= 0.0 && v.val < 4294967296.0)) { // bigints not fitting the 32-bit payload are packed as a heap pointer, flagged by bit 42 const u32 ptr = v.val >= 2251799813685248.0 && v.val < 2251804108652544.0 ? (u32)(v.val - 2251799813685248.0) : porf_bigint_heapify(v.val); return JV_PATTERN | ((u64)${TYPES.bigint} << 43) | (1ull << 42) | (u64)ptr; } return JV_PATTERN | ((u64)(v.type & 0xFF) << 43) | (u64)(u32)v.val;}${sti}jsbits porf_arr_pack(jsval v) { const jsbits b = porf_pack(v); return b == 0 ? JV_ZERO_BITS : b;}static inline jsval porf_unpack(jsbits b) { if ((b & JV_PATTERN) != JV_PATTERN) return porf_box_num(porf_bits_to_f64(b)); if (b & (1ull << 42)) return (jsval){(f64)(u32)b + 2251799813685248.0, ${TYPES.bigint}}; return (jsval){(f64)(u32)b, (i32)((b >> 43) & 0xFF)};}static inline f64 porf_canon(f64 d) { return d == d ? d : porf_bits_to_f64(0x7FF8000000000000ull); }
static u32* porf_promise_job_queue = NULL;static u32 porf_promise_job_head = 0;static u32 porf_promise_job_len = 0;static u32 porf_promise_job_cap = 0;
static void (*porf_promise_run_coro_reaction_impl)(u32) = NULL;static void (*porf_native_fetch_run_response_reaction_impl)(u32) = NULL;
static u32 porf_native_fetch_set_timer(jsval callback, jsval args, jsval delay_value, i32 repeat);static void porf_native_fetch_clear_timer(jsval timer);
static void porf_promise_enqueue_job(u32 reaction) { if (reaction == 0) return; if (porf_promise_job_len == porf_promise_job_cap) { const u32 old_cap = porf_promise_job_cap; const u32 new_cap = old_cap == 0 ? 64u : old_cap * 2u; u32* next = (u32*)malloc((size_t)new_cap * sizeof(u32)); if (!next) abort(); for (u32 i = 0; i < porf_promise_job_len; i++) { next[i] = porf_promise_job_queue[(porf_promise_job_head + i) & (old_cap - 1u)]; } free(porf_promise_job_queue); porf_promise_job_queue = next; porf_promise_job_cap = new_cap; porf_promise_job_head = 0; } porf_promise_job_queue[(porf_promise_job_head + porf_promise_job_len) & (porf_promise_job_cap - 1u)] = reaction; porf_promise_job_len++;}
static u32 porf_promise_dequeue_job(void) { if (porf_promise_job_len == 0) return 0; const u32 reaction = porf_promise_job_queue[porf_promise_job_head]; porf_promise_job_head = (porf_promise_job_head + 1u) & (porf_promise_job_cap - 1u); porf_promise_job_len--; if (porf_promise_job_len == 0) porf_promise_job_head = 0; return reaction;}
static void porf_promise_run_coro_reaction(u32 reaction) { if (!porf_promise_run_coro_reaction_impl) abort(); porf_promise_run_coro_reaction_impl(reaction);}
static void porf_native_fetch_run_response_reaction(u32 reaction) { if (!porf_native_fetch_run_response_reaction_impl) abort(); porf_native_fetch_run_response_reaction_impl(reaction);}
static inline i32 porf_jv_eq(jsval a, jsval b) { if (a.type != b.type) return 0; if (a.type == ${TYPES.number}) return a.val == b.val; return (u32)a.val == (u32)b.val;}
// truthiness: number path = one compare; boxed = payload check, strings load lengthstatic inline i32 porf_truthy(jsval v) { if (v.type == ${TYPES.number}) return v.val == v.val && v.val != 0.0; if (v.type == ${TYPES.string} || v.type == ${TYPES.bytestring}) return *(u32*)(MEM + (u32)v.val) != 0u; // Boolean/Number objects hold their primitive inline but are always truthy return (u32)v.val != 0u || v.type == ${TYPES.booleanobject} || v.type == ${TYPES.numberobject};}
static inline i32 porf_falsy(jsval v) { if (v.type == ${TYPES.number}) return v.val != v.val || v.val == 0.0; if (v.type == ${TYPES.string} || v.type == ${TYPES.bytestring}) return *(u32*)(MEM + (u32)v.val) == 0u; return (u32)v.val == 0u && v.type != ${TYPES.booleanobject} && v.type != ${TYPES.numberobject};}
static inline i32 porf_nullish(jsval v) { return v.type == ${TYPES.undefined} || (v.type == ${TYPES.object} && (u32)v.val == 0u);}
// saturating f64 -> int (JS semantics handled above; this is trunc_sat)static inline i32 porf_f64_to_i32(f64 d) { if (d != d) return 0; if (d <= -2147483648.0) return -2147483648; if (d >= 2147483647.0) return 2147483647; return (i32)d;}static inline u32 porf_f64_to_u32(f64 d) { if (d != d || d <= 0.0) return 0; if (d >= 4294967295.0) return 4294967295u; return (u32)d;}
static inline i32 porf_clz32(u32 x) { return x ? __builtin_clz(x) : 32; }static inline i32 porf_ctz32(u32 x) { return x ? __builtin_ctz(x) : 32; }static inline u32 porf_rotl32(u32 x, u32 k) { k &= 31u; return (x << k) | (x >> ((0u - k) & 31u)); }static inline u32 porf_rotr32(u32 x, u32 k) { k &= 31u; return (x >> k) | (x << ((0u - k) & 31u)); }static inline u64 porf_rotl64(u64 x, u64 k) { k &= 63ull; return (x << k) | (x >> ((0ull - k) & 63ull)); }static inline u64 porf_rotr64(u64 x, u64 k) { k &= 63ull; return (x >> k) | (x << ((0ull - k) & 63ull)); }static inline f64 porf_round(f64 d) { f64 r = floor(d); if (d - r >= 0.5) r += 1.0; return copysign(r, d); }static inline f64 porf_nearest(f64 d) { f64 r = round(d); if (fabs(d - trunc(d)) == 0.5 && ((i64)r & 1)) r -= d < 0 ? -1.0 : 1.0; return r; }static inline f64 porf_f64_min(f64 a, f64 b) { return a != a ? a : b != b ? b : a < b ? a : b; }static inline f64 porf_f64_max(f64 a, f64 b) { return a != a ? a : b != b ? b : a > b ? a : b; }
static struct timespec porf_performance_start;static f64 porf_performance_time_origin_value = 0.0;static int porf_performance_started = 0;
static inline void porf_performance_init(void) { if (!porf_performance_started) { struct timespec real; clock_gettime(CLOCK_REALTIME, &real); clock_gettime(CLOCK_MONOTONIC, &porf_performance_start); porf_performance_time_origin_value = (f64)real.tv_sec * 1000.0 + (f64)real.tv_nsec / 1.0e6; porf_performance_started = 1; }}
${sti}f64 porf_performance_now(void) { porf_performance_init();
struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now);
i64 sec = (i64)now.tv_sec - (i64)porf_performance_start.tv_sec; i64 nsec = (i64)now.tv_nsec - (i64)porf_performance_start.tv_nsec; if (nsec < 0) { sec--; nsec += 1000000000ll; } return (f64)sec * 1000.0 + (f64)nsec / 1.0e6;}
${sti}f64 porf_performance_time_origin(void) { porf_performance_init(); return porf_performance_time_origin_value;}
// unaligned access (DataView, object entry values): memcpy folds to plain loads on x86/arm// tcc: plain derefs instead - it emits real memcpy calls, and it never optimizes on alignment UB#ifdef __TINYC__#define porf_load_un_u16(p) (*(const u16*)(p))#define porf_load_un_u32(p) (*(const u32*)(p))#define porf_load_un_u64(p) (*(const u64*)(p))#define porf_load_un_f32(p) (*(const f32*)(p))#define porf_load_un_f64(p) (*(const f64*)(p))#define porf_store_un_u16(p, v) (*(u16*)(p) = (v))#define porf_store_un_u32(p, v) (*(u32*)(p) = (v))#define porf_store_un_u64(p, v) (*(u64*)(p) = (v))#define porf_store_un_f32(p, v) (*(f32*)(p) = (v))#define porf_store_un_f64(p, v) (*(f64*)(p) = (v))#else#define PORF_UN(ctype) \\ static inline ctype porf_load_un_##ctype(const u8* p) { ctype v; memcpy(&v, p, sizeof v); return v; } \\ static inline void porf_store_un_##ctype(u8* p, ctype v) { memcpy(p, &v, sizeof v); }PORF_UN(u16) PORF_UN(u32) PORF_UN(u64) PORF_UN(f32) PORF_UN(f64)#endif
// exceptions: setjmp-based, exception is a jsval${st}jmp_buf* porf_try_data;${st}i32 porf_try_cap = 0;${st}i32 porf_try_depth = 0;${st}jsval porf_exception = {0.0, ${TYPES.undefined}};
${sti}jmp_buf* porf_try_ensure(void) { if (porf_try_depth > porf_try_cap) { while (porf_try_depth > porf_try_cap) porf_try_cap = porf_try_cap ? porf_try_cap << 1 : 8; porf_try_data = (jmp_buf*)realloc(porf_try_data, (size_t)porf_try_cap * sizeof(jmp_buf)); if (!porf_try_data) abort(); } return porf_try_data;}
// innermost open iterator record of the running coroutine${st}jsval porf_iter_open = {0.0, ${TYPES.undefined}};${st}i32 porf_iter_base = 0;${iterClose ? `static void porf_iter_unwind(void);` : ''}\${toStr ? `jsval ${toStr}(jsval);` : ''}\PORF_NORETURN ${st}void porf_throw(jsval v) { porf_exception = v;${iterClose ? ` porf_iter_unwind();` : ''}\ if (porf_try_depth > 0) _longjmp(porf_try_data[porf_try_depth - 1], 1);${toStr ? ` static i32 _uncaught_busy = 0; if (!_uncaught_busy) { _uncaught_busy = 1; const jsval _s = ${toStr}(v); const i32 _st = porf_jv_type(_s); const u32 _sp = (u32)_s.val; if (_st == ${TYPES.bytestring} && _sp) { fprintf(stderr, "Uncaught %.*s\\n", (int)*(u32*)(MEM + _sp), (const char*)(MEM + _sp + 4)); exit(1); } if (_st == ${TYPES.string} && _sp) { const u32 _sl = *(u32*)(MEM + _sp); fprintf(stderr, "Uncaught "); for (u32 _i = 0; _i < _sl; _i++) { const u16 _c = porf_load_un_u16(MEM + _sp + 4 + _i * 2); fputc(_c < 128 ? (int)_c : '?', stderr); } fputc('\\n', stderr); exit(1); } }` : ''}\ fprintf(stderr, "Uncaught exception\\n"); exit(1);}
// internal throws construct a standard error (message jsval at +0, like the error// builtins) so a caught internal error behaves identically to a \`new X(msg)\` one${sti}u32 porf_alloc(u32 bytes, u32 typeId);PORF_NORETURN ${st}void porf_throw_new(i32 errType, u32 msgId) { const u32 p = porf_alloc(8, (u32)errType); *(jsbits*)(MEM + p) = JV_PATTERN | ((u64)${TYPES.bytestring} << 43) | msgId; porf_throw(porf_box((f64)p, errType));}
PORF_NORETURN ${st}void porf_unreachable(const char* msg) { fprintf(stderr, "porffor: unreachable%s%s\\n", msg ? ": " : "", msg ? msg : ""); abort();}
${prefs.gc === false ? PORF_BUMP_ALLOC() : PORF_GC_ALLOC(prefs)}
// array: [len i32 @0][ent u32 @4][cap i32 @8]; entries = jsval[cap]// object: [count i32 @0][bcap i32 @4][ent u32 @8][buckets u32 @12]// entries = {key jsval, val jsval}[count] in insertion order// buckets = i32[bcap] entry indices, -1 empty (ordered hashmap)// bytestring: [len u32 @0][bytes @4]// function: [fnIdx u32 @0][env u32 @4]; env = jsval slots// array header padded to 16 so inline entries stay 8-aligned#define PORF_ARR_LEN(a) (*(i32*)(MEM + (a)))#define PORF_ARR_ENT(a) (*(u32*)(MEM + (a) + 4))#define PORF_ARR_CAP(a) (*(i32*)(MEM + (a) + 8))// cached underlying object (props beyond the elements), see __Porffor_object_underlyingPtr#define PORF_ARR_UNDERLYING(a) (*(u32*)(MEM + (a) + 12))
${st}u32 porf_arr_new(i32 len, i32 cap) { if (cap < len) cap = len; if (cap < 4) cap = 4; const u32 a = porf_alloc(16 + ((u32)cap << 3), ${TYPES.array}); PORF_ARR_LEN(a) = len; PORF_ARR_ENT(a) = a + 16; PORF_ARR_CAP(a) = cap; PORF_ARR_UNDERLYING(a) = 0; memset(MEM + PORF_ARR_ENT(a), 0, (size_t)cap << 3); return a;}
// length can exceed capacity (a.length = n doesn't grow): slots past cap are holes${sti}int porf_arr_has_own(u32 a, u32 i) { if (i >= (u32)PORF_ARR_LEN(a) || i >= (u32)PORF_ARR_CAP(a)) return 0; return *(jsbits*)(MEM + PORF_ARR_ENT(a) + ((u64)i << 3)) != 0;}
${sti}jsval porf_arr_get(u32 a, u32 i) { if (i >= (u32)PORF_ARR_LEN(a) || i >= (u32)PORF_ARR_CAP(a)) return JV_UNDEFINED; const jsbits b = *(jsbits*)(MEM + PORF_ARR_ENT(a) + ((u64)i << 3)); if (b == 0) return JV_UNDEFINED; return porf_unpack(b);}
${st}u32 porf_arr_grow(u32 a, i32 need) { i32 cap = PORF_ARR_CAP(a); if (need <= cap) return PORF_ARR_ENT(a); const i32 copy = PORF_ARR_LEN(a) < cap ? PORF_ARR_LEN(a) : cap; while (cap < need) cap += cap >> 1 > 4 ? cap >> 1 : 4; const u32 ent = porf_alloc((u32)cap << 3, 0); memcpy(MEM + ent, MEM + PORF_ARR_ENT(a), (size_t)copy << 3); memset(MEM + ent + ((u64)copy << 3), 0, ((size_t)cap - (size_t)copy) << 3); PORF_ARR_ENT(a) = ent; PORF_ARR_CAP(a) = cap; porf_gc_barrier(a, ${TYPES.array}); return ent;}
${st}void porf_arr_set(u32 a, u32 i, jsval v) { const i32 len = PORF_ARR_LEN(a); if (i >= (u32)PORF_ARR_CAP(a)) porf_arr_grow(a, (i32)i + 1); if (i >= (u32)len) PORF_ARR_LEN(a) = (i32)i + 1; *(jsbits*)(MEM + PORF_ARR_ENT(a) + ((u64)i << 3)) = porf_arr_pack(v); if (porf_gc_type_can_reference(v.type)) porf_gc_barrier(a, ${TYPES.array});}${iterClose ? `${st}void porf_iter_link(jsval rec) { porf_arr_set((u32)rec.val, 4, porf_iter_open); porf_arr_set((u32)rec.val, 5, porf_box_num((f64)(porf_try_depth - porf_iter_base))); porf_iter_open = rec;}
${st}void porf_iter_unlink(void) { porf_iter_open = porf_arr_get((u32)porf_iter_open.val, 4);}
jsval ${iterClose}(jsval);
// close the records a throw leaves, dropping errors from closing unless it is a generator returnstatic void porf_iter_unwind(void) { jsval exception = porf_exception; while (porf_jv_type(porf_iter_open) != ${TYPES.undefined} && porf_iter_base + (i32)porf_arr_get((u32)porf_iter_open.val, 5).val >= porf_try_depth) { const jsval rec = porf_iter_open; porf_iter_open = porf_arr_get((u32)rec.val, 4); const i32 depth = porf_try_depth++; if (_setjmp(porf_try_ensure()[depth]) == 0) ${iterClose}(rec); else if (porf_jv_eq(exception, porf_box(0.0, ${TYPES.__porffor_generator}))) exception = porf_exception; porf_try_depth = depth; } porf_exception = exception;}` : ''}${st}void porf_arr_delete(u32 a, u32 i) { if (i >= (u32)PORF_ARR_LEN(a) || i >= (u32)PORF_ARR_CAP(a)) return; *(jsbits*)(MEM + PORF_ARR_ENT(a) + ((u64)i << 3)) = 0;}
${st}void porf_arr_set_len(u32 a, u32 new_len) { const u32 old_len = (u32)PORF_ARR_LEN(a); if (new_len < old_len) { const u32 cap = (u32)PORF_ARR_CAP(a); const u32 clear = old_len < cap ? old_len : cap; if (new_len < clear) memset(MEM + PORF_ARR_ENT(a) + ((u64)new_len << 3), 0, ((size_t)clear - new_len) << 3); } PORF_ARR_LEN(a) = (i32)new_len;}
${st}jsval porf_arr_push(u32 a, jsval v) { const i32 len = PORF_ARR_LEN(a); porf_arr_grow(a, len + 1); *(jsbits*)(MEM + PORF_ARR_ENT(a) + ((u64)len << 3)) = porf_arr_pack(v); PORF_ARR_LEN(a) = len + 1; if (porf_gc_type_can_reference(v.type)) porf_gc_barrier(a, ${TYPES.array}); return porf_box_num((f64)(len + 1));}
${st}u32 porf_bstr_new(u32 len) { const u32 s = porf_alloc(4 + len, ${TYPES.bytestring}); *(u32*)(MEM + s) = len; return s;}
${st}jsval porf_str_concat(jsval a, jsval b) { volatile u32 keep_a = (u32)a.val, keep_b = (u32)b.val; const u32 pa = keep_a, pb = keep_b; const u32 la = *(u32*)(MEM + pa), lb = *(u32*)(MEM + pb); if (a.type == ${TYPES.bytestring} && b.type == ${TYPES.bytestring}) { const u32 s = porf_bstr_new(la + lb); memcpy(MEM + s + 4, MEM + pa + 4, la); memcpy(MEM + s + 4 + la, MEM + pb + 4, lb); return porf_box((f64)s, ${TYPES.bytestring}); }
const u32 s = porf_alloc(4 + (la + lb) * 2, ${TYPES.string}); *(u32*)(MEM + s) = la + lb; u16* out = (u16*)(MEM + s + 4); if (a.type == ${TYPES.bytestring}) { for (u32 i = 0; i < la; i++) out[i] = MEM[pa + 4 + i]; } else { memcpy(out, MEM + pa + 4, (size_t)la * 2); } out += la; if (b.type == ${TYPES.bytestring}) { for (u32 i = 0; i < lb; i++) out[i] = MEM[pb + 4 + i]; } else { memcpy(out, MEM + pb + 4, (size_t)lb * 2); } return porf_box((f64)s, ${TYPES.string});}
${st}i32 porf_str_eq(jsval a, jsval b) { const u32 pa = (u32)a.val, pb = (u32)b.val; if (pa == pb) return 1; const u32 la = *(u32*)(MEM + pa); if (la != *(u32*)(MEM + pb)) return 0; const i32 ta = porf_jv_type(a), tb = porf_jv_type(b); if (ta == tb) return memcmp(MEM + pa + 4, MEM + pb + 4, (size_t)la * (ta == ${TYPES.string} ? 2 : 1)) == 0; if (ta == ${TYPES.bytestring}) { for (u32 i = 0; i < la; i++) if ((u16)MEM[pa + 4 + i] != *(u16*)(MEM + pb + 4 + i * 2)) return 0; } else { for (u32 i = 0; i < la; i++) if (*(u16*)(MEM + pa + 4 + i * 2) != (u16)MEM[pb + 4 + i]) return 0; } return 1;}
${st}jsval porf_bstr_of(const char* buf, u32 len) { const u32 s = porf_bstr_new(len); memcpy(MEM + s + 4, buf, len); return porf_box((f64)s, ${TYPES.bytestring});}
// would rounding x * 10^k to an integer hit an exact tiestatic int porf_num_tie(f64 x, i32 k) { x = fabs(x); if (k >= 0) { const f64 t = ldexp(x, k + 1); return t == trunc(t) && fmod(t, 2.0) == 1.0; } if (k < -22) return 0; f64 m = 1; for (i32 i = 0; i < -k; i++) m *= 10; return fmod(x, m) == m / 2;}
static int porf_digits_inc(char* s, i32 n) { for (i32 i = n - 1; i >= 0; i--) { if (s[i] == '.') continue; if (s[i] != '9') { s[i]++; return 0; } s[i] = '0'; } return 1;}
// C rounds exact ties to even, JS away from zerostatic i32 porf_num_digits(f64 x, i32 prec, char* digits) { char buf[128]; snprintf(buf, sizeof buf, "%.*e", prec - 1, x); i32 e = atoi(strchr(buf, 'e') + 1); const int tie = porf_num_tie(x, prec - 1 - e); if (tie) snprintf(buf, sizeof buf, "%.*e", prec, x); digits[0] = buf[0]; memcpy(digits + 1, buf + 2, (size_t)(prec - 1)); if (tie && porf_digits_inc(digits, prec)) { digits[0] = '1'; e++; } return e;}
static i32 porf_num_shortest(f64 x, char* digits, i32* e) { char buf[32]; i32 prec = isnormal(x) ? 15 : 1; for (; prec < 17; prec++) { snprintf(buf, sizeof buf, "%.*e", prec - 1, x); if (strtod(buf, NULL) == x) break; } if (prec == 17) snprintf(buf, sizeof buf, "%.16e", x); *e = atoi(strchr(buf, 'e') + 1); digits[0] = buf[0]; memcpy(digits + 1, buf + 2, (size_t)(prec - 1)); while (prec > 1 && digits[prec - 1] == '0') prec--; return prec;}
static char* porf_num_write_exp(char* p, const char* digits, i32 k, i32 e) { *p++ = digits[0]; if (k > 1) { *p++ = '.'; memcpy(p, digits + 1, (size_t)(k - 1)); p += k - 1; } return p + sprintf(p, "e%+d", e);}
${st}jsval porf_num_to_str(f64 d) { if (d != d) return porf_bstr_of("NaN", 3); if (d == INFINITY) return porf_bstr_of("Infinity", 8); if (d == -INFINITY) return porf_bstr_of("-Infinity", 9);
char buf[32]; char* p = buf; if (d < 0) { *p++ = '-'; d = -d; }
if (d == trunc(d) && d < 9007199254740992.0) { char tmp[16]; i32 n = 0; u64 v = (u64)d; do tmp[n++] = (char)('0' + v % 10u); while (v /= 10u); while (n) *p++ = tmp[--n]; return porf_bstr_of(buf, (u32)(p - buf)); }
char digits[17]; i32 e; const i32 k = porf_num_shortest(d, digits, &e), n = e + 1; if (k <= n && n <= 21) { memcpy(p, digits, (size_t)k); memset(p + k, '0', (size_t)(n - k)); p += n; } else if (0 < n && n <= 21) { memcpy(p, digits, (size_t)n); p += n; *p++ = '.'; memcpy(p, digits + n, (size_t)(k - n)); p += k - n; } else if (-6 < n && n <= 0) { *p++ = '0'; *p++ = '.'; memset(p, '0', (size_t)-n); p += -n; memcpy(p, digits, (size_t)k); p += k; } else p = porf_num_write_exp(p, digits, k, n - 1); return porf_bstr_of(buf, (u32)(p - buf));}
${st}jsval porf_num_to_fixed(f64 d, i32 f) { if (!(fabs(d) < 1e21)) return porf_num_to_str(d); if (d == 0) d = 0; char buf[128]; if (!porf_num_tie(d, f)) return porf_bstr_of(buf, (u32)snprintf(buf, sizeof buf, "%.*f", f, d));
i32 n = snprintf(buf, sizeof buf, "%.*f", f + 1, d) - (f == 0 ? 2 : 1); const i32 start = d < 0; if (porf_digits_inc(buf + start, n - start)) { memmove(buf + start + 1, buf + start, (size_t)(n - start)); buf[start] = '1'; n++; } return porf_bstr_of(buf, (u32)n);}
// f < 0: as many digits as needed${st}jsval porf_num_to_exponential(f64 d, i32 f) { if (!isfinite(d)) return porf_num_to_str(d); char buf[128], digits[101]; char* p = buf; if (d < 0) { *p++ = '-'; d = -d; }
i32 k, e = 0; if (d == 0) { k = f < 0 ? 1 : f + 1; memset(digits, '0', (size_t)k); } else if (f < 0) k = porf_num_shortest(d, digits, &e); else e = porf_num_digits(d, k = f + 1, digits); p = porf_num_write_exp(p, digits, k, e); return porf_bstr_of(buf, (u32)(p - buf));}
${st}jsval porf_num_to_precision(f64 d, i32 prec) { if (!isfinite(d)) return porf_num_to_str(d); char buf[128], digits[100]; char* p = buf; if (d < 0) { *p++ = '-'; d = -d; }
i32 e = 0; if (d == 0) memset(digits, '0', (size_t)prec); else e = porf_num_digits(d, prec, digits); if (e < -6 || e >= prec) p = porf_num_write_exp(p, digits, prec, e); else if (e >= 0) { memcpy(p, digits, (size_t)(e + 1)); p += e + 1; if (e + 1 < prec) { *p++ = '.'; memcpy(p, digits + e + 1, (size_t)(prec - e - 1)); p += prec - e - 1; } } else { *p++ = '0'; *p++ = '.'; memset(p, '0', (size_t)(-e - 1)); p += -e - 1; memcpy(p, digits, (size_t)prec); p += prec; } return porf_bstr_of(buf, (u32)(p - buf));}
${st}jsval porf_num_to_radix(f64 d, i32 radix) { if (radix == 10 || !isfinite(d)) return porf_num_to_str(d); static const char chars[] = "0123456789abcdefghijklmnopqrstuvwxyz"; char buf[2200]; i32 lo = 1100, hi = 1100; const int negative = d < 0; if (negative) d = -d;
f64 integer = floor(d), fraction = d - integer; f64 delta = fmax(0.5 * (nextafter(d, INFINITY) - d), nextafter(0.0, 1.0)); if (fraction >= delta) { buf[hi++] = '.'; do { fraction *= radix; delta *= radix; const i32 digit = (i32)fraction; buf[hi++] = chars[digit]; fraction -= digit; if ((fraction > 0.5 || (fraction == 0.5 && (digit & 1))) && fraction + delta > 1) { while (1) { if (--hi == 1100) { integer += 1; break; } const char c = buf[hi]; const i32 v = c > '9' ? c - 'a' + 10 : c - '0'; if (v + 1 < radix) { buf[hi++] = chars[v + 1]; break; } } break; } } while (fraction >= delta); }
while (integer / radix >= 9007199254740992.0) { integer /= radix; buf[--lo] = '0'; } do { const f64 r = fmod(integer, radix); buf[--lo] = chars[(i32)r]; integer = (integer - r) / radix; } while (integer > 0);
if (negative) buf[--lo] = '-'; return porf_bstr_of(buf + lo, (u32)(hi - lo));}
// non-ascii as 0x7f so strtod stops therestatic char* porf_str_cstr(jsval s, char* small, u32 cap) { const u32 p = (u32)s.val, len = *(u32*)(MEM + p); char* buf = len < cap ? small : (char*)malloc(len + 1); for (u32 i = 0; i < len; i++) { const u32 c = s.type == ${TYPES.string} ? ((u16*)(MEM + p + 4))[i] : MEM[p + 4 + i]; buf[i] = c < 0x80 ? (char)c : 0x7f; } buf[len] = 0; return buf;}
static f64 porf_digits_radix(const char* q, i32 radix, const char** end) { f64 n = 0; for (;; q++) { const i32 v = *q >= '0' && *q <= '9' ? *q - '0' : (*q | 32) >= 'a' && (*q | 32) <= 'z' ? (*q | 32) - 'a' + 10 : 36; if (v >= radix) break; n = n * radix + v; } *end = q; return n;}
${st}f64 porf_str_to_num(jsval s, i32 strict) { char small[256]; char* buf = porf_str_cstr(s, small, sizeof small); const char* q = buf + (buf[0] == '+' || buf[0] == '-'); const char* end = q; f64 n = NAN;
// strtod also takes hex floats, inf and nan if (q[0] == '0' && ((q[1] | 32) == 'x' || (q[1] | 32) == 'o' || (q[1] | 32) == 'b')) { const i32 radix = (q[1] | 32) == 'x' ? 16 : (q[1] | 32) == 'o' ? 8 : 2; if (strict) { if (q == buf && q[2]) n = porf_digits_radix(q + 2, radix, &end); } else { n = buf[0] == '-' ? -0.0 : 0; end = q + 1; } } else if ((q[0] | 32) == 'i' || (q[0] | 32) == 'n') { if (strncmp(q, "Infinity", 8) == 0) { n = buf[0] == '-' ? -INFINITY : INFINITY; end = q + 8; } } else n = strtod(buf, (char**)&end);
if (end <= q || (strict && *end)) n = NAN; if (buf != small) free(buf); return n;}
${st}f64 porf_parse_int(jsval s, i32 radix) { char small[256]; char* buf = porf_str_cstr(s, small, sizeof small); char* q = buf + (buf[0] == '+' || buf[0] == '-'); f64 n = NAN; if (radix == 0 || radix == 16) { if (q[0] == '0' && (q[1] | 32) == 'x') { q += 2; radix = 16; } } if (radix == 0) radix = 10;
if (radix >= 2 && radix <= 36) { const char* end; n = porf_digits_radix(q, radix, &end); if (end == q) n = NAN; else if (radix == 10) { const char c = *end; *(char*)end = 0; n = strtod(q, NULL); *(char*)end = c; } if (buf[0] == '-') n = -n; } if (buf != small) free(buf); return n;}
// ToString for + and template literals${st}jsval porf_to_str(jsval v);${st}jsval porf_str_concat(jsval a, jsval b);${st}jsval porf_to_str(jsval v) { if (porf_jv_is_num(v)) return porf_num_to_str(v.val); const i32 t = v.type; if (t == ${TYPES.bytestring} || t == ${TYPES.string}) return v; if (t == ${TYPES.array}) { // join(',') const u32 a = (u32)v.val; const i32 n = PORF_ARR_LEN(a); jsval acc = porf_box((f64)porf_bstr_new(0), ${TYPES.bytestring}); for (i32 i = 0; i < n; i++) { if (i) { const u32 c = porf_bstr_new(1); MEM[c + 4] = 44; acc = porf_str_concat(acc, porf_box((f64)c, ${TYPES.bytestring})); } const jsval e = porf_arr_get(a, (u32)i); const i32 et = porf_jv_type(e); if (et != ${TYPES.undefined} && !(et == ${TYPES.object} && (u32)e.val == 0)) acc = porf_str_concat(acc, porf_to_str(e)); } return acc; } const char* lit = t == ${TYPES.undefined} ? "undefined" : t == ${TYPES.boolean} ? ((u32)v.val ? "true" : "false") : t == ${TYPES.object} && (u32)v.val == 0 ? "null" : t == ${TYPES.function} ? "function" : "[object Object]"; const u32 len = (u32)strlen(lit); const u32 s = porf_bstr_new(len); memcpy(MEM + s + 4, lit, len); return porf_box((f64)s, ${TYPES.bytestring});}
// basic ToNumber (string parsing arrives with builtins port)${sti}f64 porf_to_num(jsval v) { if (porf_jv_is_num(v)) return v.val; const i32 t = v.type; if (t == ${TYPES.boolean}) return (f64)(u32)v.val; if (t == ${TYPES.object} && (u32)v.val == 0) return 0.0; // null return porf_bits_to_f64(0x7FF8000000000000ull); // NaN}
static inline int porf_bigint_is_heap(jsval v) { return v.type == ${TYPES.bigint} && v.val >= 2251799813685248.0;}
static inline u32 porf_bigint_ptr(jsval v) { return (u32)(v.val - 2251799813685248.0);}
static inline int porf_bigint_is_negative(jsval v) { return porf_bigint_is_heap(v) ? *(u8*)(MEM + porf_bigint_ptr(v)) != 0 : v.val < 0;}
static u32 porf_bigint_trimmed_len(u32 p) { const u32 len = *(u16*)(MEM + p + 2); u32 i = 0; while (i < len && *(u32*)(MEM + p + 4 + (i << 2)) == 0) i++; return len - i;}
static i32 porf_bigint_cmp_abs(jsval a, jsval b) { const int ah = porf_bigint_is_heap(a), bh = porf_bigint_is_heap(b); if (!ah && !bh) { const f64 av = a.val < 0 ? -a.val : a.val; const f64 bv = b.val < 0 ? -b.val : b.val; return av < bv ? -1 : av > bv ? 1 : 0; } if (ah != bh) { // heap vs inline: heap bigints can hold small values too (see porf_pack) const u32 hp = porf_bigint_ptr(ah ? a : b); const u32 hlen = porf_bigint_trimmed_len(hp); i32 c = 1; if (hlen <= 2) { const u32 hstart = *(u16*)(MEM + hp + 2) - hlen; u64 hv = 0; for (u32 i = 0; i < hlen; i++) hv = (hv << 32) | *(u32*)(MEM + hp + 4 + ((hstart + i) << 2)); const f64 iv = ah ? b.val : a.val; const u64 im = (u64)(iv < 0 ? -iv : iv); c = hv < im ? -1 : hv > im ? 1 : 0; } return ah ? c : -c; }
const u32 ap = porf_bigint_ptr(a), bp = porf_bigint_ptr(b); const u32 alen = porf_bigint_trimmed_len(ap), blen = porf_bigint_trimmed_len(bp); if (alen != blen) return alen < blen ? -1 : 1;
const u32 astart = *(u16*)(MEM + ap + 2) - alen; const u32 bstart = *(u16*)(MEM + bp + 2) - blen; for (u32 i = 0; i < alen; i++) { const u32 av = *(u32*)(MEM + ap + 4 + ((astart + i) << 2)); const u32 bv = *(u32*)(MEM + bp + 4 + ((bstart + i) << 2)); if (av != bv) return av < bv ? -1 : 1; } return 0;}
static u32 porf_bigint_heapify(f64 d) { const int neg = d < 0; f64 a = neg ? -d : d; u32 digits[40]; u32 n = 0; while (a >= 1.0 && n < 40) { const f64 q = floor(a / 4294967296.0); digits[n++] = (u32)(a - q * 4294967296.0); a = q; } if (n == 0) digits[n++] = 0; const u32 ptr = porf_alloc((4 + n * 4 + 7) & ~7u, ${TYPES.bigint}); *(u8*)(MEM + ptr) = (u8)neg; *(u16*)(MEM + ptr + 2) = (u16)n; for (u32 i = 0; i < n; i++) *(u32*)(MEM + ptr + 4 + (i << 2)) = digits[n - 1 - i]; return ptr;}
static PORF_NOINLINE i32 porf_bigint_cmp(jsval a, jsval b) { const int an = porf_bigint_is_negative(a), bn = porf_bigint_is_negative(b); if (an != bn) return an ? -1 : 1; const i32 c = porf_bigint_cmp_abs(a, b); return an ? -c : c;}
${sti}int porf_is_strlike(jsval v) { if (porf_jv_is_num(v)) return 0; const i32 t = v.type; return t == ${TYPES.bytestring} || t == ${TYPES.string} || t == ${TYPES.object} && (u32)v.val != 0 || t == ${TYPES.array} || t == ${TYPES.function};}
// JS + : string-ish on either side concats; else numeric coercion${sti}jsval porf_add(jsval a, jsval b) { if (porf_jv_is_num(a) && porf_jv_is_num(b)) return porf_box_num(a.val + b.val); if (porf_is_strlike(a) || porf_is_strlike(b)) { jsval sa = porf_to_str(a); volatile u32 keep_sa = porf_gc_type_can_reference(sa.type) ? (u32)sa.val : 0u; jsval sb = porf_to_str(b); (void)keep_sa; return porf_str_concat(sa, sb); } return porf_box_num(porf_to_num(a) + porf_to_num(b));}
// numeric -, *, / on two unknown-typed operands: a single combined both-number fast-path (one// hoistable/unswitchable condition) rather than two independent porf_to_num branches.${sti}jsval porf_sub(jsval a, jsval b) { if (porf_jv_is_num(a) && porf_jv_is_num(b)) return porf_box_num(a.val - b.val); return porf_box_num(porf_to_num(a) - porf_to_num(b));}${sti}jsval porf_mul(jsval a, jsval b) { if (porf_jv_is_num(a) && porf_jv_is_num(b)) return porf_box_num(a.val * b.val); return porf_box_num(porf_to_num(a) * porf_to_num(b));}${sti}jsval porf_div(jsval a, jsval b) { if (porf_jv_is_num(a) && porf_jv_is_num(b)) return porf_box_num(a.val / b.val); return porf_box_num(porf_to_num(a) / porf_to_num(b));}
// JS abstract relational comparison: -1/0/1, 2 = unordered (NaN involved).// 3 = needs ToPrimitive/StringToNumber/BigInt mixing: codegen falls back to __Porffor_comparestatic inline int porf_cmp_numish(jsval v) { const i32 t = porf_jv_type(v); return t == ${TYPES.number} || t == ${TYPES.boolean} || t == ${TYPES.undefined} || (t == ${TYPES.object} && (u32)v.val == 0);}
${sti}i32 porf_cmp(jsval a, jsval b) { const i32 ta = porf_jv_type(a), tb = porf_jv_type(b);${bigints ? ` if (ta == ${TYPES.bigint} && tb == ${TYPES.bigint}) return porf_bigint_cmp(a, b);` : ''} if ((ta == ${TYPES.bytestring} || ta == ${TYPES.string}) && (tb == ${TYPES.bytestring} || tb == ${TYPES.string})) { const u32 pa = (u32)a.val, pb = (u32)b.val; const u32 la = *(u32*)(MEM + pa), lb = *(u32*)(MEM + pb); const u32 n = la < lb ? la : lb; if (ta == ${TYPES.bytestring} && tb == ${TYPES.bytestring}) { const int c = memcmp(MEM + pa + 4, MEM + pb + 4, n); if (c != 0) return c < 0 ? -1 : 1; } else { for (u32 i = 0; i < n; i++) { const u32 ca = ta == ${TYPES.string} ? (u32)MEM[pa + 4 + i * 2] | (u32)MEM[pa + 5 + i * 2] << 8 : MEM[pa + 4 + i]; const u32 cb = tb == ${TYPES.string} ? (u32)MEM[pb + 4 + i * 2] | (u32)MEM[pb + 5 + i * 2] << 8 : MEM[pb + 4 + i]; if (ca != cb) return ca < cb ? -1 : 1; } } return la == lb ? 0 : la < lb ? -1 : 1; } if (!porf_cmp_numish(a) || !porf_cmp_numish(b)) return 3; const f64 x = porf_to_num(a), y = porf_to_num(b); if (x != x || y != y) return 2; return x < y ? -1 : x > y ? 1 : 0;}
// loose ==: 0/1, or 2 when coercion is needed: codegen falls back to __Porffor_looseEqstatic inline int porf_is_prim_type(i32 t) { return t == ${TYPES.undefined} || t == ${TYPES.number} || t == ${TYPES.boolean} || t == ${TYPES.string} || t == ${TYPES.bytestring} || t == ${TYPES.bigint} || t == ${TYPES.symbol};}
${sti}i32 porf_loose_eq(jsval a, jsval b) { const i32 ta = porf_jv_type(a), tb = porf_jv_type(b); if (ta == ${TYPES.number} && tb == ${TYPES.number}) return a.val == b.val; const int an = ta == ${TYPES.undefined} || (ta == ${TYPES.object} && (u32)a.val == 0); const int bn = tb == ${TYPES.undefined} || (tb == ${TYPES.object} && (u32)b.val == 0); if (an || bn) return an && bn; const int sa = ta == ${TYPES.bytestring} || ta == ${TYPES.string}, sb = tb == ${TYPES.bytestring} || tb == ${TYPES.string}; if (sa && sb) return porf_str_eq(a, b); if (ta == ${TYPES.boolean}) return porf_loose_eq(porf_box_num((f64)(u32)a.val), b); if (tb == ${TYPES.boolean}) return porf_loose_eq(a, porf_box_num((f64)(u32)b.val));${bigints ? ` if (ta == ${TYPES.bigint} && tb == ${TYPES.bigint}) return porf_bigint_cmp(a, b) == 0;` : ''} const int pa = porf_is_prim_type(ta), pb = porf_is_prim_type(tb); if (!pa && !pb) return porf_jv_eq(a, b); if (pa && pb) { // string/number and BigInt/string|number coerce, symbols only equal themselves if (ta == ${TYPES.symbol} || tb == ${TYPES.symbol}) return porf_jv_eq(a, b); if (ta == tb) return porf_jv_eq(a, b); } return 2;}
// == inside builtins: pointer/zero checks (x == 0) must not coerce, strings only equal 0 when empty${sti}i32 porf_loose_eq_raw(jsval a, jsval b) { const i32 r = porf_loose_eq(a, b); if (r != 2) return r; if (porf_jv_type(a) == ${TYPES.boolean}) a = porf_box_num((f64)(u32)a.val); if (porf_jv_type(b) == ${TYPES.boolean}) b = porf_box_num((f64)(u32)b.val); const i32 ta = porf_jv_type(a), tb = porf_jv_type(b); if ((ta == ${TYPES.bytestring} || ta == ${TYPES.string}) && tb == ${TYPES.number}) return b.val == 0.0 && ((u32)a.val == 0u || *(u32*)(MEM + (u32)a.val) == 0u); if (ta == ${TYPES.number} && (tb == ${TYPES.bytestring} || tb == ${TYPES.string})) return a.val == 0.0 && ((u32)b.val == 0u || *(u32*)(MEM + (u32)b.val) == 0u); return 0;}
// === : numbers as f64, strings by content, else identity${sti}i32 porf_strict_eq(jsval a, jsval b) { if (porf_jv_is_num(a)) return porf_jv_is_num(b) && a.val == b.val; const i32 ta = porf_jv_type(a), tb = porf_jv_type(b); if ((ta == ${TYPES.bytestring} || ta == ${TYPES.string}) && (tb == ${TYPES.bytestring} || tb == ${TYPES.string})) return porf_str_eq(a, b); if (ta != tb) return 0;${bigints ? ` if (ta == ${TYPES.bigint}) return porf_bigint_cmp(a, b) == 0;` : ''} return (u32)a.val == (u32)b.val;}
static int porf_argc;static char** porf_argv;static void porf_init(int argc, char** argv) { porf_argc = argc; porf_argv = argv; porf_arena_init();}
`;};
const PROMISE_DIRECT_RUNTIME = () => `static jsval porf_promise_pending(void) { const u32 p = porf_alloc(PORF_PROMISE_SIZE, ${TYPES.promise}); *(jsbits*)(MEM + p + PORF_PROMISE_RESULT) = JV_UNDEFINED_BITS; *(u32*)(MEM + p + PORF_PROMISE_FULFILL_HEAD) = 0; *(u32*)(MEM + p + PORF_PROMISE_FULFILL_TAIL) = 0; *(u32*)(MEM + p + PORF_PROMISE_REJECT_HEAD) = 0; *(u32*)(MEM + p + PORF_PROMISE_REJECT_TAIL) = 0; *(jsbits*)(MEM + p + PORF_PROMISE_PAYLOAD) = JV_UNDEFINED_BITS; *(u8*)(MEM + p + PORF_PROMISE_STATE) = 0; *(u8*)(MEM + p + PORF_PROMISE_FLAGS) = 0; *(u8*)(MEM + p + PORF_PROMISE_HANDLED) = 0; return porf_box((f64)p, ${TYPES.promise});}
static void porf_promise_trigger_reactions(u32 reaction, jsval value) { while (reaction != 0) { const u32 next = *(u32*)(MEM + reaction + PORF_REACTION_NEXT); *(u32*)(MEM + reaction + PORF_REACTION_NEXT) = 0; *(jsbits*)(MEM + reaction + PORF_REACTION_VALUE) = porf_pack(value); porf_gc_barrier(reaction, PORF_GC_KIND_PROMISE_REACTION); porf_promise_enqueue_job(reaction); reaction = next; }}
static void porf_promise_settle_direct(jsval promise, jsval value, i32 state) { if (promise.type != ${TYPES.promise}) return; const u32 p = (u32)promise.val; if (*(u8*)(MEM + p + PORF_PROMISE_STATE) != 0) return; const u32 reactions = *(u32*)(MEM + p + (state == 1 ? PORF_PROMISE_FULFILL_HEAD : PORF_PROMISE_REJECT_HEAD)); *(jsbits*)(MEM + p + PORF_PROMISE_RESULT) = porf_pack(value); *(u32*)(MEM + p + PORF_PROMISE_FULFILL_HEAD) = 0; *(u32*)(MEM + p + PORF_PROMISE_FULFILL_TAIL) = 0; *(u32*)(MEM + p + PORF_PROMISE_REJECT_HEAD) = 0; *(u32*)(MEM + p + PORF_PROMISE_REJECT_TAIL) = 0; *(u8*)(MEM + p + PORF_PROMISE_STATE) = (u8)state; porf_gc_barrier(p, ${TYPES.promise}); porf_promise_trigger_reactions(reactions, value);}`;
const CORO_RUNTIME = () => `// record: header, frame, args. a generator is its recordtypedef struct porf_coro_call { jsval callee; jsval thisv; jsval newtv; jsval result; jsval channel; // value (or thrown exception) crossing the boundary jsval iter_open; // porf_iter_open inside it jsval caller_iter_open; struct porf_coro_call* parent; u32 heap; // block offset in the GC heap, 0 while on the C stack u32 idx; u32 env; u32 frame_size; i32 argc; i32 state; // 0 idle, 1 running, 2 suspended, 3 done i32 resume; // suspend point to re-enter at, 0 = start i32 throw_pending; // resume delivers channel as a throw at the suspend point i32 raw; // channel is an iterator result to pass through as-is i32 awaiting; // suspended at an await (channel is its promise), not a yield i32 caller_iter_base;} porf_coro_call;#define PORF_CORO_HDR ((u32)((sizeof(porf_coro_call) + 7u) & ~7u))#define PORF_CORO_BYTES(frame, argc) (PORF_CORO_HDR + (frame) + (u32)(argc) * 8u)
static porf_coro_call* porf_coro_cur = 0;#define PORF_CORO_RETURN porf_box(0.0, ${TYPES.__porffor_generator})static i32 porf_coro_mode = 0;static jsval porf_coro_dispatch(porf_coro_call* call);static u32 porf_coro_frame_bytes(u32 idx);#ifndef PORF_GC_KIND_CORO#define PORF_GC_KIND_CORO 247u#endif
static inline void* porf_coro_frame(porf_coro_call* c) { return (u8*)c + PORF_CORO_HDR; }static inline jsbits* porf_coro_argv(porf_coro_call* c) { return (jsbits*)((u8*)c + PORF_CORO_HDR + c->frame_size); }static inline void porf_coro_touch(porf_coro_call* c) { if (c->heap) porf_gc_barrier(c->heap, 0); }
static u32 porf_promise_new_coro_reaction(porf_coro_call* call, jsval out_promise, i32 is_throw) { const u32 reaction = porf_alloc(PORF_REACTION_SIZE, 0); *(u64*)(MEM + reaction + PORF_REACTION_HANDLER) = (u64)call->heap; *(jsbits*)(MEM + reaction + PORF_REACTION_OUT_PROMISE) = porf_pack(out_promise); *(jsbits*)(MEM + reaction + PORF_REACTION_VALUE) = JV_UNDEFINED_BITS; *(u32*)(MEM + reaction + PORF_REACTION_NEXT) = 0; *(u32*)(MEM + reaction + PORF_REACTION_PAYLOAD) = (u32)is_throw; *(u8*)(MEM + reaction + PORF_REACTION_KIND) = 11; *(u8*)(MEM + reaction + PORF_REACTION_FLAGS) = 0; porf_gc_barrier(reaction, PORF_GC_KIND_PROMISE_REACTION); return reaction;}
static void porf_promise_append_raw_reaction(u32 promise, u32 reaction, i32 reject) { const u32 head_off = reject ? PORF_PROMISE_REJECT_HEAD : PORF_PROMISE_FULFILL_HEAD; const u32 tail_off = reject ? PORF_PROMISE_REJECT_TAIL : PORF_PROMISE_FULFILL_TAIL; const u32 tail = *(u32*)(MEM + promise + tail_off); if (tail == 0) { *(u32*)(MEM + promise + head_off) = reaction; } else { *(u32*)(MEM + tail + PORF_REACTION_NEXT) = reaction; porf_gc_barrier(tail, PORF_GC_KIND_PROMISE_REACTION); } *(u32*)(MEM + promise + tail_off) = reaction; porf_gc_barrier(promise, ${TYPES.promise});}
static void porf_promise_attach_coro(jsval awaited, porf_coro_call* call, jsval out_promise) { if (awaited.type != ${TYPES.promise}) porf_unreachable("coroutine awaited non-promise"); const u32 p = (u32)awaited.val; *(u8*)(MEM + p + PORF_PROMISE_HANDLED) = 1; const u8 state = *(u8*)(MEM + p + PORF_PROMISE_STATE); if (state != 0) { // already settled: resume in a job, like then on a settled promise const u32 reaction = porf_promise_new_coro_reaction(call, out_promise, state == 2); *(jsbits*)(MEM + reaction + PORF_REACTION_VALUE) = *(jsbits*)(MEM + p + PORF_PROMISE_RESULT); porf_gc_barrier(reaction, PORF_GC_KIND_PROMISE_REACTION); porf_promise_enqueue_job(reaction); return; } porf_promise_append_raw_reaction(p, porf_promise_new_coro_reaction(call, out_promise, 0), 0); porf_promise_append_raw_reaction(p, porf_promise_new_coro_reaction(call, out_promise, 1), 1);}
static void porf_coro_init(porf_coro_call* c, u32 heap, u32 frame, u32 idx, jsval callee, u32 env, jsval thisv, jsval newtv, i32 argc, jsbits* argv) { c->callee = callee; c->thisv = thisv; c->newtv = newtv; c->result = JV_UNDEFINED; c->channel = JV_UNDEFINED; c->parent = 0; c->heap = heap; c->idx = idx; c->env = env; c->frame_size = frame; c->argc = argc; c->state = 0; c->resume = 0; c->throw_pending = 0; c->raw = 0; c->awaiting = 0; c->iter_open = JV_UNDEFINED; c->caller_iter_open = JV_UNDEFINED; jsbits* a = porf_coro_argv(c); for (i32 i = 0; i < argc; i++) a[i] = argv[i];}
static porf_coro_call* porf_coro_alloc(u32 kind, u32 frame, i32 argc) { const u32 p = porf_alloc(PORF_CORO_BYTES(frame, argc), kind);#if PORF_GC_ENABLED porf_gc_set_kind((i32)p, kind);#endif porf_coro_call* c = (porf_coro_call*)(MEM + p); c->heap = p; return c;}
static void porf_coro_finish(porf_coro_call* c) { c->state = 3; c->resume = 0;}
// yield* gets throw/return as values, porf_yield_mode says whichstatic inline jsval porf_coro_received(porf_coro_call* c, i32 delegate) { c->resume = 0; if (c->throw_pending) { c->throw_pending = 0; if (!delegate) porf_throw(c->channel); porf_coro_mode = porf_jv_eq(c->channel, PORF_CORO_RETURN) ? 2 : 1; return porf_coro_mode == 2 ? c->result : c->channel; } porf_coro_mode = 0; return c->channel;}
#if PORF_GC_ENABLEDstatic void porf_coro_gc_scan(porf_coro_call* call) { porf_gc_mark_js(call->callee.val, call->callee.type); porf_gc_mark_js(call->thisv.val, call->thisv.type); porf_gc_mark_js(call->newtv.val, call->newtv.type); porf_gc_mark_js(call->result.val, call->result.type); porf_gc_mark_js(call->channel.val, call->channel.type); porf_gc_mark_js(call->iter_open.val, call->iter_open.type); if (call->state == 1) porf_gc_mark_js(call->caller_iter_open.val, call->caller_iter_open.type); const jsbits* argv = porf_coro_argv(call); for (i32 i = 0; i < call->argc; i++) porf_gc_mark_jsbits(argv[i]); if (call->state == 2) { const u8* frame = (const u8*)porf_coro_frame(call); porf_gc_cons_scan_range((const u64*)frame, (const u64*)(frame + call->frame_size)); }}#endif
static void porf_coro_restore_caller(porf_coro_call* c) { porf_coro_cur = c->parent; c->iter_open = porf_iter_open; porf_iter_open = c->caller_iter_open; porf_iter_base = c->caller_iter_base; porf_coro_touch(c);}
static i32 porf_coro_run(porf_coro_call* c) { c->state = 1; c->parent = porf_coro_cur; porf_coro_cur = c; c->caller_iter_open = porf_iter_open; c->caller_iter_base = porf_iter_base; porf_coro_touch(c); porf_iter_open = c->iter_open; porf_iter_base = porf_try_depth; const jsval r = porf_coro_dispatch(c); if (c->resume == 0) c->result = r; porf_coro_restore_caller(c); c->state = c->resume != 0 ? 2 : 3; return c->state == 3;}
static i32 porf_coro_call_step(porf_coro_call* call, jsval value, i32 is_throw) { if (call->state == 0) return porf_coro_run(call); if (call->state != 2) porf_unreachable("resume of non-suspended coroutine"); call->channel = value; call->raw = 0; call->awaiting = 0; call->throw_pending = is_throw; porf_coro_touch(call); return porf_coro_run(call);}
static void porf_coro_abort(porf_coro_call* call) { porf_coro_restore_caller(call); porf_coro_finish(call);}
static porf_coro_call* porf_coro_promote(porf_coro_call* c) { if (c->heap) return c; porf_coro_call* h = porf_coro_alloc(PORF_GC_KIND_CORO, c->frame_size, 0); const u32 heap = h->heap; memcpy(h, c, PORF_CORO_HDR + c->frame_size); h->heap = heap; h->argc = 0; h->parent = 0; return h;}
static i32 porf_yield_mode(void) { return porf_coro_mode;}
`;