12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722letvalidate_refs=reftruemoduleUint32=Wax_utils.Uint32moduleUint64=Wax_utils.Uint64openTypes.InternalmoduleNz=Types.Normalized(* The [@]-suffixed operators sequence [option] computations, short-circuiting
on [None]: [let*@] binds, [let+@] maps, and [let>@] runs the body for its
side effect and discards the result. (The unsuffixed [let*]/[let*!]/[let*?]
defined further down instead thread the value stack.) *)let(let*@)=Option.bindlet(let+@)of=Option.mapfolet(let>@)of=Option.iterfo(*** Source types and printers ***)(* WAT types and identifiers are rendered directly into a diagnostic's styled
printer (see {!Wax_utils.Styled_printer}), so an embedded type shares the
message's colour theme and width — rather than being pre-rendered to a flat
string. These helpers wrap the layout/colour primitives. *)letsp_spacepp=Wax_utils.Printer.spacepp.Wax_utils.Styled_printer.printer()letsp_boxppf=Wax_utils.Printer.boxpp.Wax_utils.Styled_printer.printer~indent:1fletsp_typepps=Wax_utils.Styled_printer.print_styledppWax_utils.Colors.Typesletsp_kwpps=Wax_utils.Styled_printer.print_styledppWax_utils.Colors.Keywordsletsp_punctpps=Wax_utils.Styled_printer.print_styledppWax_utils.Colors.Punctuationsletprint_stringpps=letlen,escaped=Wax_utils.Unicode.escape_strings.Ast.descinWax_utils.Styled_printer.print_styledppWax_utils.Colors.String~len:(Somelen)escapedletprint_identppid=lets=ifLexer.is_valid_identifieridthen"$"^idelse"$\""^snd(Wax_utils.Unicode.escape_stringid)^"\""inWax_utils.Styled_printer.print_styledppWax_utils.Colors.Identifiersletprint_indexpp(idx:Ast.Text.idx)=matchidx.descwith|Numn->Wax_utils.Styled_printer.print_styledppWax_utils.Colors.Constant(Uint32.to_stringn)|Idid->print_identppid(* Render a type as the source wrote it, naming an indexed type by its source
reference ($foo or a number) rather than an interned canonical index. *)letprint_text_heaptypepp(ty:Ast.Text.heaptype)=matchAst.Text.heaptype_keywordtywith|Somekw->sp_typeppkw|None->(matchtywith|Typeidx->print_indexppidx|Exactidx->sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"exact";sp_spacepp;print_indexppidx;sp_punctpp")")|_->assertfalse)letprint_text_valtypepp(ty:Ast.Text.valtype)=matchtywith|I32->sp_typepp"i32"|I64->sp_typepp"i64"|F32->sp_typepp"f32"|F64->sp_typepp"f64"|V128->sp_typepp"v128"|Ref{nullable;typ}->sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"ref";sp_spacepp;ifnullablethen(sp_kwpp"null";sp_spacepp);print_text_heaptypepptyp;sp_punctpp")")letprint_text_storagetypepp(ty:Ast.Text.storagetype)=matchtywith|Valuev->print_text_valtypeppv|PackedI8->sp_typepp"i8"|PackedI16->sp_typepp"i16"letprint_text_fieldtypepp({mut;typ}:Ast.Text.fieldtype)=ifmutthensp_boxpp(fun()->sp_punctpp"(";sp_kwpp"mut";sp_spacepp;print_text_storagetypepptyp;sp_punctpp")")elseprint_text_storagetypepptypletprint_text_functypepp({params;results}:Ast.Text.functype)=Array.iter(funp->sp_spacepp;sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"param";sp_spacepp;print_text_valtypepp(sndp.Ast.desc);sp_punctpp")"))params;Array.iter(funt->sp_spacepp;sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"result";sp_spacepp;print_text_valtypeppt;sp_punctpp")"))results(* Render a composite type as its source signature, for a reference to a type
the user did not name (an implicit [ref.func] type, the internal string
type). *)letprint_text_comptypepp(ty:Ast.Text.comptype)=matchtywith|Funcft->sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"func";print_text_functypeppft;sp_punctpp")")|Arrayft->sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"array";sp_spacepp;print_text_fieldtypeppft;sp_punctpp")")|Structfields->sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"struct";Array.iter(fune->let_,ft=e.Ast.descinsp_spacepp;sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"field";sp_spacepp;print_text_fieldtypeppft;sp_punctpp")"))fields;sp_punctpp")")|Contidx->sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"cont";sp_spacepp;print_indexppidx;sp_punctpp")")(* The source rendering of a stack value: either a value type the user wrote
(or that names a type the user declared), or — for a reference whose type has
no source name — that referenced type's signature, shown inline. *)typesource_type=|PlainofAst.Text.valtype|Inline_refofAst.Text.comptype(* The bottom reference type [(ref bot)]. It has no user-written form; it is
synthesized only to render a stack value of the bottom reference type in a
diagnostic (e.g. when such a value reaches a numeric context). *)|Bottom_refletprint_source_typepp=function|Plainv->print_text_valtypeppv|Inline_refcomptype->sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"ref";sp_spacepp;print_text_comptypeppcomptype;sp_punctpp")")|Bottom_ref->sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"ref";sp_spacepp;sp_typepp"bot";sp_punctpp")")(* Render a source type to a plain (uncoloured) string. *)letrender_source_typesource=String.trim(Wax_utils.Printer.run_string(funp->letpp=Wax_utils.Styled_printer.create~printer:p~theme:Wax_utils.Colors.no_color~trivia:(Wax_utils.Trivia.empty())()inprint_source_typeppsource))(* What the editor type sink records at each instruction span. Kept unrendered:
the recording pass runs over the whole module, but the editor renders only
the few entries under the cursor, so rendering is deferred to
[render_recorded_type]. *)typerecorded_type=|Pushedofsource_type(* a value the instruction leaves on the stack *)|Polymorphic(* the unknown value of an unreachable / polymorphic stack *)|No_result(* the instruction produces no value *)|Signatureofsource_typearray*source_typearray(* a function's (params, results), for the identifier of a call / ref.func *)|Subtypeof(Ast.Text.nameoption*Ast.Text.subtype,Ast.location)Ast.annotated(* the source definition of the type a type identifier refers to *)|Value_typeofAst.location(* the definition span of a value's named reference type, for go-to-type-def;
carries no display type (a [Pushed] at the same span renders the value) *)letrender_recorded_type=function|Pushedsource->Some(render_source_typesource)|Polymorphic->Some"any"|No_result|Value_type_->None|Subtypee->Some(Output.subtype_stringe)|Signature(params,results)->letgroupkwarr=ifArray.lengtharr=0thenNoneelseSome(Printf.sprintf"(%s %s)"kw(String.concat" "(Array.to_list(Array.maprender_source_typearr))))inletparts=List.filter_mapFun.id[group"param"params;group"result"results]inSome(Printf.sprintf"(func%s)"(String.concat""(List.map(funs->" "^s)parts)))(* The definition span of the type a recorded entry refers to, for
go-to-type-definition: a value's named reference type, or the type a type
identifier names; [None] otherwise. *)lettype_def_location=function|Value_typel->Somel|Subtypee->(matchfste.Ast.descwith|Some(n:Ast.Text.name)->Somen.Ast.info|None->Somee.Ast.info)|Pushed_|Polymorphic|No_result|Signature_->None(* The rendered parameter and result types of a recorded function signature (a
[call]/[ref.func] identifier), for signature help; [None] for any other kind. *)letsignature_labels=function|Signature(params,results)->Some(Array.to_list(Array.maprender_source_typeparams),Array.to_list(Array.maprender_source_typeresults))|_->None(* Editor type sink. Like [validate_refs], a module-level ref rather than a
threaded parameter: the push chokepoints below record into it without
carrying it through the ~130 call sites. When set (only in editor mode, via
[f]'s [?record_types]), every value pushed onto the stack is recorded as
[(span of the pushing instruction, configuration index, type)] — the raw
material for WAT hover. The configuration index distinguishes entries from
different explored configurations (conditional compilation), so a consumer can
join a single configuration's stack results as a tuple yet keep the types a
config-varying span takes across configurations apart. [None] on ordinary
validation, so the recording is free. *)letrecorded_types:(Ast.location*int*recorded_type)listrefoptionref=refNone(* The configuration currently being validated (0 for a module without
conditional compilation; bumped for each configuration {!Cond_explore}
explores), tagging every recorded entry. *)letsink_config=ref0(* Record [rt] at instruction span [loc], if the sink is active and [loc] is a
real source span (not a synthesized / recovery placeholder). No rendering
here — an ordinary validation pays nothing beyond the [!recorded_types]
test. *)letrecordlocrt=match(!recorded_types,loc)with|Somer,Somelwhenl.Ast.loc_start.Lexing.pos_cnum>=0->r:=(l,!sink_config,rt)::!r|_->()(* A named index with a zero-width span is one error recovery synthesized in
place — the placeholder [$_] it inserts for a missing index ([(call)] repaired
to [(call $_)]). A diagnostic anchored solely to it (the placeholder name
being unbound) is suppressed: the "Missing index" syntax error already stands
there. The check is narrow on purpose — a real [$id] spans at least two
characters, and an {e omitted} index that defaults to [0] (e.g. the implicit
memory of [memory.copy]) is a [Num], whose unbound-ness is a genuine error —
so only the synthetic named placeholder is caught. *)letis_recovery_placeholder(idx:Ast.Text.idx)=matchidx.Ast.descwith|Ast.Text.Id_->idx.info.Ast.loc_start.Lexing.pos_cnum=idx.info.Ast.loc_end.Lexing.pos_cnum|Ast.Text.Num_->false(* Reconstruct a source type from an interned one. Used as the source type of a
pushed value when no truer reference is available, so every concrete stack
value carries a source type (as on the Wax side, where an inferred type
bundles both forms). Only abstract heap types are reconstructed this way: a
concrete [Type] reference always carries a truer source from its declaration. *)letsource_of_heaptype(h:heaptype):Ast.Text.heaptype=matchhwith|Func->Func|NoFunc->NoFunc|Exn->Exn|NoExn->NoExn|Cont->Cont|NoCont->NoCont|Extern->Extern|NoExtern->NoExtern|Any->Any|Eq->Eq|I31->I31|Struct->Struct|Array->Array|None_->None_|Type_|Exact_->assertfalseletsource_of_valtype(ty:valtype):source_type=Plain(matchtywith|I32->I32|I64->I64|F32->F32|F64->F64|V128->V128|Ref{nullable;typ}->Ref{nullable;typ=source_of_heaptypetyp})(*** Diagnostics ***)letloc_first_char(loc:Ast.location)=letloc_start=loc.Ast.loc_startin{locwithloc_end={loc_startwithLexing.pos_cnum=loc_start.Lexing.pos_cnum+1};}letloc_last_char(loc:Ast.location)=letloc_end=loc.Ast.loc_endin{locwithAst.loc_start={loc_endwithLexing.pos_cnum=loc_end.Lexing.pos_cnum-1};}moduleError=structopenWax_utilsmoduleD=Diagnostic(* Message-building combinators (see {!Wax_utils.Message}). Prose is [text],
joined with [++] (soft, wrap-point space) or [^^] (no space). An emphasized
atom — [styp]/[sources] a source type, [index]/[ident] an identifier, [str]
a string literal, [num] a numeric literal — is coloured when the theme is
coloured and quoted ['…'] when it is not. Its text is produced by the
[Format] [print_*] printers above and emitted as one styled atom. *)lettext=Message.textletkw=Message.codelet(++)=Message.(++)let(^^)=Message.(^^)(* Render an AST fragment ([render], drawing into the styled printer) as one
emphasized atom: coloured when the theme is coloured, wrapped in ['…'] when
it is not. [style] is the atom's colour — forced over the whole fragment
(via [with_style]) so a type reads as one unit rather than syntax-
highlighting its parens/keywords/idents in separate role colours — and it
also decides the quoting. *)letstyled_atomstylerender=Message.raw(funpp->letp=pp.Styled_printer.printerinletquote=Colors.escape_sequencepp.Styled_printer.themestyle=""inifquotethenPrinter.stringp"'";Styled_printer.with_styleppstyle(fun()->renderpp);ifquotethenPrinter.stringp"'")letstypsource=styled_atomColors.Type(funpp->print_source_typeppsource)letindexidx=styled_atomColors.Identifier(funpp->print_indexppidx)letidentid=styled_atomColors.Identifier(funpp->print_identppid)letstrs=styled_atomColors.String(funpp->print_stringpps)letnums=Message.styledColors.Constantsletreportcontext~location~severity?warning?universal?hint?relatedmessage=(* In error-recovery mode (see [Wax_utils.Parsing.parse_recover], used by the editor
to validate a best-effort partial AST across syntax errors) the module's
whole-module analyses are unreliable, so suppress every warning — the same
policy the Wax typer applies in recovery. Errors still surface, so a real
defect in an intact region shows; the few error {e cascades} a dropped or
auto-closed construct triggers are suppressed at their own call sites (see
[empty_stack]/[non_empty_stack]/[leftover_values]). *)matchseveritywith|D.WarningwhenD.in_recoverycontext->()|_->D.reportcontext~location~severity?warning?universal?hint?related~message()letdid_you_mean=function|[]->None|suggestions->Some(text"Did you mean"++Message.enumerate~conj:"or"(List.mapMessage.identsuggestions)^^text"?")(* A zero-width [Id] is a placeholder to suppress the cascade from — but only
in error-recovery mode, where the parser inserts them. Outside recovery a
zero-width [Id] instead marks a name synthesized by [Binary_to_text] (which
uses [no_loc]); its unbound-index error is a genuine soundness finding on a
malformed binary and must be reported, not swallowed. *)letsuppress_placeholdercontextid=is_recovery_placeholderid&&D.in_recoverycontextletunbound_labelcontext~locationidlst=ifsuppress_placeholdercontextidthen()elsereportcontext~location~severity:Error?hint:(did_you_meanlst)(text"Unknown label:"++indexid++text"is not bound.")letunbound_indexcontext~locationkindidlst=ifsuppress_placeholdercontextidthen()elsereportcontext~location~severity:Error?hint:(did_you_meanlst)((text"Unknown"++textkind)^^(text": index"++indexid++text"is not bound."))letpacked_array_accesscontext~location=reportcontext~location~severity:Error(text"This instruction cannot be used on packed arrays. Use array.get_s or \
array.get_u to specify sign extension.")letunpacked_array_accesscontext~location=reportcontext~location~severity:Error(text"This instruction is only valid for packed arrays. Use array.get.")letpacked_struct_accesscontext~location=reportcontext~location~severity:Error(text"This instruction cannot be used on packed fields. Use struct.get_s \
or struct.get_u to specify sign extension.")letunpacked_struct_accesscontext~location=reportcontext~location~severity:Error(text"This instruction is only valid for packed fields. Use struct.get.")(* The caret points at the instruction that {e produced} the value still on
the stack, not at the one consuming it (whose location is [consumer]): the
wording makes that explicit, and a secondary caret marks the use site. *)letinstruction_type_mismatchcontext~location~consumer~provided_source~expected_source=(* Mark the use site with a secondary caret, but only when it is a distinct
location that does not enclose the producer: an implicit function or
block result spans the whole construct, so a caret there would just be
noise around the precise one. *)letenclosesouterinner=outer.Ast.loc_start.Lexing.pos_cnum<=inner.Ast.loc_start.Lexing.pos_cnum&&inner.Ast.loc_end.Lexing.pos_cnum<=outer.Ast.loc_end.Lexing.pos_cnuminletrelated=matchconsumerwith|Somelocwhenloc.Ast.loc_start.Lexing.pos_cnum>=0&¬(enclosesloclocation)->[{Wax_utils.Diagnostic.location=loc;message=text"expected here";};]|_->[]inreportcontext~location~severity:Error~related(text"Type mismatch: this produces a value of type"++stypprovided_source^^text","++text"but type"++stypexpected_source++text"is expected.")letexpected_ref_typecontext~location~src_loc~source=matchsrc_locwith|None->reportcontext~location~severity:Error(text"Type mismatch: expected reference type but got type"++stypsource^^text".")|Somelocation->reportcontext~location~severity:Error(text"Type mismatch: this instruction should return a reference type \
but has type"++stypsource^^text".")lettable_type_mismatchcontext~location~sourceidx=reportcontext~location~severity:Error(text"Type mismatch: the table"++indexidx++text"should contain functions but its elements have type"++stypsource^^text".")letelem_segment_type_mismatchcontext~location~elem_source~table_source=reportcontext~location~severity:Error((text"Type mismatch: the element segment has type"++stypelem_source)^^text","++text"which is not a subtype of the table element type"++styptable_source^^text".")letduplicate_localcontext~location~prev_locname=reportcontext~location~severity:Error~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"previously defined here";};](text"The local"++identname++text"is already defined.")lettype_mismatchcontext~location~provided_source~expected_source=reportcontext~location~severity:Error(text"Type mismatch: expecting type"++stypexpected_source++text"but got type"++stypprovided_source^^text".")letbr_cast_type_mismatchcontext~location=reportcontext~location~severity:Error(text"Type mismatch: the first type must be a supertype of the second one.")letbr_on_non_null_no_refcontext~location=reportcontext~location~severity:Error(text"Type mismatch:"++kw"br_on_non_null"++text"requires the target label to end in a reference type, but it has \
no result types.")letselect_type_mismatchcontext~location~loc1~source1~loc2~source2=(* Point a caret at each branch value (when its push site is known),
labelled with its type. A placeholder location uses a negative column;
skip those, as in [locations]. *)letbranch_labellocsource=matchlocwith|Somelocwhenloc.Ast.loc_start.Lexing.pos_cnum>=0->Some{Wax_utils.Diagnostic.location=loc;message=stypsource}|_->Noneinletrelated=List.filter_mapFun.id[branch_labelloc1source1;branch_labelloc2source2]in(* When both carets are shown they carry the types; otherwise name the two
types in the message so they are not lost. *)letmessage=ifList.lengthrelated=2thentext"Type mismatch: both branches of a"++kw"select"++text"should have the same type."elsetext"Type mismatch: both branches of a"++kw"select"++text"should have the same type."++text"Here, they have type"++stypsource1++text"and"++stypsource2^^text"."inreportcontext~location~severity:Error~relatedmessage(* The stack-shape mismatches ([empty_stack], [non_empty_stack],
[leftover_values]) are the error cascades a partial AST triggers: an
auto-closed body ([(func (i32.const 1)] at EOF) or a dropped instruction
leaves the operand stack the wrong height through no fault of the intact
code. Suppress them in recovery mode — the analogue of the Wax typer
dropping leftover Error-typed values under [with_empty_stack]. *)letempty_stackcontext~location=ifD.in_recoverycontextthen()elsereportcontext~location~severity:Error(text"Type mismatch: the stack is empty (a value is missing).")(* The counted variant of [empty_stack], for a pop whose caller knows how
many values the construct takes ([pop_args]): say how many were expected
and how many were there (the Wax typer's [short_stack]). *)letshort_stackcontextkind~location~actual~expected=letvalues=matchkindwith`Input->"argument(s)"|`Output->"returned value(s)"inifD.in_recoverycontextthen()elsereportcontext~location~severity:Error(text"Type mismatch: expecting"++Message.intexpected++textvalues++text"from the stack, but there are"++Message.intactual^^text".")letnon_empty_stackcontext~locationrender=ifD.in_recoverycontextthen()elsereportcontext~location:(loc_last_charlocation)~severity:Error(text"Type mismatch: unexpected values left on the stack:"^^Message.rawrender)(* Report the values still on the stack by pointing a caret at each of them.
[location] carries the topmost value; [related] the others. *)letleftover_valuescontext~location~related=ifD.in_recoverycontextthen()elsereportcontext~location~severity:Error~related(text(ifrelated=[]then"Type mismatch: this value is left on the stack."else"Type mismatch: these values are left on the stack."))(* Print a list of source types, [\[a b c\]]. *)letprint_sourcesppsource=sp_boxpp(fun()->sp_punctpp"[";Array.iteri(funis->ifi>0thensp_spacepp;print_source_typepps)source;sp_punctpp"]")letsourcessource=styled_atomColors.Type(funpp->print_sourcesppsource)letargument_count_mismatchcontext~location~descr~provided_source~expected_source=reportcontext~location~severity:Error(text"Type mismatch:"++textdescr++text"provides type"++sourcesprovided_source++text"but type"++sourcesexpected_source++text"was expected.")(* [nth] names the mismatching position when several values are compared
([None] for a single value, where it would only be noise). *)letargument_type_mismatchcontext~location~descr~nth~provided_source~expected_source=letposition=matchnthwith|Somen->text"for value"++Message.intn|None->Message.emptyinreportcontext~location~severity:Error(text"Type mismatch:"++textdescr++text"provides type"++stypprovided_source++position++text"but type"++stypexpected_source++text"was expected.")letbranch_parameter_count_mismatchcontext~location~default_loclabellenlabel'len'=reportcontext~location~severity:Error~related:[{Wax_utils.Diagnostic.location=default_loc;message=text"default branch target here";};](text"Type mismatch: the default branch target"++indexlabel++text"expects"++Message.intlen++text"parameters, while branch target"++indexlabel'++text"expects"++Message.intlen'++text"parameters.")letmemory_offset_too_largecontext~locationmax_offset=reportcontext~location~severity:Error(text"The memory offset should be less than"++num(Printf.sprintf"0x%Lx"(Uint64.to_int64max_offset))^^text".")letmemory_align_too_largecontext~locationnatural=reportcontext~location~severity:Error(text"The memory alignment is larger than the natural alignment"++Message.intnatural^^text".")letbad_memory_aligncontext~location=reportcontext~location~severity:Error(text"The memory alignment should be a power of two.")letatomic_alignmentcontext~locationnatural=reportcontext~location~severity:Error(text"The alignment of an atomic access must be its natural alignment"++Message.intnatural^^text".")letinvalid_lane_indexcontext~locationmax_lane=reportcontext~location~severity:Error((text"The lane index should be less than"++Message.intmax_lane)^^text".")(* The definition's parameters/results are passed as source types (from its
declaration), not reconstructed from the resolved [functype] via
[source_of_valtype]: a definition may name a concrete reference ([(ref
$t)]/[(ref (exact $t))]), which [source_of_heaptype] cannot rebuild. *)letinline_function_type_mismatchcontext~location~params~results=reportcontext~location~severity:Error(text"The inline function type does not match the type definition, whose \
parameters are"++sourcesparams++text"and results are"++sourcesresults^^text".")letconstant_expression_requiredcontext~location=reportcontext~location~severity:Error(text"Only constant expressions are allowed here.")letimmutable_globalcontext~locationidx=reportcontext~location~severity:Error(text"The global"++indexidx++text"should be mutable.")letlimit_too_largecontext~locationkindmax=reportcontext~location~severity:Error(text"The"++textkind++text"size is too large. It should be less than"++num(Printf.sprintf"0x%Lx"(Uint64.to_int64max))^^text".")letinvalid_page_sizecontext~location=reportcontext~location~severity:Error(text"The custom page size must be 1 or 65536.")letbranch_hint_invalid_targetcontext~location=reportcontext~location~severity:Error(text"A branch hint may only prefix a conditional branch (if, br_if, or \
br_on_*).")letinstr_freq_invalid_targetcontext~location=reportcontext~location~severity:Error(text"An instruction-frequency hint may only prefix a call or a control \
instruction.")letcall_targets_invalid_targetcontext~location=reportcontext~location~severity:Error(text"A call-target hint may only prefix an indirect call (call_ref or \
call_indirect).")letcall_targets_over_100context~location~total=reportcontext~location~severity:Error((text"The call-target frequencies add up to"++num(string_of_inttotal)^^text"%, more than 100%.")++text"A shortfall is how the hint says other, unlisted targets take the \
remainder.")letshared_memory_without_maxcontext~location=reportcontext~location~severity:Error(text"A shared memory must have a maximum size.")letlimit_mismatchcontext~locationkind=reportcontext~location~severity:Error(text"The"++textkind++text"maximum size should be larger than the minimal size.")letduplicated_exportcontext~location~prev_locname=reportcontext~location~severity:Error~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"previously exported here";};]((text"There is already an export of name"++strname)^^text".")letimport_after_definitioncontext~location~prev_lockind=reportcontext~location~severity:Error~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"first definition here";};](text"This import is after a"++textkind++text"definition.")letsupertype_mismatchcontext~location=reportcontext~location~severity:Error(text"The supertype is not of the same kind as this type.")letinvalid_subtypecontext~location=reportcontext~location~severity:Error(text"This type is not a valid subtype of its declared supertype.")letdescriptor_outside_rec_groupcontext~location~described=reportcontext~location~severity:Error(text"The"++text(ifdescribedthen"described"else"descriptor")++text"type must be in the same recursion group.")letdescriptor_not_reciprocalcontext~location~described=reportcontext~location~severity:Error(text(ifdescribedthen"This descriptor does not describe the type it is attached to."else"The descriptor of this type does not describe it back."))letforward_use_of_describedcontext~location=reportcontext~location~severity:Error(text"A described type must be declared before its descriptor.")letdescriptor_finality_mismatchcontext~location=reportcontext~location~severity:Error((text"A type and its descriptor must both be"++kw"final")^^text", or neither.")letdescriptor_not_structcontext~location~described=reportcontext~location~severity:Error(text"A"++text(ifdescribedthen"described"else"descriptor")++text"type must be a struct type.")letnot_function_typecontext~location=reportcontext~location~severity:Error(text"This should be a function type.")letexception_tag_with_resultscontext~location=reportcontext~location~severity:Error(text"The type of an exception tag must have no results.")letselect_result_countcontext~location=reportcontext~location~severity:Error(text"A typed"++kw"select"++text"must be annotated with exactly one result type.")letnon_nullable_table_typecontext~location=reportcontext~location~severity:Error(text"Type mismatch: the type of the elements of this table must be \
nullable.")letuninitialized_localcontext~locationidx=reportcontext~location~severity:Error(text"The local variable"++indexidx++text"has not been initialized.")(* A local that is declared but never read. Prefix its name with [_] to
silence the warning. *)letunused_localcontext~locationname=reportcontext~location~severity:Warning~warning:Warning.Unused_local~universal:true(matchnamewith|Someid->text"The local variable"++identid++text"is never used."|None->text"This local is never used.")(* A module field (a function, global, memory, table, tag, or a passive
data/element segment) defined but never referenced, exported, or used as the
start function. Prefix its name with [_] to silence the warning. *)letunused_fieldcontext~locationkindname=reportcontext~location~severity:Warning~warning:Warning.Unused_field~universal:true(matchnamewith|Someid->text"The"++textkind++identid++text"is never used."|None->text"This"++textkind++text"is never used.")(* An imported field never referenced, exported, or used as the start function.
Prefix its name with [_] to silence the warning. *)letunused_importcontext~locationkindname=reportcontext~location~severity:Warning~warning:Warning.Unused_import~universal:true(matchnamewith|Someid->text"The imported"++textkind++identid++text"is never used."|None->text"This imported"++textkind++text"is never used.")(* A block label declared but never branched to. Prefix its name with [_] to
silence the warning. *)letunused_labelcontext~locationname=reportcontext~location~severity:Warning~warning:Warning.Unused_label~universal:true(text"The label"++identname++text"is never used.")(* A module-defined global declared [mut] but never the target of a
[global.set]. An import (whose mutability is part of the linking contract)
and an exported global (which the host may assign) are exempt, as is a name
starting with [_]. *)letunnecessary_mutcontext~locationname=reportcontext~location~severity:Warning~warning:Warning.Unnecessary_mut~universal:true~hint:(text"Drop the 'mut' to declare it immutable.")(matchnamewith|Someid->text"The global"++identid++text"is mutable but is never assigned."|None->text"This global is mutable but is never assigned.")(* --- The correctness lint tier (shared with the Wax typer; same warnings and
wording). Emitted while validating a WAT/WASM function body. --- *)letwarn_lintcontext~location?hint?relatedwarningmessage=reportcontext~location~severity:Warning~warning~universal:true?hint?relatedmessageletconfusable_unicodecontext~locationu=warn_lintcontext~locationWarning.Confusable_unicode(text(Printf.sprintf"This string contains a bidirectional control character (U+%04X) \
that can make the displayed text read differently than it runs."(Uchar.to_intu)))(* [count] is the shift count as an unsigned 64-bit value (an i32/i64 const
with the high bit set is a large positive count, not a negative one), so
print and reduce it unsigned — matching the Wax typer's [shift_overflow]. *)letshift_overflowcontext~location~widthcount=warn_lintcontext~locationWarning.Shift_overflow~hint:((text"Wasm masks the count modulo"++Message.intwidth)^^text","++text"shifting by"++Message.uint64(Int64.unsigned_remcount(Int64.of_intwidth))++text"instead.")(text"The shift count"++Message.uint64count++text"is at least the operand width ("^^Message.intwidth^^text" bits).")letdivision_by_zerocontext~location=warn_lintcontext~locationWarning.Constant_trap(text"This integer division or remainder by zero always traps.")letconversion_out_of_rangecontext~location=warn_lintcontext~locationWarning.Constant_trap(text"This conversion always traps: the constant is out of the target \
type's range.")lettautological_comparisoncontext~location~value=warn_lintcontext~locationWarning.Tautological_comparison((text"This comparison is always"++Message.boolvalue)^^text".")letconstant_conditioncontext~location~value=warn_lintcontext~locationWarning.Constant_condition((text"This condition is always"++Message.boolvalue)^^text".")letunused_resultcontext~location=warn_lintcontext~locationWarning.Unused_result(text"The result of this expression is discarded, and computing it has no \
effect.")letdead_codecontext~location~related=warn_lintcontext~location~relatedWarning.Dead_code(text"This code is unreachable.")(* A conditional-annotation branch no configuration selects (the mirror of
the Wax typer's [dead_branch]). *)letdead_branchcontext~location~side=warn_lintcontext~locationWarning.Dead_code(text(ifsidethen"The @then branch of this conditional is unreachable:"else"The @else branch of this conditional is unreachable:")++text"no configuration selects it.")letredundant_operationcontext~locationmessage=warn_lintcontext~locationWarning.Redundant_operationmessageletcast_always_failscontext~location~is_test=warn_lintcontext~locationWarning.Cast_always_fails(text(ifis_testthen"This type test is always false: the value can never have this \
type."else"This cast always traps: the value can never have this type."))letredundant_castcontext~location~is_test=warn_lintcontext~locationWarning.Redundant_operation(text(ifis_testthen"This type test is always true: the value already has this type."else"This cast is redundant: the value already has this type."))(* A trapping or effectful operation among the value operands of a [select],
which evaluates both operands unconditionally. Mirrors the Wax typer's
[eager-select] lint (a Wax [?:] compiles to a [select]). [select] points at
the [select] instruction. *)leteager_selectcontext~location~select=warn_lintcontext~locationWarning.Eager_select~related:[{Wax_utils.Diagnostic.location=select;message=text"This"++kw"select"++text"evaluates both of its operands.";};](text"This operation is evaluated even when the condition selects the \
other operand.")letindex_already_boundcontext~location~prev_lockindindex=reportcontext~location~severity:Error~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"previously bound here";};](text"The"++textkind++text"index"++identindex.Ast.desc++text"is already bound.")letexpected_func_typecontext~locationidx=reportcontext~location~severity:Error(text"Type"++indexidx++text"should be a function type.")letexpected_struct_typecontext~locationidx=reportcontext~location~severity:Error(text"Type"++indexidx++text"should be a struct type.")letexpected_array_typecontext~locationidx=reportcontext~location~severity:Error(text"Type"++indexidx++text"should be an array type.")letexpected_cont_typecontext~locationidx=reportcontext~location~severity:Error(text"Type"++indexidx++text"should be a continuation type.")letstack_switching_type_mismatchcontext~location~descr=reportcontext~location~severity:Error((text"Type mismatch in this stack switching instruction:"++textdescr)^^text".")letinvalid_cast_typecontext~location=reportcontext~location~severity:Error(text"Continuation types cannot be used in a cast instruction.")lettype_without_descriptorcontext~location=reportcontext~location~severity:Error(text"This descriptor instruction requires a type that has a descriptor.")letfeature_disabledcontext~locationfeature=reportcontext~location~severity:Error(text"This uses the"++text(Wax_utils.Feature.namefeature)++text"feature, which is not enabled; pass --feature"++text(Wax_utils.Feature.namefeature)^^text".")letunknown_featurecontext~locationname=reportcontext~location~severity:Error((text"Unknown feature"++Message.codename)^^text". Known features:"++text(String.concat", "(List.mapWax_utils.Feature.nameWax_utils.Feature.all))^^text".")letfeature_conflictcontext~locationfeature=reportcontext~location~severity:Error(text"This module requires the"++text(Wax_utils.Feature.namefeature)++text"feature, which is disabled on the command line; drop --feature"++text(Wax_utils.Feature.namefeature^"=off")^^text".")letdescriptor_allocation_requiredcontext~location=reportcontext~location~severity:Error(text"A type with a descriptor must be allocated with a descriptor \
(struct.new_desc / struct.new_default_desc).")(* [src_loc], when known, is the offending operand's push location; the
report is anchored there (like [expected_ref_type]) so two failing
operands of one instruction point at themselves, not both at the
instruction. *)letexpected_number_or_veccontext~location~src_loc~source=matchsrc_locwith|None->reportcontext~location~severity:Error(text"Type mismatch: expecting a numeric or vector type but got type"++stypsource^^text".")|Somelocation->reportcontext~location~severity:Error((text"Type mismatch: this produces a value of type"++stypsource)^^text", but a numeric or vector type is expected.")letimmutablecontext~locationwhat=reportcontext~location~severity:Error(text"This"++textwhat++text"is immutable.")letnot_defaultablecontext~location=reportcontext~location~severity:Error(text"This type has no default value for all its fields.")letfield_index_out_of_boundscontext~location~index~count=reportcontext~location~severity:Error(text"The field index"++Message.intindex++text"is out of bounds: the structure has"++Message.intcount++text"field(s).")letunknown_fieldcontext~location=reportcontext~location~severity:Error(text"There is no such field.")letnumeric_array_requiredcontext~location=reportcontext~location~severity:Error(text"This operation requires an array of numeric elements.")letstring_array_requiredcontext~location=reportcontext~location~severity:Error(text"A string can only build an i8 or i16 array.")letstring_not_unicodecontext~location=reportcontext~location~severity:Error(text"A string building an i16 array must be a valid Unicode string.")letincompatible_array_elementcontext~location=reportcontext~location~severity:Error(text"The array element type is incompatible.")letref_func_inaccessiblecontext~locationidx=reportcontext~location~severity:Error((text"The function"++indexidx++text"is not declared as referenceable ("^^kw"ref.func")^^text").")letnon_constant_globalcontext~locationidx=reportcontext~location~severity:Error(text"Only an immutable global may be used in a constant expression:"++indexidx^^text".")letstart_function_signaturecontext~location=reportcontext~location~severity:Error(text"The start function must have no parameters and no results.")letmultiple_startcontext~location~prev_loc=reportcontext~location~severity:Error~related:[{Wax_utils.Diagnostic.location=prev_loc;message=text"other start function here";};](text"A module can have at most one start function.")endletprint_instri=Wax_utils.Printer.run_err(funp->Output.instrpi)(* A diagnostics context that drops everything reported to it. Passed to a
pre-pass that resolves references the validating pass resolves (and reports)
again, so a broken reference is diagnosed once, by the pass that owns it. *)letmutedd=Wax_utils.Diagnostic.collector~parent:d()(*** Symbol tables (sequences) ***)moduleSequence=struct(* A poisoned entry ([None] in [index_mapping]) claims an index for a
definition whose own resolution failed: [last_index] still advances, so
later definitions keep their positions and numeric references stay aligned,
but [get]/[find] resolve it to [None] silently (no unbound-index error —
the real error was reported at the definition site). *)type'at={name:string;index_mapping:(int,'aoption)Hashtbl.t;label_mapping:(string,int)Hashtbl.t;mutablelast_index:int;}letmakename={name;index_mapping=Hashtbl.create16;label_mapping=Hashtbl.create16;last_index=0;}(* The index the next [register] will assign (the current length). *)letnext_indexseq=seq.last_indexletregisterseqidv=letidx=seq.last_indexinseq.last_index<-seq.last_index+1;Hashtbl.addseq.index_mappingidx(Somev);Option.iter(funid->Hashtbl.addseq.label_mappingid.Ast.descidx)id(* Claim the next index for a definition whose own resolution failed, so later
definitions keep their positions. The entry is poisoned (see the type). *)letregister_failedseqid=letidx=seq.last_indexinseq.last_index<-seq.last_index+1;Hashtbl.addseq.index_mappingidxNone;Option.iter(funid->Hashtbl.addseq.label_mappingid.Ast.descidx)idletgetdseq(idx:Ast.Text.idx)=matchmatchidx.descwith|Numn->Hashtbl.find_optseq.index_mapping(Uint32.to_intn)|Idid->Option.bind(Hashtbl.find_optseq.label_mappingid)(Hashtbl.find_optseq.index_mapping)with|Someentry->(* [Some v] is a real definition; [None] a poisoned entry, resolved
silently. *)entry|None->letlst=matchidx.descwith|Num_->[]|Idid->Wax_utils.Spell_check.f(funf->Hashtbl.iter(funid'_->fid')seq.label_mapping)idinError.unbound_indexd~location:idx.infoseq.nameidxlst;Noneletget_indexseq(idx:Ast.Text.idx)=matchidx.descwith|Numn->Uint32.to_intn|Idid->(tryHashtbl.findseq.label_mappingidwithNot_found->assertfalse(* Should not happen *))(* Resolve to an index without reporting or raising when a name is unbound —
for callers that only want to note a resolvable reference and leave the
error reporting to the pass that validates the reference. *)letget_index_optseq(idx:Ast.Text.idx)=matchidx.descwith|Numn->Some(Uint32.to_intn)|Idid->Hashtbl.find_optseq.label_mappingid(* Resolve to the value without reporting when the reference is unbound (or is
a poisoned entry) — a silent [get], for the same kind of caller as
[get_index_opt]. *)letfindseq(idx:Ast.Text.idx)=matchidx.descwith|Numn->Option.join(Hashtbl.find_optseq.index_mapping(Uint32.to_intn))|Idid->Option.join(Option.bind(Hashtbl.find_optseq.label_mappingid)(Hashtbl.find_optseq.index_mapping))end(*** Types and the type context ***)(* Where the reference currently being resolved is made from, for the reachability
analysis behind [unused-field]. Lives on the type context (which every
resolution point can reach) and is read for every index space, not just types.
[Root] is a module-level context — an export, the start function, an import
declaration, a global or table initializer, a segment — which runs, or is
callable, whatever the bodies do. [Ignored] suppresses recording, for a
resolution that is not a source reference at all: [check_type_definitions]
sweeps every type index to check its own well-formedness, and marking those
would make every type look used. *)typeorigin=|Root|From_functionofint|From_typeofint(** a type definition's own components *)|Ignored(* A source type reference, as written. Kept unresolved (rather than as a
canonical index) because deduplication makes canonical indices non-injective:
two structurally equal named types share one, so a reference to either would
mark both used. [unused_fields] maps these back to definition indices through
[type_defs]. *)typetype_ref=By_indexofint|By_nameofstringtypetype_context={types:Types.t;mutablelast_index:int;(* Keyed by a source type reference (numeric index / name): the resolved
global index, a struct's field-name-to-position map, and the source
composite type. The last lets an error name a component (a struct field,
an array element, a function result) as the source wrote it; keying by the
reference (rather than the deduplicated global index) keeps it injective,
so [$a] is named with [$a] even when a structurally-equal [$b] shares its
global index. *)(* The fourth component is the source subtype definition — kept, keyed by the
(injective) source reference so hover on a type identifier shows the type
as written; [None] for a synthesized (implicit) function type, which has no
source. *)index_mapping:(Uint32.t,Types.ref_index*(string*int)list*Ast.Text.comptype*(Ast.Text.nameoption*Ast.Text.subtype,Ast.location)Ast.annotatedoption)Hashtbl.t;label_mapping:(string,Types.ref_index*(string*int)list*Ast.Text.comptype*(Ast.Text.nameoption*Ast.Text.subtype,Ast.location)Ast.annotatedoption)Hashtbl.t;(* Text indices / names of poisoned type definitions: a rec group whose own
resolution failed still advances [last_index] (so later numeric type
references stay aligned) but registers no mapping. These sets let
[get_type_info] resolve such a reference to [None] silently instead of
reporting "unknown type" again — the real error was reported at the
definition. The analogue of {!Sequence}'s poisoned entries. *)poisoned_index:(Uint32.t,unit)Hashtbl.t;poisoned_label:(string,unit)Hashtbl.t;(* For each type definition, keyed by its text-level index: its source index
node (its name when it has one, else its numeric index, carrying the
definition's location), and — for a continuation type — the source
reference to the wrapped type. Keying by text index (rather than the
resolved, deduplicated global index) keeps the mapping injective, so a
check on a type is reported at the exact definition and names types as they
appear in the source. *)type_defs:(int,Ast.Text.idx*Ast.Text.idxoption)Hashtbl.t;(* For a type carrying a [descriptor] clause, keyed by its resolved global
index: the source reference to its descriptor type. The descriptor
instructions derive the descriptor type from the described one, so this
recovers the descriptor's source name for error rendering (there is no
immediate to name it by). Deduplicated types share a descriptor, so keying
by the global index is unambiguous. *)descriptor_source:(Types.Id.t,Ast.Text.idx)Hashtbl.t;(* The enabled optional features / proposals, and which are used. *)features:Wax_utils.Feature.set;(* Where references are currently being made from (see {!origin}), and every
type reference seen so far. Both feed the [unused-field] reachability
analysis; nothing is recorded when the lint is off. A type reference is
recorded here rather than in a [used_*] table because a type's liveness
depends on the liveness of whatever names it, type definitions included. *)mutableorigin:origin;mutabletype_references:(origin*type_ref)list;(* References to a type by its *canonical* index rather than by a source name,
which is how the implicit Wax string type is reached: [@string] interns
[(array (mut i8))] structurally, so a module that also defines that type by
name is deduplicated onto it and uses the definition without ever naming it.
Resolved against every source definition sharing the canonical index — all
of them, since deduplication makes that ambiguous and over-keeping is the
safe direction. *)mutablecanonical_type_references:(origin*Types.Id.t)list;(* Whether the [unused-field] analysis runs, so the recording above can be
skipped entirely otherwise. Mirrors the module context's [warn_unused],
which is not reachable from every resolution point. *)record_references:bool;}(* The source composite type a reference resolves to, named as the source wrote
it (injective), or [None] for an unbound or sourceless reference. Does not
report errors — callers that resolve the reference do. *)letreference_comptypetc(idx:Ast.Text.idx)=let_,_,c,_=matchidx.descwith|Numx->Hashtbl.findtc.index_mappingx|Idid->Hashtbl.findtc.label_mappingidinc(* The source function type a reference resolves to, when it names one. *)letreference_functypetcidx=matchreference_comptypetcidxwith|Funcft->ft|Struct_|Array_|Cont_->assertfalse(* The source function type a [typeuse] denotes: the one named by its type
reference, or its inline signature. Resolve the reference in preference to the
inline signature, consistent with [typeuse] (which drives the corresponding
[return_types]); for valid input the two agree, and preferring one uniformly
keeps their arities in step when a malformed module gives both a [(type $i)]
and a disagreeing inline signature. *)lettypeuse_functypetc(tu_idx,tu_sign)=match(tu_idx,tu_sign)with|Someidx,_->reference_functypetcidx|None,Someft->ft|_->assertfalse(* Per-element source types for a source function type's params and results, to
pass straight to [pop_args]/[push_results]'s [~source]. *)letfunctype_sources({params;results}:Ast.Text.functype)=(Array.map(funp->Plain(sndp.Ast.desc))params,Array.map(funv->Plainv)results)(* The source function type that the continuation type named by [idx] wraps. *)letcont_source_functypetcidx=matchreference_comptypetcidxwith|Contr->reference_functypetcr|_->assertfalseletget_type_infodctx(idx:Ast.Text.idx)=letpoisoned=matchidx.descwith|Numx->Hashtbl.memctx.poisoned_indexx|Idid->Hashtbl.memctx.poisoned_labelidinletresult=ifpoisonedthen(* Silently: the definition site already reported. *)Noneelsetrymatchidx.descwith|Numx->Some(Hashtbl.findctx.index_mappingx)|Idid->Some(Hashtbl.findctx.label_mappingid)withNot_found->letlst=matchidx.descwith|Num_->[]|Idid->Wax_utils.Spell_check.f(funf->Hashtbl.iter(funid'_->fid')ctx.label_mapping)idinError.unbound_indexd~location:idx.info"type"idxlst;Nonein(* The single resolution point for every type reference — a typeuse, a named
reference type wherever one may appear, a supertype or descriptor clause, an
instruction's type immediate — so this is where one is recorded for the
[unused-field] analysis. Resolution failures are recorded too, harmlessly:
they name no definition. *)ifctx.record_references&&ctx.origin<>Ignoredthenctx.type_references<-(ctx.origin,matchidx.descwith|Numx->By_index(Uint32.to_intx)|Idid->By_nameid)::ctx.type_references;(* Record the referenced type's source definition, so hover over the type
identifier shows its subtype. Guarded on the sink, so an ordinary
validation pays nothing. *)(match(!recorded_types,result)with|Some_,Some(_,_,_,Somee)->record(Someidx.info)(Subtypee)|_->());result(* The type context in force during the current body validation, so [push] can
resolve a pushed value's named reference type to the type's definition for
go-to-type-definition. Set (editor mode only) by [validate_configuration];
like [recorded_types] it avoids threading through the push chokepoints. *)letsink_type_context:type_contextoptionref=refNone(* The source subtype entry a type reference resolves to (its definition),
without reporting or recording — a silent [get_type_info]. *)letlookup_subtype_entrytc(idx:Ast.Text.idx)=matchmatchidx.descwith|Numx->Hashtbl.find_opttc.index_mappingx|Idid->Hashtbl.find_opttc.label_mappingidwith|Some(_,_,_,e)->e|None->None(* The source [comptype] a type reference resolves to. Unlike the subtype entry
above, the source comptype is recorded for *every* mapped type, synthesized
ones included, so a resolved index always has one. *)letlookup_source_comptypetc(idx:Ast.Text.idx)=matchmatchidx.descwith|Numx->Hashtbl.find_opttc.index_mappingx|Idid->Hashtbl.find_opttc.label_mappingidwith|Some(_,_,ct,_)->Somect|None->None(* If [source] is a value of a named reference type, record its type's
definition span at [loc] for go-to-type-definition. *)letrecord_value_type_deflocsource=match(!recorded_types,loc,!sink_type_context)with|Somer,Somel,Sometcwhenl.Ast.loc_start.Lexing.pos_cnum>=0->(matchsourcewith|Plain(Ast.Text.Ref{typ=Typeidx|Exactidx;_})->(matchlookup_subtype_entrytcidxwith|Somee->letdef=matchfste.Ast.descwith|Some(n:Ast.Text.name)->n.Ast.info|None->e.Ast.infoinr:=(l,!sink_config,Value_typedef)::!r|None->())|_->())|_->()(* Resolve a source type reference to how it should appear inside a rec group
being registered: [Def id] for an already-defined type, [Rec pos] for a member
of the group currently under construction. This is what the type-definition
builders below produce. *)letresolve_type_refdctxidx=let+@r,_,_,_=get_type_infodctxidxinr(* The canonical index of an already-defined type. A [Rec] would mean referring
to a group still under construction, which never happens outside the
type-definition builders. *)letdef_id:Types.ref_index->Types.Id.t=function|Defid->id|Rec_->assertfalse(* Resolve a source type reference to its canonical index, for the many contexts
that consult an *already-defined* type (function/cont/struct lookups, casts,
…). *)letresolve_type_indexdctxidx=let+@r=resolve_type_refdctxidxindef_idr(* Record that [feature] is used and, if it is not enabled, report it at
[location]. Validation continues either way (the construct is still typed,
for error recovery). *)letrequire_featured(ctx:type_context)~locationfeature=Wax_utils.Feature.mark_usedctx.featuresfeature;ifnot(Wax_utils.Feature.is_enabledctx.featuresfeature)thenError.feature_disabledd~locationfeatureletheaptypedctx(h:Ast.Text.heaptype):heaptypeoption=matchhwith|Func->SomeFunc|NoFunc->SomeNoFunc|Exn->SomeExn|NoExn->SomeNoExn|Cont->SomeCont|NoCont->SomeNoCont|Extern->SomeExtern|NoExtern->SomeNoExtern|Any->SomeAny|Eq->SomeEq|I31->SomeI31|Struct->SomeStruct|Array->SomeArray|None_->SomeNone_|Typeidx->let+@ty=resolve_type_indexdctxidxinTypety|Exactidx->require_featuredctx~location:idx.infoWax_utils.Feature.Custom_descriptors;let+@ty=resolve_type_indexdctxidxinExacttyletreftypedctx{Ast.Text.nullable;typ}=let+@typ=heaptypedctxtypin{nullable;typ}letvaltypedctx(ty:Ast.Text.valtype)=matchtywith|I32->SomeI32|I64->SomeI64|F32->SomeF32|F64->SomeF64|V128->SomeV128|Refr->let+@ty=reftypedctxrinReftyletarray_map_optfarr=letexceptionShort_circuitintryletresult=Array.init(Array.lengtharr)(funi->matchfarr.(i)withSomev->v|None->raiseShort_circuit)inSomeresultwithShort_circuit->Noneletarray_mapi_optfarr=letexceptionShort_circuitintryletresult=Array.init(Array.lengtharr)(funi->matchfiarr.(i)withSomev->v|None->raiseShort_circuit)inSomeresultwithShort_circuit->Noneletfunctypedctx{Ast.Text.params;results}=let*@params=array_map_opt(funp->valtypedctx(sndp.Ast.desc))paramsinlet+@results=array_map_opt(funty->valtypedctxty)resultsin{params;results}letmuttypefdctx{mut;typ}=let+@typ=fdctxtypin{mut;typ}lettabletypedctx({limits;reftype=typ}:Ast.Text.tabletype)=let+@reftype=reftypedctxtypin{Types.Internal.limits=limits.desc;reftype}letglobaltypedctxty=muttypevaltypedctxty(* Type-definition builders. These produce the *normalized* representation
([Types.Normalized]) that {!Types.add_rectype} takes: a reference to a member
of the group being defined is [Rec pos], anything else is [Def id]. They are
deliberately separate from the [Internal]-producing builders above, which
serve instruction checking where every reference is already defined. *)letn_heaptypedctx(h:Ast.Text.heaptype):Nz.heaptypeoption=matchhwith|Func->SomeNz.Func|NoFunc->SomeNz.NoFunc|Exn->SomeNz.Exn|NoExn->SomeNz.NoExn|Cont->SomeNz.Cont|NoCont->SomeNz.NoCont|Extern->SomeNz.Extern|NoExtern->SomeNz.NoExtern|Any->SomeNz.Any|Eq->SomeNz.Eq|I31->SomeNz.I31|Struct->SomeNz.Struct|Array->SomeNz.Array|None_->SomeNz.None_|Typeidx->let+@r=resolve_type_refdctxidxinNz.Typer|Exactidx->require_featuredctx~location:idx.infoWax_utils.Feature.Custom_descriptors;let+@r=resolve_type_refdctxidxinNz.Exactrletn_reftypedctx{Ast.Text.nullable;typ}:Nz.reftypeoption=let+@typ=n_heaptypedctxtypin{Nz.nullable;typ}letn_valtypedctx(ty:Ast.Text.valtype):Nz.valtypeoption=matchtywith|I32->SomeNz.I32|I64->SomeNz.I64|F32->SomeNz.F32|F64->SomeNz.F64|V128->SomeNz.V128|Refr->let+@ty=n_reftypedctxrinNz.Reftyletn_functypedctx{Ast.Text.params;results}:Nz.functypeoption=let*@params=array_map_opt(funp->n_valtypedctx(sndp.Ast.desc))paramsinlet+@results=array_map_opt(funty->n_valtypedctxty)resultsin{Nz.params;results}letn_storagetypedctx(ty:Ast.Text.storagetype):Nz.storagetypeoption=matchtywith|Valuety->let+@ty=n_valtypedctxtyinNz.Valuety|Packedty->Some(Nz.Packedty)letn_fieldtypedctxty:Nz.fieldtypeoption=muttypen_storagetypedctxtyletcomptypedctx(ty:Ast.Text.comptype):Nz.comptypeoption=matchtywith|Functy->let+@ty=n_functypedctxtyinNz.Functy|Structfields->let+@fields=array_map_opt(fune->n_fieldtypedctx(snde.Ast.desc))fieldsinNz.Structfields|Arrayfield->let+@field=n_fieldtypedctxfieldinNz.Arrayfield|Contidx->let+@r=resolve_type_refdctxidxinNz.Contr(* A reference is to an already-defined type when it is a [Def], or a [Rec]
member strictly before [current] in the group (defined earlier). *)letdefined_beforecurrent(r:Types.ref_index)=matchrwithDef_->true|Recpos->pos<currentletsubtypedctxcurrent({Ast.Text.typ;supertype;final;descriptor;describes}:Ast.Text.subtype):Types.Normalized.subtypeoption=let*@typ=comptypedctxtypinlet*@supertype=matchsupertypewith|None->SomeNone|Someidx->let+@r=resolve_type_refdctxidxin(* A supertype must be an already-defined type: reject a self/forward
reference to a member of this group at position [>= current]. *)(ifnot(defined_beforecurrentr)thenletlst=matchidx.descwith|Num_->[]|Idid->Wax_utils.Spell_check.f(funf->Hashtbl.iter(funid'(r,_,_,_)->ifdefined_beforecurrentrthenfid')ctx.label_mapping)idinError.unbound_indexd~location:idx.info"type"idxlst);Somerinletresolve_opt=function|None->SomeNone|Some(idx:Ast.Text.idx)->require_featuredctx~location:idx.infoWax_utils.Feature.Custom_descriptors;let+@r=resolve_type_refdctxidxinSomerinlet*@descriptor=resolve_optdescriptorinlet+@describes=resolve_optdescribesin{Nz.typ;supertype;final;descriptor;describes}(* Each member's components (its supertype, field and element reference types, a
[cont]'s wrapped type, a descriptor clause) are references made *by that
member*, so they only keep their targets alive if the member itself is: a rec
group nothing else names is dead as a whole, cycle and all. [ctx.last_index] is
still the group's base here — [add_type] advances it once the group resolves. *)letrectypedctxty=letbase=ctx.last_indexinletouter=ctx.origininletr=array_mapi_opt(funie->ifctx.origin<>Ignoredthenctx.origin<-From_type(base+i);subtypedctxi(snde.Ast.desc))tyinctx.origin<-outer;rlettypeusedctx(idx,sign)=match(idx,sign)with|Someidx,_->((* A typeuse always denotes a function type, so reject a reference to a
struct/array type with a clean error rather than letting the later
[typeuse_functype]/[reference_functype] assert. *)let*@ty=resolve_type_indexdctxidxinmatchreference_comptypectxidxwith|Func_->Somety|_->Error.expected_func_typed~location:idx.infoidx;None)|_,Somesign->let+@ty=n_functypedctxsigninTypes.add_rectypectx.types[|{typ=Functy;supertype=None;final=true;descriptor=None;describes=None;};|]|None,None->assertfalse(* Should not happen *)(* Intern the internal representation type of Wax strings — a mutable array of
[i8] — and return its canonical index. [string_type_reference] is the form to
use where a [@string] in the source actually *uses* that type, so that a
like-typed named definition it deduplicates onto is not reported unused; plain
[string_type] is for the up-front registration, which is not a use. *)letstring_typectx=Types.add_rectypectx.types[|{typ=Array{mut=true;typ=PackedI8};supertype=None;final=true;descriptor=None;describes=None;};|]letstring_type_referencectx=letid=string_typectxinifctx.record_references&&ctx.origin<>Ignoredthenctx.canonical_type_references<-(ctx.origin,id)::ctx.canonical_type_references;id(*** The module context ***)(* A reference made from inside a function body, for the reachability analysis
behind [unused-field]: which index space it targets, and which index in it. A
reference made from a module-level context instead — an export, the start
function, a global or table initializer, an element or data segment — is a
*root*, recorded directly in the matching [used_*] table, because it runs (or
becomes callable) whatever the bodies do. *)typereference=|Ref_functionofint|Ref_globalofint|Ref_memoryofint|Ref_tableofint|Ref_tagofint|Ref_dataofint|Ref_elemofinttypemodule_context={diagnostics:Wax_utils.Diagnostic.context;types:type_context;subtyping_info:Types.subtyping_info;(* Each function carries its type's global index, the source type index it was
declared with (when it names one, for a [ref.func]'s rendering), and its
source signature (from its declaration, or its referenced type) — so a call
names argument and result types from the function's own declaration rather
than a shared (deduplicated) type index that another structurally-equal type
may own. *)(* Per function: interned type, source type index (if named), signature, and
whether [ref.func] on it yields an *exact* reference (true for a defined
function or an exact import, false for a plain import). *)functions:(Types.Id.t*Ast.Text.idxoption*Ast.Text.functype*bool)Sequence.t;memories:limitsSequence.t;tables:(Types.Internal.tabletype*source_type)Sequence.t;globals:(globaltype*source_type)Sequence.t;(* Each tag carries its type's global index and its source signature, to name
a thrown payload's types. *)tags:(Types.Id.t*Ast.Text.functype)Sequence.t;data:unitSequence.t;(* Each element segment carries its interned reference type and the source
reference type from its declaration, to name a mismatched element type. *)elem:(reftype*source_type)Sequence.t;exports:(string,Ast.location)Hashtbl.t;refs:(int,unit)Hashtbl.t;(* Indices referenced from a *root* context, per index space: an export, the
start function, a global or table initializer, an element or data segment.
These seed the [unused-field] reachability analysis. An active data/element
segment (and a declarative one) is marked at its own definition: it runs at
instantiation, so it is a root whether or not an instruction names it. *)used_functions:(int,unit)Hashtbl.t;used_globals:(int,unit)Hashtbl.t;used_memories:(int,unit)Hashtbl.t;used_tables:(int,unit)Hashtbl.t;used_tags:(int,unit)Hashtbl.t;used_data:(int,unit)Hashtbl.t;used_elem:(int,unit)Hashtbl.t;(* Every reference made from inside a body, as (enclosing function, target).
[unused_fields] walks these to find what each *live* function reaches; a
reference from a dead function keeps nothing alive. Where the reference is
made from is [types.origin] (shared with the type references, which are
recorded on the type context because every type-resolution point can reach
it). *)mutablebody_references:(int*reference)list;(* Global indices that are the target of a [global.set] anywhere — the marks
the [unnecessary-mut] warning checks against. Deliberately NOT filtered by
reachability: dropping the [mut] from a global that a dead function assigns
would not validate, so a textual assignment, live or not, is enough to keep
the mutability. *)assigned_globals:(int,unit)Hashtbl.t;(* Each module-defined (non-import) field, as (index, source name, report
location): the candidates the [unused-field] warning ranges over. *)mutabledefined_functions:(int*Ast.Text.nameoption*Ast.location)list;mutabledefined_globals:(int*Ast.Text.nameoption*Ast.location)list;mutabledefined_memories:(int*Ast.Text.nameoption*Ast.location)list;mutabledefined_tables:(int*Ast.Text.nameoption*Ast.location)list;mutabledefined_tags:(int*Ast.Text.nameoption*Ast.location)list;mutabledefined_data:(int*Ast.Text.nameoption*Ast.location)list;mutabledefined_elem:(int*Ast.Text.nameoption*Ast.location)list;(* The module-defined globals declared [mut], in the same shape: the
[unnecessary-mut] candidates. *)mutablemutable_globals:(int*Ast.Text.nameoption*Ast.location)list;(* Likewise for imports — the [unused-import] candidates. They share the index
space (and so the [used_*] marks) with the defined ones, but are reported
with a distinct wording. *)mutableimported_functions:(int*Ast.Text.nameoption*Ast.location)list;mutableimported_globals:(int*Ast.Text.nameoption*Ast.location)list;mutableimported_memories:(int*Ast.Text.nameoption*Ast.location)list;mutableimported_tables:(int*Ast.Text.nameoption*Ast.location)list;mutableimported_tags:(int*Ast.Text.nameoption*Ast.location)list;(* Whether the extra "unused" analyses run (tied to [-v]/[check], like
[unused-local]); consulted by lints emitted during stack validation. *)warn_unused:bool;}moduleIntSet=Set.Make(Int)typectx={(* Each local carries the interned type and the source type for error
messages (reconstructed from the interned type if no source is known). *)locals:(valtype*source_type)Sequence.t;(* Each entry is a branch target: its optional label, the interned types a
branch carries to it, their source types for error messages, and a flag set
when a branch resolves to this frame (used to report labels never branched
to). The flag is shared by reference, so a branch deep in a block marks the
frame the enclosing instruction created. *)control_types:(stringoption*valtypearray*source_typearray*boolref)list;return_types:valtypearray;return_source:source_typearray;modul:module_context;mutableinitialized_locals:IntSet.t;(* Indices of locals read by a [local.get]. A local that is never read is
reported as unused once the function body has been validated. A [ref]
(rather than a snapshot field like [initialized_locals]) so a read inside a
block propagates up to the function level. *)used_locals:IntSet.tref;(* Indices of locals whose declared type failed to resolve (an unbound type
index, already reported at the declaration). Such a local is poisoned: a
[local.get] pushes the bottom reference and a [local.set]/[local.tee]
accepts any operand, so a use does not cascade a second, spurious type
mismatch against the recovery dummy type. *)poisoned_locals:IntSet.t;(* Named block labels declared in this function, each with the [bool ref] its
control frame carries. A label whose flag is still unset once the body has
been validated was never branched to and is reported as unused. *)label_decls:(Ast.Text.name*boolref)listref;}letlookup_func_typectxidx=letctx=ctx.modulinlet*@ty=resolve_type_indexctx.diagnosticsctx.typesidxinletdef=Types.get_subtypectx.subtyping_infotyinmatchdef.typwith|Funcf->Some(ty,f)|Struct_|Array_|Cont_->Error.expected_func_typectx.diagnostics~location:idx.infoidx;Noneletlookup_struct_typectxidx=letctx=ctx.modulinlet*@ty,field_map,_,_=get_type_infoctx.diagnosticsctx.typesidxinletty=def_idtyinletdef=Types.get_subtypectx.subtyping_infotyinmatchdef.typwith|Structfields->Some(ty,field_map,fields)|Func_|Array_|Cont_->Error.expected_struct_typectx.diagnostics~location:idx.infoidx;Noneletstruct_field_indexctxidx'field_mapfields=matchidx'.Ast.descwith|Ast.Text.Idid->(matchList.assoc_optidfield_mapwith|Somen->Somen|None->Error.unknown_fieldctx.modul.diagnostics~location:idx'.Ast.info;None)|Ast.Text.Numn->letn=Uint32.to_intninifn<Array.lengthfieldsthenSomenelse(Error.field_index_out_of_boundsctx.modul.diagnostics~location:idx'.Ast.info~index:n~count:(Array.lengthfields);None)letlookup_array_typectxidx=letctx=ctx.modulinlet*@ty=resolve_type_indexctx.diagnosticsctx.typesidxinletdef=Types.get_subtypectx.subtyping_infotyinmatchdef.typwith|Arrayfield->Some(ty,field)|Func_|Struct_|Cont_->Error.expected_array_typectx.diagnostics~location:idx.infoidx;None(* The descriptor type of the type at global index [ty], for the descriptor
instructions ([struct.new_desc], [ref.get_desc], …). Reports an error and
returns [None] when the type carries no [descriptor] clause. *)lettype_descriptorctx~locationty=match(Types.get_subtypectx.modul.subtyping_infoty).descriptorwith|Somedesc->Somedesc|None->Error.type_without_descriptorctx.modul.diagnostics~location;None(* The heap type of the descriptor operand expected by a [_desc_eq] cast/branch
whose target heap type is [target] (either [Exact x] or [Type x]). The
operand's exactness matches the target's ([exact_1] in the spec); [y] is [x]'s
descriptor. *)letdescriptor_operand_typectx~location(target:heaptype)=matchtargetwith|Exactx->let+@d=type_descriptorctx~locationxinExactd|Typex->let+@d=type_descriptorctx~locationxinTyped|_->Error.invalid_cast_typectx.modul.diagnostics~location;None(* Mark the index [idx] resolves to in [seq], whatever the enclosing context. *)letmark_indexusedseqidx=Option.iter(funi->Hashtbl.replaceusedi())(Sequence.get_index_optseqidx)(* Record the reference [idx] makes to the index space [seq], for the
[unused-field] warning. Inside a function body it becomes an edge from that
function; from a module-level context it is a root, marked in [used]. [mctx] is
the module context. *)letmark_referencemctxwrapusedseqidx=(* Only the [unused-field] analysis consumes any of this, and [body_references]
grows with every reference in the module, so record nothing when the lint is
off. *)ifmctx.warn_unusedthenOption.iter(funi->matchmctx.types.originwith|From_functioncaller->mctx.body_references<-(caller,wrapi)::mctx.body_references|Ignored->()(* Only types reference types, so [From_type] never reaches an index
space; treat it as a root rather than losing the reference. *)|Root|From_type_->Hashtbl.replaceusedi())(Sequence.get_index_optseqidx)(* The parameter types of an exception [tag] and, when known, their source
types for naming a thrown payload. [lookup_tag_type]/[lookup_tag_signature]
are the two resolution points for every tag reference (a [throw], a [catch]
clause, a [suspend]/[resume] handler), so they record the tag as used. *)letlookup_tag_typectxtag=letctx=ctx.modulinmark_referencectx(funi->Ref_tagi)ctx.used_tagsctx.tagstag;let*@ty,sign=Sequence.getctx.diagnosticsctx.tagstaginmatch(Types.get_subtypectx.subtyping_infoty).typwith|Struct_|Array_|Cont_->Error.not_function_typectx.diagnostics~location:tag.info;None|Func{params;results}->ifresults<>[||]thenError.exception_tag_with_resultsctx.diagnostics~location:tag.info;Some(params,Array.map(funp->Plain(sndp.Ast.desc))sign.params)(* Full function type of a tag, used for stack-switching suspension tags whose
results may be non-empty (unlike exception tags). *)letlookup_tag_signaturectxtag=letctx=ctx.modulinmark_referencectx(funi->Ref_tagi)ctx.used_tagsctx.tagstag;let*@ty,sign=Sequence.getctx.diagnosticsctx.tagstaginmatch(Types.get_subtypectx.subtyping_infoty).typwith|Funcft->Some(ft,sign)|Struct_|Array_|Cont_->Error.not_function_typectx.diagnostics~location:tag.info;None(* Resolve a continuation type index to its own index and the function type it
wraps. Emits an error if the type is not a continuation type. *)letlookup_cont_typectxidx=letmctx=ctx.modulinlet*@ty=resolve_type_indexmctx.diagnosticsmctx.typesidxinmatch(Types.get_subtypemctx.subtyping_infoty).typwith|Contft->(match(Types.get_subtypemctx.subtyping_infoft).typwith|Funcf->Some(ty,ft,f)|Struct_|Array_|Cont_->Error.expected_cont_typemctx.diagnostics~location:idx.infoidx;None)|Struct_|Array_|Func_->Error.expected_cont_typemctx.diagnostics~location:idx.infoidx;None(* The continuation type referenced by a heap type, if any. *)letcont_functype_of_heaptypectx(h:heaptype)=matchhwith|Typety|Exactty->(match(Types.get_subtypectx.modul.subtyping_infoty).typwith|Contft->(match(Types.get_subtypectx.modul.subtyping_infoft).typwith|Funcf->Somef|Struct_|Array_|Cont_->None)|Func_|Struct_|Array_->None)|_->None(* [functype_matches info ft ft'] holds when [ft] is a subtype of [ft']:
parameters are contravariant and results covariant. *)letfunctype_matchesinfo(ft:functype)(ft':functype)=Array.lengthft.params=Array.lengthft'.params&&Array.lengthft.results=Array.lengthft'.results&&Array.for_allFun.id(Array.mapi(funip->Types.val_subtypeinfoft'.params.(i)p)ft.params)&&Array.for_allFun.id(Array.mapi(funir->Types.val_subtypeinforft'.results.(i))ft.results)(* [result_subtype info ts ts'] holds when result type [ts] matches [ts']
(same length, covariant element by element). *)letresult_subtypeinfo(ts:valtypearray)(ts':valtypearray)=Array.lengthts=Array.lengthts'&&Array.for_allFun.id(Array.mapi(funit->Types.val_subtypeinfotts'.(i))ts)(* [result_equivalent info ts ts'] holds when the two result types are
equivalent, i.e. mutual subtypes (which coincides with equality in the
single-inheritance reference-type lattice). *)letresult_equivalentinfotsts'=result_subtypeinfotsts'&&result_subtypeinfots'ts(* Source type of struct field [n] of the type that reference [idx] names, when
the field has a (non-packed) value type. Packed fields surface as i32, so
they get no name. Resolving through the reference (not the deduplicated
global index) names the field as written at this very type. *)letsource_field_valtypectxidxn:source_type=matchreference_comptypectx.modul.typesidxwith|Structfields->(let_,(ft:Ast.Text.fieldtype)=fields.(n).Ast.descinmatchft.typwithValuev->Plainv|Packed_->PlainI32)|_->assertfalse(* Source type of the element of the array type that reference [idx] names. *)letsource_element_valtypectxidx:source_type=matchreference_comptypectx.modul.typesidxwith|Array(ft:Ast.Text.fieldtype)->(matchft.typwithValuev->Plainv|Packed_->PlainI32)|_->assertfalse(*** The validation stack ***)(* A stack entry is one of the two bottoms of the validation type lattice or a
concrete value. [Bot] is the unknown value of a polymorphic (unreachable)
stack: a subtype of every type. [Bot_ref] is the bottom reference type
[(ref bot)], produced when a reference-eliminating instruction consumes a
[Bot]: it is a subtype of every reference type but of no numeric or vector
type, which is what lets [ref.as_non_null] / [br_on_null] reject a numeric
use of their result on an otherwise polymorphic stack. [Val] pairs the
interned type used for subtype checking with the source type the value was
written as, mirroring the Wax side's [inferred_valtype]; the source type is
always present, a push that does not supply one reconstructing it from the
interned type (naming an indexed type by its canonical index). *)typestack_entry=Bot|Bot_ref|Valofvaltype*source_typetypestack=|Unreachable|Empty|ConsofAst.locationoption*stack_entry*stack(* Returns the popped entry, along with its push location. A pop from an
unreachable or empty stack yields the unknown value [Bot]; an underflow
turns the stack unreachable (as in [pop]), so one missing value is reported
once rather than once per subsequent pop. *)letpop_anyctxlocst=matchstwith|Unreachable->(Unreachable,(Bot,None))|Cons(loc,ty,r)->(r,(ty,loc))|Empty->Error.empty_stackctx.modul.diagnostics~location:loc;(Unreachable,(Bot,None))(* The non-null version of a popped reference's source type, for an instruction
that re-pushes the value with the null case removed. *)letnon_null_source(source:source_type):source_type=matchsourcewith|Plain(Refr)->Plain(Ref{rwithnullable=false})|Inline_ref_assource->source|_->assertfalseletpopctxloc?arity~expected_sourcetyst=letmismatchlocationsource=matchlocationwith|Somelocation->Error.instruction_type_mismatchctx.modul.diagnostics~location~consumer:(Someloc)~provided_source:source~expected_source|None->Error.type_mismatchctx.modul.diagnostics~location:loc~provided_source:source~expected_sourceinmatchstwith|Unreachable->(Unreachable,())|Cons(_,Bot,r)->(r,())|Cons(location,Bot_ref,r)->(* [(ref bot)] is a subtype of every reference type but of no numeric or
vector type. *)(matchtywithRef_->()|_->mismatchlocationBottom_ref);(r,())|Cons(location,Val(ty',source),r)->letok=Types.val_subtypectx.modul.subtyping_infoty'tyinifnotokthenmismatchlocationsource;(r,())|Empty->(* With an arity context ([pop_args]) the counts are known: report how
many values the construct takes and how many were there — the results
of a block or function are anchored at its closing token. A lone pop
keeps the plain report. *)(matcharitywith|Some(kind,current,expected)->Error.short_stackctx.modul.diagnosticskind~location:(matchkindwith`Input->loc|`Output->loc_last_charloc)~actual:(expected-current-1)~expected|None->Error.empty_stackctx.modul.diagnostics~location:loc);(Unreachable,())(* Pop a value whose expected type has no user-written source form — a builtin,
address, or abstract reference type — so its rendering is reconstructed. *)letpop_knownctxlocty=popctxloc~expected_source:(source_of_valtypety)tyletpush_polylocst=record(Someloc)Polymorphic;(Cons(Someloc,Bot,st),())letpush_bot_reflocst=recordloc(PushedBottom_ref);(Cons(loc,Bot_ref,st),())letpush~sourceloctyst=recordloc(Pushedsource);record_value_type_deflocsource;(Cons(loc,Val(ty,source),st),())(* Push a value whose type has no user-written source form, reconstructing its
rendering from the type. *)letpush_knownlocty=push~source:(source_of_valtypety)locty(* The source rendering of a reference to the named type [idx], used as the
[source] form of a value an instruction pushes ([named_ref_source], non-null) or
expects ([named_ref_null_source], the nullable form pop accepts). *)letnamed_ref_sourceidx:source_type=Plain(Ref{nullable=false;typ=Typeidx})letnamed_ref_null_sourceidx:source_type=Plain(Ref{nullable=true;typ=Typeidx})(* The push-source of a value a concrete allocator produces at exactly type [idx]
([struct.new], [array.new*], [cont.new], [cont.bind]): these push an *exact*
internal reference, so under custom-descriptors the source is rendered exact
to match. Without the proposal exact reference types are not expressible, so it
falls back to the plain named source (the internal type stays exact, but that
extra precision is unobservable there). *)(* Whether a value an ALLOCATION (or a [ref.func]) yields carries the EXACT type
allocated. It does — but exact reference types are part of custom-descriptors,
so without the feature it is the plain inexact reference, as before the
proposal. The Wax typer gates its own [construction_result]/[register_function]
the same way and the two must agree: ungated, the exact type made a [ref.test]
against any other type look impossible, so the validator reported an
always-false test on a downcast that can in fact succeed (two structurally
identical rec groups are the same canonical type) while the typer stayed
correctly silent — a lint-parity finding from the wax mutation fuzzer, first
seen on [ref.func] and true of every allocation. The [*_desc] forms below need
no gate: those instructions exist only under the feature. *)letallocation_is_exactctx=Wax_utils.Feature.is_enabledctx.modul.types.featuresWax_utils.Feature.Custom_descriptorsletexact_ref_sourcectxidx:source_type=ifWax_utils.Feature.is_enabledctx.modul.types.featuresWax_utils.Feature.Custom_descriptorsthenPlain(Ref{nullable=false;typ=Exactidx})elsenamed_ref_sourceidx(* The source rendering of the descriptor of the type at global index
[described] — the [_desc_eq] cast/branch operand (nullable), or the
[ref.get_desc] result (non-null, via [~nullable:false]). The descriptor type
has no immediate to name it by, so its source name is recovered from
[descriptor_source] (recorded at its definition); [exact] matches [described]'s
own exactness. *)letdescriptor_operand_source?(nullable=true)tc(described:heaptype):source_type=letbuildexactx=matchHashtbl.find_opttc.descriptor_sourcexwith|Somenode->Plain(Ref{nullable;typ=(ifexactthenExactnodeelseTypenode)})|None->(* No recorded source (a well-formed descriptor type always has one);
fall back to the abstract struct supertype rather than a misleading
index. *)Plain(Ref{nullable;typ=source_of_heaptypeStruct})inmatchdescribedwith|Exactx->buildtruex|Typex->buildfalsex|_->Plain(Ref{nullable;typ=source_of_heaptypeStruct})(* Source-type array for popping the prefix arguments [param_source] followed by a
continuation operand of the type named by [x]. *)letcont_operand_sourceparam_sourcex=Array.appendparam_source[|named_ref_null_sourcex|](* Source params of the function type the continuation type [x] wraps; they are
exactly that function type's parameters. *)letcont_param_sourcectxx=Array.map(funp->Plain(sndp.Ast.desc))(cont_source_functypectx.modul.typesx).paramsletunreachable_=(Unreachable,())letreturnvst=(st,v)(* These operators thread the value stack through validation. [let*] sequences
two stack transformers, passing the stack from one to the next. [let*!] and
[let*?] guard a transformer on an [option] (typically a failed lookup that
has already reported an error): on [None] they abandon this instruction's
effect, [let*!] yielding the [unreachable] transformer and [let*?] yielding
no stack effect at all (for checks run outside a transformer). *)let(let*)efst=letst,v=estinfvstlet(let*!)ef=matchewithSomev->fv|None->unreachablelet(let*?)ef=matchewithSomev->fv|None->()letget_localctx?(initialize=false)i=let+@l=Sequence.getctx.modul.diagnosticsctx.localsiinletidx=Sequence.get_indexctx.localsiinifinitializethenctx.initialized_locals<-IntSet.addidxctx.initialized_localselsebeginctx.used_locals:=IntSet.addidx!(ctx.used_locals);ifnot(IntSet.memidxctx.initialized_locals)thenError.uninitialized_localctx.modul.diagnostics~location:i.infoiend;l(* Whether local [i] resolves to a poisoned local (declared with a type that did
not resolve). Uses the same resolved index [get_local] tracks; a use of such
a local is treated as the bottom reference to avoid cascading a second error. *)letlocal_is_poisonedctxi=matchSequence.get_index_optctx.localsiwith|Someidx->IntSet.memidxctx.poisoned_locals|None->false(* The result nullability of [extern.convert_any] / [any.convert_extern], which
propagate the operand's nullability. The operand must be a reference in the
[typ] hierarchy: a bottom reference satisfies it as known non-null, a
fully-unknown [Bot] (a polymorphic, unreachable stack) is treated as non-null
— the most precise choice, like [Bot_ref] — and a non-reference operand (a
reported error) is treated as nullable rather than crashing. *)letconvert_operand_nullablectxloc(entry,entry_loc)~typ=matchentrywith|Bot->false|Bot_ref->false|Val(ty,source)->(letexpected=Ref{nullable=true;typ}in(ifnot(Types.val_subtypectx.modul.subtyping_infotyexpected)then(* As in [pop]: blame the value where it was pushed when that is
known, with the conversion as the consumer. *)matchentry_locwith|Somelocation->Error.instruction_type_mismatchctx.modul.diagnostics~location~consumer:(Someloc)~provided_source:source~expected_source:(source_of_valtypeexpected)|None->Error.type_mismatchctx.modul.diagnostics~location:loc~provided_source:source~expected_source:(source_of_valtypeexpected));matchtywithRef{nullable;_}->nullable|_->true)letis_defaultablety=matchtywith|I32|I64|F32|F64|V128->true|Ref{nullable;_}->nullableletnumber_or_vecty=matchtywithI32|I64|F32|F64|V128->true|Ref_->falseletint_un_op_typety(op:Ast.Text.int_un_op)=matchopwith|Clz|Ctz|Popcnt|ExtendS_->(ty,ty)|Trunc(sz,_)|TruncSat(sz,_)->((matchszwith`F32->F32|`F64->F64),ty)|Reinterpret->((matchtywith|I32->F32|I64->F64|_->assertfalse(* Should not happen *)),ty)|Eqz->(ty,I32)letint_bin_op_typety(op:Ast.Text.int_bin_op)=matchopwith|Add|Sub|Mul|Div_|Rem_|And|Or|Xor|Shl|Shr_|Rotl|Rotr->ty|Eq|Ne|Lt_|Gt_|Le_|Ge_->I32letfloat_un_op_typety(op:Ast.Text.float_un_op)=matchopwith|Neg|Abs|Ceil|Floor|Trunc|Nearest|Sqrt->ty|Convert(sz,_)->(matchszwith`I32->I32|`I64->I64)|Reinterpret->(matchtywith|F32->I32|F64->I64|_->assertfalse(* Should not happen *))letfloat_bin_op_typety(op:Ast.Text.float_bin_op)=matchopwith|Add|Sub|Mul|Div|Min|Max|CopySign->ty|Eq|Ne|Lt|Gt|Le|Ge->I32(* Returns the interned block parameter and result types, plus their per-element
source types (for [pop_args]/[push_results]'s [~source]). Like [typeuse], a
type reference is resolved in preference to an inline signature; for valid
input the two agree ([check_syntax]'s inline check), and preferring the
reference makes it the one place a block's typeuse index is resolved (and an
unbound one reported). *)letblocktypectx(ty:Ast.Text.blocktypeoption)=matchtywith|None->Some([||],[||],[||],[||])|Some(Typeuse(Someidx,_))->let+@_,{params;results}=lookup_func_typectxidxinletparam_source,result_source=functype_sources(reference_functypectx.modul.typesidx)in(params,results,param_source,result_source)|Some(Typeuse(None,Some({params;results}asft)))->let*@iparams=array_map_opt(funp->valtypectx.modul.diagnosticsctx.modul.types(sndp.Ast.desc))paramsinlet+@iresults=array_map_opt(valtypectx.modul.diagnosticsctx.modul.types)resultsinletparam_source,result_source=functype_sourcesftin(iparams,iresults,param_source,result_source)|Some(Typeuse(None,None))->assertfalse(* Should not happen *)|Some(Valtypety)->let+@t=valtypectx.modul.diagnosticsctx.modul.typestyin([||],[|t|],[||],[|Plainty|])letpop_argsctx?(kind=`Input)loc~sourceargs=letlen=Array.lengthargsinletrecloopi=ifi<0thenreturn()elselet*()=popctxloc~arity:(kind,i,len)~expected_source:source.(i)args.(i)inloop(i-1)inloop(len-1)(* [sink] (default [true]) records each pushed result at the instruction span
[loc] for the editor type sink. A {e single} result cell also carries [loc] as
its provenance — that value was unambiguously pushed by this instruction, so a
"value pushed here" diagnostic (and hover) points at it; [push] does the sink
recording in that case. Several results cannot each be that one span, so their
cells push location [None] and the span is recorded here instead. Set
[~sink:false] where [push_results] simulates a branch target or a block's
entry parameters rather than pushing the instruction's own results: nothing is
attributed to the instruction's span, cells included. *)letpush_results?(sink=true)~loc~sourceresults=letsingle=Array.lengthresults=1inletcell_loc=ifsink&&singlethenSomelocelseNoneinletrecloopi=ifi>=Array.lengthresultsthenreturn()elsebeginifsink&¬singlethenbeginrecord(Someloc)(Pushedsource.(i));record_value_type_def(Someloc)source.(i)end;let*()=push~source:source.(i)cell_locresults.(i)inloop(i+1)endinloop0letrecoutput_stack~fullppst=matchstwith|Empty->()|Unreachable->iffullthen(sp_spacepp;sp_kwpp"unreachable")|Cons(_,ty,st)->sp_spacepp;(matchtywith|Val(_,source)->print_source_typeppsource|Bot->sp_typepp"bot"|Bot_ref->sp_boxpp(fun()->sp_punctpp"(";sp_kwpp"ref";sp_spacepp;sp_typepp"bot";sp_punctpp")"));output_stack~fullppstletprint_stackst=Wax_utils.Printer.run_err(funp->letpp=Wax_utils.Styled_printer.create~printer:p~theme:Wax_utils.Colors.no_color~trivia:(Wax_utils.Trivia.empty())()inWax_utils.Printer.stringp"Stack:";output_stack~full:trueppst);(st,())let_=print_stackletwith_empty_stackctxlocationf=letst,()=fEmptyin(* The source locations of the values still on the stack, topmost first.
Values without a usable location (a block parameter/result, or an
error-recovery placeholder) are dropped. *)letreclocations=function|Cons(Someloc,_,st)whenloc.Ast.loc_start.Lexing.pos_cnum>=0->loc::locationsst|Cons(_,_,st)->locationsst|Empty|Unreachable->[]inmatchstwith|Empty|Unreachable->()|Cons_->(matchlocationsstwith|location::rest->(* Point a caret right at each leftover value rather than at the
(potentially large) enclosing construct. *)letrelated=List.map(funlocation->{Wax_utils.Diagnostic.location;message=Wax_utils.Message.empty;})restinError.leftover_valuesctx.diagnostics~location~related|[]->(* No value carries a usable location: point at the construct and
list the values that remain, since the location alone does not
show them. *)Error.non_empty_stackctx.diagnostics~location(funpp->output_stack~full:falseppst))(*** Instruction-checking helpers ***)(* Check that a list of [provided] argument types matches a list of [expected]
parameter types: same length, and each argument a subtype of the
corresponding parameter. [descr] names the construct supplying the
arguments. Reporting the two lists directly gives a far clearer message than
simulating the comparison on the value stack. *)letcompare_typesctx~location~descr~provided_source~expected_source~provided~expected()=ifArray.lengthprovided<>Array.lengthexpectedthenError.argument_count_mismatchctx.diagnostics~location~descr~provided_source~expected_sourceelseArray.iteri(funip->lete=expected.(i)inifnot(Types.val_subtypectx.subtyping_infope)thenError.argument_type_mismatchctx.diagnostics~location~descr~nth:(ifArray.lengthprovided>1thenSome(i+1)elseNone)~provided_source:provided_source.(i)~expected_source:expected_source.(i))providedletbranch_targetctx(idx:Ast.Text.idx)=matchidx.descwith|Numi->(trylet_,params,source,used=List.nthctx.control_types(Uint32.to_inti)inused:=true;Some(params,source)withFailure_->Error.unbound_labelctx.modul.diagnostics~location:idx.Ast.infoidx[];None)|Idid->letrecfindlid=matchlwith|[]->letlst=Wax_utils.Spell_check.f(funf->List.iter(fun(id_opt,_,_,_)->matchid_optwithSomeid->fid|None->())ctx.control_types)idinError.unbound_labelctx.modul.diagnostics~location:idx.Ast.infoidxlst;None|(Someid',params,source,used)::_whenid=id'->used:=true;Some(params,source)|_::rem->findremidinfindctx.control_typesid(* The top of the heap-type hierarchy that [t] belongs to (one of [any], [func],
[exn], [cont], [extern]). A cast or test pops a reference to this top type —
the most general operand the instruction accepts — before checking against
the precise target. *)lettop_heap_typectx(t:heaptype):heaptype=matchtwith|Any|Eq|I31|Struct|Array|None_->Any|Func|NoFunc->Func|Exn|NoExn->Exn|Cont|NoCont->Cont|Extern|NoExtern->Extern|Typety|Exactty->(match(Types.get_subtypectx.modul.subtyping_infoty).typwith|Struct_|Array_->Any|Func_->Func|Cont_->Cont)letstorage_subtypeinfotyty'=match(ty,ty')with|PackedI8,PackedI8|PackedI16,PackedI16->true|Valuety,Valuety'->Types.val_subtypeinfotyty'|PackedI8,PackedI16|PackedI16,PackedI8|Packed_,Value_|Value_,Packed_->falseletfield_subtypeinfo(ty:fieldtype)(ty':fieldtype)=ty.mut=ty'.mut&&storage_subtypeinfoty.typty'.typ&&((notty.mut)||storage_subtypeinfoty'.typty.typ)(* The reference type difference [t1 \ t2] from the spec: [t1]'s heap type, made
non-nullable once a nullable [t2] has consumed the null case. This is the type
that falls through a [br_on_cast] (or is sent on by [br_on_cast_fail]); the
inline [src_diff] in those arms is the same operation on source types. *)letdiff_ref_typet1t2={nullable=t1.nullable&¬t2.nullable;typ=t1.typ}(* Whether a branching cast from [ty1] to [ty2] is well-typed. The
custom-descriptors proposal relaxes the pre-existing [rt2 <: rt1] to only
requiring that [rt1] and [rt2] share a supertype — i.e. lie in the same heap
type hierarchy — so under that feature we compare the hierarchy tops. *)letbr_cast_compatiblectx(ty1:reftype)(ty2:reftype)=ifWax_utils.Feature.is_enabledctx.modul.types.featuresWax_utils.Feature.Custom_descriptorsthentop_heap_typectxty1.typ=top_heap_typectxty2.typelseTypes.val_subtypectx.modul.subtyping_info(Refty2)(Refty1)(* The target of a branching cast ([br_on_cast] and its variants) always carries
at least the matched reference to the label, so a zero-result label cannot
receive it. [branch_target] alone accepts it — with a polymorphic operand the
per-value [pop_args] check below is vacuous — so reject an empty label here. *)letbranch_cast_targetctx(idx:Ast.Text.idx)~location=matchbranch_targetctxidxwith|None->None|Some(params,source)->ifArray.lengthparams=0then(Error.br_cast_type_mismatchctx.modul.diagnostics~location;None)elseSome(params,source)(* Run [f] on the current stack and return its result as the monad value while
leaving the stack untouched — a peek. [Br_table] uses it to validate every
branch target against the same incoming stack. *)letwith_current_stackfst=(st,fst)(* Lint a [ref.cast]/[ref.test] against its operand (the top of the current
stack). Under single-inheritance subtyping two heap types share a value only
when one is a subtype of the other, so unrelated types make the cast always
trap (the test always false) — unless a shared [null] slips through. When the
operand already has the target type the cast/test is redundant. Only fires
when unused reporting is on. *)letlint_castctx~location~is_test(target:reftype)=ifnotctx.modul.warn_unusedthenreturn()elsewith_current_stack(funst->matchstwith|Cons(_,Val(Refop,_),_)->letinfo=ctx.modul.subtyping_infoinletrelated=Types.heap_subtypeinfoop.typtarget.typ||Types.heap_subtypeinfotarget.typop.typinif(notrelated)&¬(op.nullable&&target.nullable)thenError.cast_always_failsctx.modul.diagnostics~location~is_testelseifTypes.ref_subtypeinfooptargetthenError.redundant_castctx.modul.diagnostics~location~is_test|_->())letunpack_type(f:fieldtype)=matchf.typwithValuev->v|Packed_->I32(* Pop [n] values of type [ty] (as [array.new_fixed] does), in time proportional
to the operands actually present, not to [n]. Once the stack is [Unreachable]
-- the polymorphic base, or a reachable underflow after the first empty pop
turns it into one -- every remaining pop trivially succeeds, so stop. This
keeps a huge immediate count (e.g. [array.new_fixed 2^31]) from making
validation O(n). *)letrecpop_repeatctxloc~expected_sourcetynst=ifn<=0then(st,())elsematchstwith|Unreachable->(Unreachable,())|_->letst,()=popctxloc~expected_sourcetystinpop_repeatctxloc~expected_sourcety(n-1)stletaddress_type_to_valtype=function`I32->I32|`I64->I64(* Constants for max offsets *)letmax_offset_i32_exclusive=Uint64.of_string"0x1_0000_0000"(* 2^32 *)letmax_align=Uint64.of_int16letcheck_memargctxlocationlimitssz{Ast.Text.offset;align}=iflimits.address_type=`I32thenifUint64.compareoffsetmax_offset_i32_exclusive>=0thenError.memory_offset_too_largectx.modul.diagnostics~locationmax_offset_i32_exclusive;letnatural_alignment=matchszwith|`I8->1|`I16->2|`I32|`F32->4|`I64|`F64->8|`V128->16inifUint64.comparealignmax_align>0||Uint64.to_intalign>natural_alignmentthenError.memory_align_too_largectx.modul.diagnostics~locationnatural_alignmentelsematchUint64.to_intalignwith|1|2|4|8|16->()|_->Error.bad_memory_alignctx.modul.diagnostics~location(* An atomic access requires exactly its natural alignment, not merely at most. *)letcheck_atomic_memargctxlocationlimitsop{Ast.Text.offset;align}=iflimits.address_type=`I32&&Uint64.compareoffsetmax_offset_i32_exclusive>=0thenError.memory_offset_too_largectx.modul.diagnostics~locationmax_offset_i32_exclusive;letnatural=1lslAtomics.natural_align_log2opinifUint64.comparealign(Uint64.of_intnatural)<>0thenError.atomic_alignmentctx.modul.diagnostics~locationnaturalletmemory_instruction_type_and_sizety=match(ty:Ast.Text.num_type)with|NumI32->(I32,`I32)|NumF32->(F32,`I32)|NumI64->(I64,`I64)|NumF64->(F64,`I64)letfield_has_default(ty:fieldtype)=matchty.typwith|Packed_->true|Valuety->(matchtywith|I32|I64|F32|F64|V128->true|Ref{nullable;_}->nullable)letshape_type(shape:Ast.vec_shape)=matchshapewith|I8x16|I16x8|I32x4->I32|I64x2->I64|F32x4->F32|F64x2->F64letcheck_shape_lanesctxlocation(shape:Ast.vec_shape)lane=letmax_lane=matchshapewith|I8x16->16|I16x8->8|I32x4|F32x4->4|I64x2|F64x2->2iniflane>=max_lanethenError.invalid_lane_indexctx.modul.diagnostics~locationmax_lane(* Validate the handler clauses of a [resume]/[resume_throw] instruction. [ts2]
is the result type of the resumed continuation. *)letcheck_resume_tablectxlocts2clauses=letinfo=ctx.modul.subtyping_infoinList.iter(fun(clause:Ast.Text.on_clause)->matchclausewith|OnLabel(tag,label)->(matchlookup_tag_signaturectxtagwith|None->()|Some({params=ts3;results=ts4},_)->(matchbranch_targetctxlabelwith|None->()|Some(ts',_)->letn=Array.lengthts'inletmismatch()=Error.stack_switching_type_mismatchctx.modul.diagnostics~location:label.info~descr:"this handler must take the tag's parameters followed \
by a continuation of the remaining result type"in(* The handler label receives the tag's parameters followed by
a continuation of type [cont (ts4 -> ts2)]. *)ifn<>Array.lengthts3+1thenmismatch()elsebeginArray.iteri(funit->ifnot(Types.val_subtypeinfotts'.(i))thenmismatch())ts3;matchts'.(n-1)with|Ref{typ=ht;_}->(matchcont_functype_of_heaptypectxhtwith|Someft'->ifnot(functype_matchesinfo{params=ts4;results=ts2}ft')thenmismatch()|None->mismatch())|_->mismatch()end))|OnSwitchtag->(matchlookup_tag_signaturectxtagwith|None->()|Some({params=ts3;results=ts4},_)->(* A switch handler tag has type [] -> [t*] (no parameters). The
canonical stack-switching rule reifies the current continuation
as [cont [t2*] -> [t*]] and runs it to this [resume] boundary,
whose continuation results are [ts2]; for that to be consistent
[t*] must *equal* [ts2] (equivalence, not merely subtyping). A
subtype would let a continuation whose completion actually
produces [ts2] be observed by a peer at the narrower tag type
[t*] — an unchecked narrowing. This matches V8
(IsEquivalentTypeVec) and the spec author's fix; the older
written subtyping rule is unsound. *)ifArray.lengthts3<>0thenError.stack_switching_type_mismatchctx.modul.diagnostics~location:loc~descr:"the tag of a 'switch' handler must take no parameters"elseifnot(result_equivalentinfots4ts2)thenError.stack_switching_type_mismatchctx.modul.diagnostics~location:loc~descr:"the results of a 'switch' handler's tag must match the \
resumed continuation's results"))clauses(* Look up an entry in a module-level index space, reporting an unbound-index
error (via {!Sequence.get}) when the reference does not resolve. Each [get_*]
is the single resolution point for every reference to that space — a
[global.get]/[global.set], a [call]/[return_call]/[ref.func], a memory/table
access, a segment operand — whether in a body or a constant expression, so
noting the resolved index here records the field as used. *)letget_memoryctxidx=mark_referencectx.modul(funi->Ref_memoryi)ctx.modul.used_memoriesctx.modul.memoriesidx;Sequence.getctx.modul.diagnosticsctx.modul.memoriesidxletget_tablectxidx=mark_referencectx.modul(funi->Ref_tablei)ctx.modul.used_tablesctx.modul.tablesidx;Sequence.getctx.modul.diagnosticsctx.modul.tablesidxletget_globalctxidx=mark_referencectx.modul(funi->Ref_globali)ctx.modul.used_globalsctx.modul.globalsidx;Sequence.getctx.modul.diagnosticsctx.modul.globalsidxletget_functionctxidx=mark_referencectx.modul(funi->Ref_functioni)ctx.modul.used_functionsctx.modul.functionsidx;Sequence.getctx.modul.diagnosticsctx.modul.functionsidxletget_datactxidx=mark_referencectx.modul(funi->Ref_datai)ctx.modul.used_datactx.modul.dataidx;Sequence.getctx.modul.diagnosticsctx.modul.dataidxletget_elemctxidx=mark_referencectx.modul(funi->Ref_elemi)ctx.modul.used_elemctx.modul.elemidx;Sequence.getctx.modul.diagnosticsctx.modul.elemidx(* Pop a memory/table address operand, whose width follows the address type. *)letpop_addressctxloclimits=pop_knownctxloc(address_type_to_valtypelimits.address_type)(*** The instruction validator ***)(* The usage flag to give a block form's control frame(s). A named label is also
recorded in [ctx.label_decls] so an un-branched-to one can be reported once
the body is validated; the same flag is shared across an [if]'s two arms and a
[try]'s several bodies, which reuse one source label. *)lettrack_labelctxlabel=letused=reffalseinOption.iter(funl->ctx.label_decls:=(l,used)::!(ctx.label_decls))label;used(* Branch-hinting proposal: a hint is advisory and has no stack effect, but it is
only meaningful on a conditional branch — [if], [br_if] or a [br_on_*], reached
through a folded head. Reject it anywhere else, matching the Wax typer; the
text and binary front ends both attach a hint wherever it was written and leave
the placement to be diagnosed here. *)letrecis_branch_hint_target(d:_Ast.Text.instr_desc)=matchdwith|If_|Br_if_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_->true|Folded(b,_)->is_branch_hint_targetb.desc|_->false(* Compilation-hints proposal: an instruction frequency guides inlining, loop
unrolling and block deferral, so the proposal expects it on a call or a control
instruction. Anything else it defines as ignored; reject it instead, so a
toolchain emitting a useless hint hears about it. *)letrecis_instr_freq_target(d:_Ast.Text.instr_desc)=matchdwith|Call_|CallRef_|CallIndirect_|ReturnCall_|ReturnCallRef_|ReturnCallIndirect_|Block_|Loop_|If_|TryTable_|Try_->true|_->is_branch_hint_targetd(* A call target only means anything where the callee is not already known. *)andis_call_targets_target(d:_Ast.Text.instr_desc)=matchdwith|CallRef_|CallIndirect_|ReturnCallRef_|ReturnCallIndirect_->true|Folded(b,_)->is_call_targets_targetb.desc|_->falseletcheck_hintsctx(i:_Ast.Text.instr)=lethints=i.hintsin(matchhints.Hints.branchwith|Somehwhennot(is_branch_hint_targeti.desc)->Error.branch_hint_invalid_targetctx.modul.diagnostics~location:h.Hints.loc|_->());(matchhints.Hints.freqwith|Somehwhennot(is_instr_freq_targeti.desc)->Error.instr_freq_invalid_targetctx.modul.diagnostics~location:h.Hints.loc|_->());matchhints.Hints.targetswith|None->()|Someh->ifnot(is_call_targets_targeti.desc)thenError.call_targets_invalid_targetctx.modul.diagnostics~location:h.Hints.loc;(* Resolve each target so an unbound name is reported, but *without* marking
the function used: naming one in advisory metadata is not a use, so it
must not keep an otherwise-dead function out of the unused-field lint. *)List.iter(fun(idx,_)->ignore(Sequence.getctx.modul.diagnosticsctx.modul.functionsidx))h.Hints.value;(* The proposal requires the listed frequencies to total at most 100%. *)lettotal=List.fold_left(funacc(_,pct)->acc+pct)0h.Hints.valueiniftotal>100thenError.call_targets_over_100ctx.modul.diagnostics~location:h.Hints.loc~totalletrecinstruction_corectx(i:_Ast.Text.instr)=iffalsethenprint_instri;letloc=i.infoincheck_hintsctxi;matchi.descwith|Block{label;typ;block=b}->let*!params,results,param_source,result_source=blocktypectxtypinlet*()=pop_argsctx(loc_first_charloc)~source:param_sourceparamsinletused=track_labelctxlabelinblockctxloclabel~used~param_source~result_source~br_source:result_source~params~results~br_params:resultsb.desc;push_results~loc~source:result_sourceresults|Loop{label;typ;block=b}->let*!params,results,param_source,result_source=blocktypectxtypinlet*()=pop_argsctx(loc_first_charloc)~source:param_sourceparamsinletused=track_labelctxlabelinblockctxloclabel~used~param_source~result_source~br_source:param_source~params~results~br_params:paramsb.desc;push_results~loc~source:result_sourceresults|If{label;typ;if_block;else_block}->let*!params,results,param_source,result_source=blocktypectxtypinletloc_start=loc_first_charlocinlet*()=pop_knownctxloc_startI32inlet*()=pop_argsctxloc_start~source:param_sourceparamsinletused=track_labelctxlabelin(* Anchor each arm's stack-shape reports (a missing result, leftover
values) at that arm, not at the whole [if] — otherwise the two arms'
reports are indistinguishable. A synthesized arm (an omitted [else])
has no span of its own; fall back to the instruction's. *)letarm_loc(b:(_,Ast.location)Ast.annotated)=ifb.Ast.info.loc_start.Lexing.pos_cnum>=0thenb.Ast.infoelselocinblockctx(arm_locif_block)label~used~param_source~result_source~br_source:result_source~params~results~br_params:resultsif_block.desc;blockctx(arm_locelse_block)label~used~param_source~result_source~br_source:result_source~params~results~br_params:resultselse_block.desc;push_results~loc~source:result_sourceresults|TryTable{label;typ;block=b;catches}->let*!params,results,param_source,result_source=blocktypectxtypinlet*()=pop_argsctx(loc_first_charloc)~source:param_sourceparamsinletused=track_labelctxlabelinblockctxloclabel~used~param_source~result_source~br_source:result_source~params~results~br_params:resultsb.desc;List.iter(fun(catch:Ast.Text.catch)->matchcatchwith|Catch(tag,label)->let*?args,arg_source=lookup_tag_typectxtaginlet*?params,param_source=branch_targetctxlabelincompare_typesctx.modul~location:tag.info~descr:"this exception handler"~provided_source:arg_source~expected_source:param_source~provided:args~expected:params()|CatchRef(tag,label)->let*?args,arg_source=lookup_tag_typectxtaginlet*?params,param_source=branch_targetctxlabelinletprovided=Array.appendargs[|Ref{nullable=false;typ=Exn}|]inletprovided_source=Array.appendarg_source[|Plain(Ref{nullable=false;typ=Exn})|]incompare_typesctx.modul~location:tag.info~descr:"this exception handler"~provided_source~expected_source:param_source~provided~expected:params()|CatchAlllabel->Option.iter(fun(params,param_source)->compare_typesctx.modul~location:label.info~descr:"this exception handler"~provided_source:[||]~expected_source:param_source~provided:[||]~expected:params())(branch_targetctxlabel)|CatchAllReflabel->Option.iter(fun(params,param_source)->compare_typesctx.modul~location:label.info~descr:"this exception handler"~provided_source:[|Plain(Ref{nullable=false;typ=Exn})|]~expected_source:param_source~provided:[|Ref{nullable=false;typ=Exn}|]~expected:params())(branch_targetctxlabel))catches;push_results~loc~source:result_sourceresults|Try{label;typ;block=b;catches;catch_all}->let*!params,results,param_source,result_source=blocktypectxtypinlet*()=pop_argsctx(loc_first_charloc)~source:param_sourceparamsinletused=track_labelctxlabelinblockctxloclabel~used~param_source~result_source~br_source:result_source~params~results~br_params:resultsb.desc;List.iter(fun(tag,b)->let*?params',param_source=lookup_tag_typectxtaginblockctxloclabel~used~param_source~result_source~br_source:result_source~params:params'~results~br_params:resultsb.Ast.desc)catches;Option.iter(funb->blockctxloclabel~used~param_source~result_source~br_source:result_source~params~results~br_params:resultsb.Ast.desc)catch_all;push_results~loc~source:result_sourceresults|Unreachable->unreachable|Nop->return()|Throwidx->let*!params,param_source=lookup_tag_typectxidxinlet*()=pop_argsctxloc~source:param_sourceparamsinunreachable|ThrowRef->let*()=pop_knownctxloc(Ref{nullable=true;typ=Exn})inunreachable|ContNewx->let*!ty,ft,_=lookup_cont_typectxxinletfunc_source=matchreference_comptypectx.modul.typesxwith|Contr->named_ref_null_sourcer|_->assertfalseinlet*()=popctxloc~expected_source:func_source(Ref{nullable=true;typ=Typeft})inpush~source:(exact_ref_sourcectxx)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExacttyelseTypety);})|ContBind(x,y)->let*!xty,_,ftx=lookup_cont_typectxxinlet*!yty,_,fty=lookup_cont_typectxyinletn1=Array.lengthftx.paramsinletn1'=Array.lengthfty.paramsinifn1<n1'then(Error.stack_switching_type_mismatchctx.modul.diagnostics~location:loc~descr:"the resulting continuation takes more parameters than the \
original one";unreachable)elsebeginletts11=Array.subftx.params0(n1-n1')inletts12=Array.subftx.params(n1-n1')n1'inifnot(functype_matchesctx.modul.subtyping_info{params=ts12;results=ftx.results}fty)then(Error.stack_switching_type_mismatchctx.modul.diagnostics~location:loc~descr:"the bound parameters and results do not match between the two \
continuation types";unreachable)elsebeginlet*()=pop_argsctxloc~source:(cont_operand_source(Array.sub(cont_param_sourcectxx)0(n1-n1'))x)(Array.appendts11[|Ref{nullable=true;typ=Typexty}|])inpush~source:(exact_ref_sourcectxy)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExactytyelseTypeyty);})endend|Suspendx->let*!{params=ts1;results=ts2},sign=lookup_tag_signaturectxxinletparam_source,result_source=functype_sourcessigninlet*()=pop_argsctxloc~source:param_sourcets1inpush_results~loc~source:result_sourcets2|Resume(x,clauses)->let*!xty,_,ftx=lookup_cont_typectxxincheck_resume_tablectxlocftx.resultsclauses;let_,result_source=functype_sources(cont_source_functypectx.modul.typesx)inlet*()=pop_argsctxloc~source:(cont_operand_source(cont_param_sourcectxx)x)(Array.appendftx.params[|Ref{nullable=true;typ=Typexty}|])inpush_results~loc~source:result_sourceftx.results|ResumeThrow(x,y,clauses)->let*!xty,_,ftx=lookup_cont_typectxxinlet*!{params=ts0;_},sign=lookup_tag_signaturectxyincheck_resume_tablectxlocftx.resultsclauses;let_,result_source=functype_sources(cont_source_functypectx.modul.typesx)inlet*()=pop_argsctxloc~source:(cont_operand_source(fst(functype_sourcessign))x)(Array.appendts0[|Ref{nullable=true;typ=Typexty}|])inpush_results~loc~source:result_sourceftx.results|ResumeThrowRef(x,clauses)->let*!xty,_,ftx=lookup_cont_typectxxincheck_resume_tablectxlocftx.resultsclauses;let_,result_source=functype_sources(cont_source_functypectx.modul.typesx)inlet*()=pop_argsctxloc~source:[|Plain(Ref{nullable=true;typ=Exn});named_ref_null_sourcex;|][|Ref{nullable=true;typ=Exn};Ref{nullable=true;typ=Typexty};|]inpush_results~loc~source:result_sourceftx.results|Switch(x,y)->let*!xty,_,ftx=lookup_cont_typectxxinletts11=ftx.paramsinletn=Array.lengthts11inletinner=matchifn=0thenNoneelseSomets11.(n-1)with|Some(Ref{typ=ht;_})->cont_functype_of_heaptypectxht|_->Noneinlet*!inner_ft=matchinnerwith|Some_->inner|None->Error.stack_switching_type_mismatchctx.modul.diagnostics~location:loc~descr:"the continuation's last parameter must itself be a \
continuation type";Noneinlet*!{params=ts31;results=t},_=lookup_tag_signaturectxyinletinfo=ctx.modul.subtyping_infoinifArray.lengthts31<>0||(not(result_subtypeinfoftx.resultst))||not(result_subtypeinfotinner_ft.results)then(Error.stack_switching_type_mismatchctx.modul.diagnostics~location:loc~descr:"the 'switch' tag must take no parameters and its results must \
match the two continuation types";unreachable)elsebegin(* The inner continuation is named by [x]'s last parameter, so its
parameters' source types are that continuation's source params. *)letts21=inner_ft.paramsinletts21_text=(* [inner] is [Some] only when [x]'s last parameter is a concrete
continuation reference, so its source form is [(ref $idx)] or
[(ref (exact $idx))] — [cont_functype_of_heaptype] accepts both, so
the exactness does not change which type's params are looked up. *)match(cont_param_sourcectxx).(n-1)with|Plain(Ref{typ=Typeidx|Exactidx;_})->cont_param_sourcectxidx|_->assertfalseinletts11'=Array.subts110(n-1)inletts11'_text=Array.sub(cont_param_sourcectxx)0(n-1)inlet*()=pop_argsctxloc~source:(cont_operand_sourcets11'_textx)(Array.appendts11'[|Ref{nullable=true;typ=Typexty}|])inpush_results~loc~source:ts21_textts21end|Bridx->let*!params,param_source=branch_targetctxidxinlet*()=pop_argsctxloc~source:param_sourceparamsinunreachable|Br_ifidx->let*()=pop_knownctxlocI32inlet*!params,param_source=branch_targetctxidxinlet*()=pop_argsctxloc~source:param_sourceparamsinpush_results~sink:false~loc~source:param_sourceparams|Br_table(lst,idx)->let*()=pop_knownctxlocI32inlet*!params,_=branch_targetctxidxinletlen=Array.lengthparamsinlet*()=with_current_stack(funst->(* Check each DISTINCT target once: the check is purely per-target,
so a target repeated in the list — spelled by depth or by a
label name resolving to that same depth — would only repeat an
identical report. Unresolved targets are not deduplicated; each
occurrence reports its own unbound label at its own span. *)letseen=Hashtbl.create8inletrepeated(idx':Ast.Text.idx)=letdepth=matchidx'.descwith|Numi->Some(Uint32.to_inti)|Idid->letrecfindn=function|[]->None|(Someid',_,_,_)::_whenid=id'->Somen|_::rem->find(n+1)reminfind0ctx.control_typesinmatchdepthwith|None->false|Somed->Hashtbl.memseend||(Hashtbl.addseend();false)in(* Two DISTINCT targets can still impose the SAME types on the same
values, and then their reports are anchored identically too — a
present value's report points at where that value was pushed, not
at the label — so the second would print as one repeated
[line:col: message]. Key the value check on the requirement, not
the target: distinct types still report, once each. *)letchecked=ref[]inletrequirement_checkedparams=letsamep=Array.lengthp=Array.lengthparams&&Array.for_all2(funab->Types.val_subtypectx.modul.subtyping_infoab&&Types.val_subtypectx.modul.subtyping_infoba)pparamsinList.existssame!checked||(checked:=params::!checked;false)inList.iter(funidx'->ifnot(repeatedidx')thenlet*?params,param_source=branch_targetctxidx'inletlen'=Array.lengthparamsiniflen<>len'thenError.branch_parameter_count_mismatchctx.modul.diagnostics~location:idx'.Ast.info~default_loc:idx.Ast.infoidxlenidx'len'(* Blame the LABEL, not the instruction, as the arity report
above already does: the requirement is that target's, and
two targets demanding the same missing value would
otherwise print as one repeated [line:col: message]. *)elseifnot(requirement_checkedparams)thenignore(pop_argsctxidx'.Ast.info~source:param_sourceparamsst))(* In source order — the default target is written last — so the
per-label reports come out in the order they are read. *)(lst@[idx]))inunreachable|Br_on_nullidx->(let*ty,loc'=pop_anyctxlocin(* The branch carries the label's parameters; the value falls through with
its null case removed ([(ref bot)] when the operand was a bottom). *)letfallthroughpush_top=let*!params,param_source=branch_targetctxidxinlet*()=pop_argsctxloc~source:param_sourceparamsinlet*()=push_results~sink:false~loc~source:param_sourceparamsinpush_topinmatchtywith|Bot|Bot_ref->fallthrough(push_bot_ref(Someloc))|Val(Ref{nullable=_;typ},source)->fallthrough(push~source:(non_null_sourcesource)(Someloc)(Ref{nullable=false;typ}))|Val(_,source)->Error.expected_ref_typectx.modul.diagnostics~location:loc~src_loc:loc'~source;unreachable)|Br_on_non_nullidx->(let*ty,loc'=pop_anyctxlocin(* The branch carries the label's parameters ending in the non-null
reference ([(ref bot)] for a bottom operand); the value is consumed on
fall-through. *)letto_branchpush_ref=let*()=push_refinlet*!params,param_source=branch_targetctxidxin(* [br_on_non_null] requires the target label to be [t* (ref ht)]: the
pushed non-null reference is consumed by that trailing type. An empty
label has no such type, so [pop_args] would silently accept it. *)ifArray.lengthparams=0then(Error.br_on_non_null_no_refctx.modul.diagnostics~location:loc;unreachable)elselet*()=pop_argsctxloc~source:param_sourceparamsinlet*()=push_results~sink:false~loc~source:param_sourceparamsinlet*_=pop_anyctxlocinreturn()inmatchtywith|Bot|Bot_ref->to_branch(push_bot_refNone)|Val(Ref{nullable=_;typ},source)->to_branch(push~source:(non_null_sourcesource)None(Ref{nullable=false;typ}))|Val(_,source)->Error.expected_ref_typectx.modul.diagnostics~location:loc~src_loc:loc'~source;unreachable)|Br_on_cast(idx,ty1,ty2)->letsrc_ty1=Plain(Ast.Text.Refty1)andsrc_ty2=Plain(Ast.Text.Refty2)in(* The value that falls through has [ty1]'s heap type, non-null once a
nullable [ty2] has consumed the null case. *)letsrc_diff=Plain(Ast.Text.Ref{nullable=ty1.nullable&¬ty2.nullable;typ=ty1.typ})inlet*!ty1=reftypectx.modul.diagnosticsctx.modul.typesty1inlet*!ty2=reftypectx.modul.diagnosticsctx.modul.typesty2in(match(top_heap_typectxty1.typ,top_heap_typectxty2.typ)with|Cont,_|_,Cont->Error.invalid_cast_typectx.modul.diagnostics~location:loc|_->());ifnot(br_cast_compatiblectxty1ty2)thenError.br_cast_type_mismatchctx.modul.diagnostics~location:loc;let*()=popctxloc~expected_source:src_ty1(Refty1)inlet*()=push~source:src_ty2None(Refty2)inlet*!params,param_source=branch_cast_targetctxidx~location:locinlet*()=pop_argsctxloc~source:param_sourceparamsinlet*()=push_results~sink:false~loc~source:param_sourceparamsinlet*_=pop_anyctxlocinpush~source:src_diff(Someloc)(Ref(diff_ref_typety1ty2))|Br_on_cast_fail(idx,ty1,ty2)->letsrc_ty1=Plain(Ast.Text.Refty1)andsrc_ty2=Plain(Ast.Text.Refty2)in(* The value sent to the branch has [ty1]'s heap type, non-null once a
nullable [ty2] has consumed the null case. *)letsrc_diff=Plain(Ast.Text.Ref{nullable=ty1.nullable&¬ty2.nullable;typ=ty1.typ})inlet*!ty1=reftypectx.modul.diagnosticsctx.modul.typesty1inlet*!ty2=reftypectx.modul.diagnosticsctx.modul.typesty2in(match(top_heap_typectxty1.typ,top_heap_typectxty2.typ)with|Cont,_|_,Cont->Error.invalid_cast_typectx.modul.diagnostics~location:loc|_->());ifnot(br_cast_compatiblectxty1ty2)thenError.br_cast_type_mismatchctx.modul.diagnostics~location:loc;let*()=popctxloc~expected_source:src_ty1(Refty1)inlet*()=push~source:src_diffNone(Ref(diff_ref_typety1ty2))inlet*!params,param_source=branch_cast_targetctxidx~location:locinlet*()=pop_argsctxloc~source:param_sourceparamsinlet*()=push_results~sink:false~loc~source:param_sourceparamsinlet*_=pop_anyctxlocinpush~source:src_ty2(Someloc)(Refty2)|Br_on_cast_desc_eq(idx,ty1,ty2)->(* As [br_on_cast], preceded by consuming a descriptor operand whose
exactness matches the target [ty2]. *)letsrc_ty1=Plain(Ast.Text.Refty1)andsrc_ty2=Plain(Ast.Text.Refty2)inletsrc_diff=Plain(Ast.Text.Ref{nullable=ty1.nullable&¬ty2.nullable;typ=ty1.typ})inlet*!ty1=reftypectx.modul.diagnosticsctx.modul.typesty1inlet*!ty2=reftypectx.modul.diagnosticsctx.modul.typesty2in(match(top_heap_typectxty1.typ,top_heap_typectxty2.typ)with|Cont,_|_,Cont->Error.invalid_cast_typectx.modul.diagnostics~location:loc|_->());ifnot(br_cast_compatiblectxty1ty2)thenError.br_cast_type_mismatchctx.modul.diagnostics~location:loc;let*!desc_ht=descriptor_operand_typectx~location:locty2.typinlet*()=popctxloc~expected_source:(descriptor_operand_sourcectx.modul.typesty2.typ)(Ref{nullable=true;typ=desc_ht})inlet*()=popctxloc~expected_source:src_ty1(Refty1)inlet*()=push~source:src_ty2None(Refty2)inlet*!params,param_source=branch_cast_targetctxidx~location:locinlet*()=pop_argsctxloc~source:param_sourceparamsinlet*()=push_results~sink:false~loc~source:param_sourceparamsinlet*_=pop_anyctxlocinpush~source:src_diff(Someloc)(Ref(diff_ref_typety1ty2))|Br_on_cast_desc_eq_fail(idx,ty1,ty2)->letsrc_ty1=Plain(Ast.Text.Refty1)andsrc_ty2=Plain(Ast.Text.Refty2)inletsrc_diff=Plain(Ast.Text.Ref{nullable=ty1.nullable&¬ty2.nullable;typ=ty1.typ})inlet*!ty1=reftypectx.modul.diagnosticsctx.modul.typesty1inlet*!ty2=reftypectx.modul.diagnosticsctx.modul.typesty2in(match(top_heap_typectxty1.typ,top_heap_typectxty2.typ)with|Cont,_|_,Cont->Error.invalid_cast_typectx.modul.diagnostics~location:loc|_->());ifnot(br_cast_compatiblectxty1ty2)thenError.br_cast_type_mismatchctx.modul.diagnostics~location:loc;let*!desc_ht=descriptor_operand_typectx~location:locty2.typinlet*()=popctxloc~expected_source:(descriptor_operand_sourcectx.modul.typesty2.typ)(Ref{nullable=true;typ=desc_ht})inlet*()=popctxloc~expected_source:src_ty1(Refty1)inlet*()=push~source:src_diffNone(Ref(diff_ref_typety1ty2))inlet*!params,param_source=branch_cast_targetctxidx~location:locinlet*()=pop_argsctxloc~source:param_sourceparamsinlet*()=push_results~sink:false~loc~source:param_sourceparamsinlet*_=pop_anyctxlocinpush~source:src_ty2(Someloc)(Refty2)|Return->let*()=pop_argsctx~kind:`Outputloc~source:ctx.return_sourcectx.return_typesinunreachable|Callidx->(let*!ty,_,sign,_=get_functionctxidxinmatch(Types.get_subtypectx.modul.subtyping_infoty).typwith|Struct_|Array_|Cont_->Error.expected_func_typectx.modul.diagnostics~location:locidx;unreachable|Func{params;results}->letparam_source,result_source=functype_sourcessignin(* Give the callee identifier the function's signature on hover. *)record(Someidx.info)(Signature(param_source,result_source));let*()=pop_argsctxloc~source:param_sourceparamsinpush_results~loc~source:result_sourceresults)|CallRefidx->let*!type_idx,{params;results}=lookup_func_typectxidxinletparam_source,result_source=functype_sources(reference_functypectx.modul.typesidx)inlet*()=popctxloc~expected_source:(named_ref_null_sourceidx)(Ref{nullable=true;typ=Typetype_idx})inlet*()=pop_argsctxloc~source:param_sourceparamsinpush_results~loc~source:result_sourceresults|CallIndirect(idx,tu)->(let*!typ,table_source=get_tablectxidxinlet*!ty=typeusectx.modul.diagnosticsctx.modul.typestuinifnot(Types.val_subtypectx.modul.subtyping_info(Reftyp.reftype)(Ref{nullable=true;typ=Func}))then(Error.table_type_mismatchctx.modul.diagnostics~location:loc~source:table_sourceidx;unreachable)elsematch(Types.get_subtypectx.modul.subtyping_infoty).typwith|Struct_|Array_|Cont_->Error.expected_func_typectx.modul.diagnostics~location:locidx;unreachable|Func{params;results}->letparam_source,result_source=functype_sources(typeuse_functypectx.modul.typestu)inlet*()=pop_addressctxloctyp.limitsinlet*()=pop_argsctxloc~source:param_sourceparamsinpush_results~loc~source:result_sourceresults)|ReturnCallidx->(let*!ty,_,sign,_=get_functionctxidxinmatch(Types.get_subtypectx.modul.subtyping_infoty).typwith|Struct_|Array_|Cont_->Error.expected_func_typectx.modul.diagnostics~location:locidx;unreachable|Func{params;results}->letparam_source,result_source=functype_sourcessigninrecord(Someidx.info)(Signature(param_source,result_source));let*()=pop_argsctxloc~source:param_sourceparamsincompare_typesctx.modul~location:idx.info~descr:"this tail call"~provided_source:result_source~expected_source:ctx.return_source~provided:results~expected:ctx.return_types();unreachable)|ReturnCallRefidx->let*!type_idx,{params;results}=lookup_func_typectxidxinletparam_source,result_source=functype_sources(reference_functypectx.modul.typesidx)inlet*()=popctxloc~expected_source:(named_ref_null_sourceidx)(Ref{nullable=true;typ=Typetype_idx})inlet*()=pop_argsctxloc~source:param_sourceparamsincompare_typesctx.modul~location:idx.info~descr:"this tail call"~provided_source:result_source~expected_source:ctx.return_source~provided:results~expected:ctx.return_types();unreachable|ReturnCallIndirect(idx,tu)->(let*!typ,table_source=get_tablectxidxinlet*!ty=typeusectx.modul.diagnosticsctx.modul.typestuinifnot(Types.val_subtypectx.modul.subtyping_info(Reftyp.reftype)(Ref{nullable=true;typ=Func}))then(Error.table_type_mismatchctx.modul.diagnostics~location:loc~source:table_sourceidx;unreachable)elsematch(Types.get_subtypectx.modul.subtyping_infoty).typwith|Struct_|Array_|Cont_->Error.expected_func_typectx.modul.diagnostics~location:locidx;unreachable|Func{params;results}->letparam_source,result_source=functype_sources(typeuse_functypectx.modul.typestu)inlet*()=pop_addressctxloctyp.limitsinlet*()=pop_argsctxloc~source:param_sourceparamsin(* Anchor at the typeuse's type index (the callee type as written)
when there is one; an inline-only typeuse keeps the whole
instruction. *)compare_typesctx.modul~location:(matchfsttuwithSometidx->tidx.Ast.info|None->loc)~descr:"this tail call"~provided_source:result_source~expected_source:ctx.return_source~provided:results~expected:ctx.return_types();unreachable)|Drop->let*_=pop_anyctxlocinreturn()|SelectNone->(let*()=pop_knownctxlocI32inlet*ty1,loc1=pop_anyctxlocinlet*ty2,loc2=pop_anyctxlocin(* A bare [select] forbids reference operands; the bottom reference, like
any reference, is rejected here. Each operand reduces to its value
([None] when unknown), so the cases below mirror the operand stack.
Each report is anchored at its operand's push location (when known),
so the two operands' reports stay distinguishable. *)letas_operandsrc_loc=function|Bot->None|Bot_ref->Error.expected_number_or_vecctx.modul.diagnostics~location:loc~src_loc~source:Bottom_ref;None|Val(ty,source)->Some(ty,source)inmatch(as_operandloc1ty1,as_operandloc2ty2)with|None,None->push_polyloc|Some(ty1,source1),Some(ty2,source2)->ifnot(number_or_vecty1)thenError.expected_number_or_vecctx.modul.diagnostics~location:loc~src_loc:loc1~source:source1;ifnot(number_or_vecty2)thenError.expected_number_or_vecctx.modul.diagnostics~location:loc~src_loc:loc2~source:source2;ifty1<>ty2thenError.select_type_mismatchctx.modul.diagnostics~location:loc~loc1~source1~loc2~source2;push~source:source1(Someloc)ty1|Some(ty,source),None->ifnot(number_or_vecty)thenError.expected_number_or_vecctx.modul.diagnostics~location:loc~src_loc:loc1~source;push~source(Someloc)ty|None,Some(ty,source)->ifnot(number_or_vecty)thenError.expected_number_or_vecctx.modul.diagnostics~location:loc~src_loc:loc2~source;push~source(Someloc)ty)|Select(Somelst)->(matchlstwith|[typ]->letsrc_typ=Plaintypinlet*!typ=valtypectx.modul.diagnosticsctx.modul.typestypinlet*()=pop_knownctxlocI32inlet*()=popctxloc~expected_source:src_typtypinlet*()=popctxloc~expected_source:src_typtypinpush~source:src_typ(Someloc)typ|_->Error.select_result_countctx.modul.diagnostics~location:loc;pop_knownctxlocI32)|LocalGeti->let*!ty,source=get_localctxiin(* A poisoned local (unresolved declared type) pushes the bottom reference
rather than the recovery dummy, so a consumer does not report a second
mismatch against it. *)iflocal_is_poisonedctxithenpush_bot_ref(Someloc)elsepush~source(Someloc)ty|LocalSeti->let*!ty,source=get_local~initialize:truectxiin(* Give the identifier the local's type, so hover over [$x] shows it even
though [local.set] itself leaves nothing on the stack. *)record(Somei.info)(Pushedsource);iflocal_is_poisonedctxithenlet*_=pop_anyctxlocinreturn()elsepopctxloc~expected_source:sourcety|LocalTeei->let*!ty,source=get_local~initialize:truectxiinrecord(Somei.info)(Pushedsource);iflocal_is_poisonedctxithenlet*_=pop_anyctxlocinpush_bot_ref(Someloc)elselet*()=popctxloc~expected_source:sourcetyinpush~source(Someloc)ty|GlobalGetidx->let*!ty,source=get_globalctxidxinpush~source(Someloc)ty.typ|GlobalSetidx->let*!ty,source=get_globalctxidxin(* The single [global.set] site, so it is also where a mutable global is
recorded as actually assigned (for [unnecessary-mut]). *)mark_indexctx.modul.assigned_globalsctx.modul.globalsidx;record(Someidx.info)(Pushedsource);ifnotty.mutthenError.immutable_globalctx.modul.diagnostics~location:locidx;popctxloc~expected_source:sourcety.typ|Load(idx,memarg,ty)->let*!limits=get_memoryctxidxinletty,sz=memory_instruction_type_and_sizetyincheck_memargctxloclimitsszmemarg;let*()=pop_addressctxloclimitsinpush~source:(source_of_valtypety)(Someloc)ty|LoadS(idx,memarg,ty,sz,_)->let*!limits=get_memoryctxidxinletty=matchtywith`I32->I32|`I64->I64incheck_memargctxloclimits(sz:>[`I8|`I16|`I32|`I64|`V128])memarg;let*()=pop_addressctxloclimitsinpush~source:(source_of_valtypety)(Someloc)ty|Store(idx,memarg,ty)->let*!limits=get_memoryctxidxinletty,sz=memory_instruction_type_and_sizetyincheck_memargctxloclimitsszmemarg;let*()=pop_knownctxloctyinlet*()=pop_addressctxloclimitsinreturn()|StoreS(idx,memarg,ty,sz)->let*!limits=get_memoryctxidxinletty=matchtywith`I32->I32|`I64->I64incheck_memargctxloclimits(sz:>[`I8|`I16|`I32|`I64|`V128])memarg;let*()=pop_knownctxloctyinpop_addressctxloclimits|Atomic(idx,op,memarg)->let*!limits=get_memoryctxidxincheck_atomic_memargctxloclimitsopmemarg;letvt=function`I32->I32|`I64->I64inletoperands,results=Atomics.signatureopin(* Operands sit above the address, topmost last, so pop in reverse. *)let*()=List.fold_left(funacct->let*()=accinpop_knownctxloc(vtt))(return())(List.revoperands)inlet*()=pop_addressctxloclimitsinList.fold_left(funacct->let*()=accinpush_known(Someloc)(vtt))(return())results|AtomicFence->return()|MemorySizeidx->let*!limits=get_memoryctxidxinletty=address_type_to_valtypelimits.address_typeinpush~source:(source_of_valtypety)(Someloc)ty|MemoryGrowidx->let*!limits=get_memoryctxidxinletaddr_ty=address_type_to_valtypelimits.address_typeinlet*()=pop_knownctxlocaddr_tyinpush~source:(source_of_valtypeaddr_ty)(Someloc)addr_ty|MemoryFillidx->let*!limits=get_memoryctxidxinletaddr_ty=address_type_to_valtypelimits.address_typeinlet*()=pop_knownctxlocaddr_tyinlet*()=pop_knownctxlocI32inpop_knownctxlocaddr_ty|MemoryCopy(idx,idx')->let*!limits=get_memoryctxidxinlet*!limits'=get_memoryctxidx'in(* The length operand uses the smaller of the two address types: i32 if
either memory is 32-bit, i64 only if both are 64-bit. *)letaddress_type=match(limits.address_type,limits'.address_type)with|`I32,_|_,`I32->`I32|`I64,`I64->`I64inletaddr_ty=address_type_to_valtypelimits.address_typeinletaddr_ty'=address_type_to_valtypelimits'.address_typeinletaddr_ty''=address_type_to_valtypeaddress_typeinlet*()=pop_knownctxlocaddr_ty''inlet*()=pop_knownctxlocaddr_ty'inpop_knownctxlocaddr_ty|MemoryInit(idx,idx')->let*!limits=get_memoryctxidxinignore(get_datactxidx');letaddr_ty=address_type_to_valtypelimits.address_typeinlet*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inpop_knownctxlocaddr_ty|DataDropidx->ignore(get_datactxidx);return()|VecBinOp_->let*()=pop_knownctxlocV128inlet*()=pop_knownctxlocV128inpush_known(Someloc)V128|VecConst_->push_known(Someloc)V128|VecUnOp_->let*()=pop_knownctxlocV128inpush_known(Someloc)V128|VecTest_->let*()=pop_knownctxlocV128inpush_known(Someloc)I32|VecShift_->let*()=pop_knownctxlocI32inlet*()=pop_knownctxlocV128inpush_known(Someloc)V128|VecBitmask_->let*()=pop_knownctxlocV128inpush_known(Someloc)I32|VecTernOp_->let*()=pop_knownctxlocV128inlet*()=pop_knownctxlocV128inlet*()=pop_knownctxlocV128inpush_known(Someloc)V128|VecBitselect->let*()=pop_knownctxlocV128inlet*()=pop_knownctxlocV128inlet*()=pop_knownctxlocV128inpush_known(Someloc)V128|VecSplatshape->letty=shape_typeshapeinlet*()=pop_knownctxloctyinpush_known(Someloc)V128|VecLoad(idx,sz,memarg)->let*!limits=get_memoryctxidxincheck_memargctxloclimits(matchszwith|Load128->`V128|Load8x8S|Load8x8U|Load16x4S|Load16x4U|Load32x2S|Load32x2U|Load64Zero->`I64|Load32Zero->`I32)memarg;let*()=pop_addressctxloclimitsinpush_known(Someloc)V128|VecStore(idx,memarg)->let*!limits=get_memoryctxidxincheck_memargctxloclimits`V128memarg;let*()=pop_knownctxlocV128inlet*()=pop_addressctxloclimitsinreturn()|VecLoadLane(idx,op,mem,lane)->let*!limits=get_memoryctxidxincheck_memargctxloclimits(op:>[`I8|`I16|`I32|`I64|`F32|`F64|`V128])mem;letsz=matchopwith`I8->1|`I16->2|`I32->4|`I64->8iniflane>=16/szthenError.invalid_lane_indexctx.modul.diagnostics~location:loc(16/sz);let*()=pop_knownctxlocV128inlet*()=pop_addressctxloclimitsinpush_known(Someloc)V128|VecStoreLane(idx,op,mem,lane)->let*!limits=get_memoryctxidxincheck_memargctxloclimits(op:>[`I8|`I16|`I32|`I64|`F32|`F64|`V128])mem;letsz=matchopwith`I8->1|`I16->2|`I32->4|`I64->8iniflane>=16/szthenError.invalid_lane_indexctx.modul.diagnostics~location:loc(16/sz);let*()=pop_knownctxlocV128inlet*()=pop_addressctxloclimitsinreturn()|VecLoadSplat(idx,op,mem)->let*!limits=get_memoryctxidxincheck_memargctxloclimits(op:>[`I8|`I16|`I32|`I64|`F32|`F64|`V128])mem;let*()=pop_addressctxloclimitsinpush_known(Someloc)V128|VecExtract(shape,_,lane)->check_shape_lanesctxlocshapelane;let*()=pop_knownctxlocV128inpush_known(Someloc)(shape_typeshape)|VecReplace(shape,lane)->check_shape_lanesctxlocshapelane;let*()=pop_knownctxloc(shape_typeshape)inlet*()=pop_knownctxlocV128inpush_known(Someloc)V128|VecShufflelanes->ifnot(String.for_all(funl->Char.codel<32)lanes)thenError.invalid_lane_indexctx.modul.diagnostics~location:loc32;let*()=pop_knownctxlocV128inlet*()=pop_knownctxlocV128inpush_known(Someloc)V128|TableGetidx->let*!typ,source=get_tablectxidxinletaddr_ty=address_type_to_valtypetyp.limits.address_typeinlet*()=pop_knownctxlocaddr_tyinpush~source(Someloc)(Reftyp.reftype)|TableSetidx->let*!typ,source=get_tablectxidxinletaddr_ty=address_type_to_valtypetyp.limits.address_typeinlet*()=popctxloc~expected_source:source(Reftyp.reftype)inpop_knownctxlocaddr_ty|TableSizeidx->let*!typ,_=get_tablectxidxinpush_known(Someloc)(address_type_to_valtypetyp.limits.address_type)|TableGrowidx->let*!typ,source=get_tablectxidxinletaddr_ty=address_type_to_valtypetyp.limits.address_typeinlet*()=pop_knownctxlocaddr_tyinlet*()=popctxloc~expected_source:source(Reftyp.reftype)inpush_known(Someloc)addr_ty|TableFillidx->let*!typ,source=get_tablectxidxinletaddr_ty=address_type_to_valtypetyp.limits.address_typeinlet*()=pop_knownctxlocaddr_tyinlet*()=popctxloc~expected_source:source(Reftyp.reftype)inpop_knownctxlocaddr_ty|TableCopy(idx,idx')->let*!ty,dst_source=get_tablectxidxinlet*!ty',src_source=get_tablectxidx'inifnot(Types.val_subtypectx.modul.subtyping_info(Refty'.reftype)(Refty.reftype))thenError.type_mismatchctx.modul.diagnostics~location:loc~provided_source:src_source~expected_source:dst_source;(* The length operand uses the smaller of the two address types: i32 if
either table is 32-bit, i64 only if both are 64-bit. *)letaddress_type=match(ty.limits.address_type,ty'.limits.address_type)with|`I32,_|_,`I32->`I32|`I64,`I64->`I64inletaddr_ty=address_type_to_valtypety.limits.address_typeinletaddr_ty'=address_type_to_valtypety'.limits.address_typeinletaddr_ty''=address_type_to_valtypeaddress_typeinlet*()=pop_knownctxlocaddr_ty''inlet*()=pop_knownctxlocaddr_ty'inpop_knownctxlocaddr_ty|TableInit(idx,idx')->let*!tabletype,table_source=get_tablectxidxinlet*!typ,elem_source=get_elemctxidx'inifnot(Types.val_subtypectx.modul.subtyping_info(Reftyp)(Reftabletype.reftype))thenError.type_mismatchctx.modul.diagnostics~location:loc~provided_source:elem_source~expected_source:table_source;letaddr_ty=address_type_to_valtypetabletype.limits.address_typeinlet*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inpop_knownctxlocaddr_ty|ElemDropidx->let*!_=get_elemctxidxinreturn()|RefNulltyp->letsource=PlainAst.Text.(Ref{nullable=true;typ})inlet*!typ=heaptypectx.modul.diagnosticsctx.modul.typestypinpush~source(Someloc)(Ref{nullable=true;typ})|RefFuncidx->let*!i,type_idx,sign,exact=get_functionctxidxinletparam_source,result_source=functype_sourcessigninrecord(Someidx.info)(Signature(param_source,result_source));ifnot((not!validate_refs)||Hashtbl.memctx.modul.refs(Sequence.get_indexctx.modul.functionsidx))thenError.ref_func_inaccessiblectx.modul.diagnostics~location:locidx;(* Name the function's type when it was declared with a named type,
otherwise show the signature inline (a numeric index would be
meaningless, as the interned index space is deduplicated). *)letsource=matchtype_idxwith|Some({desc=Id_;_}asidx)->(* Match the pushed internal type's exactness (below) so the source
rendering agrees with it — but only when the exactness is
expressible ([exact_ref_source] renders exact under
custom-descriptors, plain otherwise). *)ifexactthenexact_ref_sourcectxidxelsenamed_ref_sourceidx|_->Inline_ref(Funcsign)inpush~source(Someloc)(Ref{nullable=false;typ=(ifexactthenExactielseTypei)})|RefIsNull->(let*ty,loc'=pop_anyctxlocinmatchtywith|Bot|Bot_ref|Val(Ref_,_)->push_known(Someloc)I32|Val(_,source)->Error.expected_ref_typectx.modul.diagnostics~location:loc~src_loc:loc'~source;unreachable)|RefAsNonNull->(let*ty,loc'=pop_anyctxlocinmatchtywith|Bot|Bot_ref->push_bot_ref(Someloc)|Val(Refty,source)->push~source:(non_null_sourcesource)(Someloc)(Ref{tywithnullable=false})|Val(_,source)->Error.expected_ref_typectx.modul.diagnostics~location:loc~src_loc:loc'~source;unreachable)|RefEq->let*()=pop_knownctxloc(Ref{nullable=true;typ=Eq})inlet*()=pop_knownctxloc(Ref{nullable=true;typ=Eq})inpush_known(Someloc)I32|RefTestty->let*!ty=reftypectx.modul.diagnosticsctx.modul.typestyin(matchtop_heap_typectxty.typwith|Cont->Error.invalid_cast_typectx.modul.diagnostics~location:loc|_->());let*()=lint_castctx~location:loc~is_test:truetyinlet*()=pop_knownctxloc(Ref{nullable=true;typ=top_heap_typectxty.typ})inpush_known(Someloc)I32|RefCastty->letsource=PlainAst.Text.(Refty)inlet*!ty=reftypectx.modul.diagnosticsctx.modul.typestyin(matchtop_heap_typectxty.typwith|Cont->Error.invalid_cast_typectx.modul.diagnostics~location:loc|_->());let*()=lint_castctx~location:loc~is_test:falsetyinlet*()=pop_knownctxloc(Ref{nullable=true;typ=top_heap_typectxty.typ})inpush~source(Someloc)(Refty)|RefCastDescEqty->letsource=PlainAst.Text.(Refty)inlet*!ty=reftypectx.modul.diagnosticsctx.modul.typestyin(* The descriptor operand (top of stack); its exactness matches the target
[ty]. *)let*!desc_ht=descriptor_operand_typectx~location:locty.typinlet*()=popctxloc~expected_source:(descriptor_operand_sourcectx.modul.typesty.typ)(Ref{nullable=true;typ=desc_ht})inlet*()=pop_knownctxloc(Ref{nullable=true;typ=top_heap_typectxty.typ})inpush~source(Someloc)(Refty)|RefGetDescidx->let*!ty,_,_=lookup_struct_typectxidxinlet*!desc=type_descriptorctx~location:i.infotyinlet*entry,_=pop_anyctxlocin(* [exact_1] is shared between the operand type [(ref null (exact_1 idx))]
and the result [(ref (exact_1 desc))]: the descriptor is exact exactly
when the operand is a subtype of the *exact* operand type. A subtype
[(ref (exact $c))] with [$c <: idx] is NOT — its descriptor is [$c]'s,
not [idx]'s — so the result is inexact there. A polymorphic operand
(unreachable) fits either; take the most precise, exact. Validate the
operand is a reference to [idx] (a subtype or null) either way. *)letexact=matchentrywith|Bot|Bot_ref->true|Val(ty',source)->ifnot(Types.val_subtypectx.modul.subtyping_infoty'(Ref{nullable=true;typ=Typety}))thenError.type_mismatchctx.modul.diagnostics~location:loc~provided_source:source~expected_source:(named_ref_null_sourceidx);Types.val_subtypectx.modul.subtyping_infoty'(Ref{nullable=true;typ=Exactty})inpush~source:(descriptor_operand_source~nullable:falsectx.modul.types(ifexactthenExacttyelseTypety))(Someloc)(Ref{nullable=false;typ=(ifexactthenExactdescelseTypedesc)})|StructNewidx->let*!ty,_,fields=lookup_struct_typectxidxinifOption.is_some(Types.get_subtypectx.modul.subtyping_infoty).descriptorthenError.descriptor_allocation_requiredctx.modul.diagnostics~location:i.info;let*()=pop_argsctxloc~source:(Array.init(Array.lengthfields)(source_field_valtypectxidx))(Array.map(fun(f:fieldtype)->matchf.typwithValuev->v|Packed_->I32)fields)inpush~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExacttyelseTypety);})|StructNewDefaultidx->let*!ty,_,fields=lookup_struct_typectxidxinifnot(Array.for_allfield_has_defaultfields)thenError.not_defaultablectx.modul.diagnostics~location:i.info;ifOption.is_some(Types.get_subtypectx.modul.subtyping_infoty).descriptorthenError.descriptor_allocation_requiredctx.modul.diagnostics~location:i.info;push~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExacttyelseTypety);})|StructNewDescidx->let*!ty,_,fields=lookup_struct_typectxidxinlet*!desc=type_descriptorctx~location:i.infotyin(* The descriptor operand is on top of the field values. *)let*()=popctxloc~expected_source:(descriptor_operand_sourcectx.modul.types(Exactty))(Ref{nullable=true;typ=Exactdesc})inlet*()=pop_argsctxloc~source:(Array.init(Array.lengthfields)(source_field_valtypectxidx))(Array.map(fun(f:fieldtype)->matchf.typwithValuev->v|Packed_->I32)fields)inpush~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=Exactty})|StructNewDefaultDescidx->let*!ty,_,fields=lookup_struct_typectxidxinifnot(Array.for_allfield_has_defaultfields)thenError.not_defaultablectx.modul.diagnostics~location:i.info;let*!desc=type_descriptorctx~location:i.infotyinlet*()=popctxloc~expected_source:(descriptor_operand_sourcectx.modul.types(Exactty))(Ref{nullable=true;typ=Exactdesc})inpush~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=Exactty})|StructGet(signage,idx,idx')->let*!ty,field_map,fields=lookup_struct_typectxidxinlet*()=popctxloc~expected_source:(named_ref_null_sourceidx)(Ref{nullable=true;typ=Typety})inlet*!n=struct_field_indexctxidx'field_mapfieldsin(matchfields.(n).typwith|Packed_->ifsignage=NonethenError.packed_struct_accessctx.modul.diagnostics~location:i.info|Value_->ifsignage<>NonethenError.unpacked_struct_accessctx.modul.diagnostics~location:i.info);push~source:(source_field_valtypectxidxn)(Someloc)(unpack_typefields.(n))|StructSet(idx,idx')->let*!ty,field_map,fields=lookup_struct_typectxidxinlet*!n=struct_field_indexctxidx'field_mapfieldsinifnotfields.(n).mutthenError.immutablectx.modul.diagnostics~location:i.info"field";let*()=popctxloc~expected_source:(source_field_valtypectxidxn)(unpack_typefields.(n))inpopctxloc~expected_source:(named_ref_null_sourceidx)(Ref{nullable=true;typ=Typety})|ArrayNewidx->let*!ty,field=lookup_array_typectxidxinlet*()=pop_knownctxlocI32inlet*()=popctxloc~expected_source:(source_element_valtypectxidx)(unpack_typefield)inpush~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExacttyelseTypety);})|ArrayNewDefaultidx->let*!ty,field=lookup_array_typectxidxinifnot(field_has_defaultfield)thenError.not_defaultablectx.modul.diagnostics~location:i.info;let*()=pop_knownctxlocI32inpush~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExacttyelseTypety);})|ArrayNewFixed(idx,n)->let*!ty,field=lookup_array_typectxidxinlet*()=pop_repeatctxloc~expected_source:(source_element_valtypectxidx)(unpack_typefield)(Uint32.to_intn)inpush~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExacttyelseTypety);})|ArrayNewData(idx,idx')->let*!ty,field=lookup_array_typectxidxinignore(get_datactxidx');(matchfield.typwith|Packed_|Value(I32|I64|F32|F64|V128)->()|Value(Ref_)->Error.numeric_array_requiredctx.modul.diagnostics~location:i.info);let*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inpush~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExacttyelseTypety);})|ArrayNewElem(idx,idx')->let*!ty,field=lookup_array_typectxidxinlet*!ty',_=get_elemctxidx'in(matchfield.typwith|ValuetywhenTypes.val_subtypectx.modul.subtyping_info(Refty')ty->()|_->Error.incompatible_array_elementctx.modul.diagnostics~location:i.info);let*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inpush~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExacttyelseTypety);})|ArrayGet(signage,idx)->let*!ty,field=lookup_array_typectxidxin(matchfield.typwith|Packed_->ifsignage=NonethenError.packed_array_accessctx.modul.diagnostics~location:i.info|Value_->ifsignage<>NonethenError.unpacked_array_accessctx.modul.diagnostics~location:i.info);let*()=pop_knownctxlocI32inlet*()=popctxloc~expected_source:(named_ref_null_sourceidx)(Ref{nullable=true;typ=Typety})inpush~source:(source_element_valtypectxidx)(Someloc)(unpack_typefield)|ArraySetidx->let*!ty,field=lookup_array_typectxidxinifnotfield.mutthenError.immutablectx.modul.diagnostics~location:i.info"array";let*()=popctxloc~expected_source:(source_element_valtypectxidx)(unpack_typefield)inlet*()=pop_knownctxlocI32inpopctxloc~expected_source:(named_ref_null_sourceidx)(Ref{nullable=true;typ=Typety})|ArrayLen->let*()=pop_knownctxloc(Ref{nullable=true;typ=Array})inpush_known(Someloc)I32|ArrayFillidx->let*!ty,field=lookup_array_typectxidxinifnotfield.mutthenError.immutablectx.modul.diagnostics~location:i.info"array";let*()=pop_knownctxlocI32inlet*()=popctxloc~expected_source:(source_element_valtypectxidx)(unpack_typefield)inlet*()=pop_knownctxlocI32inpopctxloc~expected_source:(named_ref_null_sourceidx)(Ref{nullable=true;typ=Typety})|ArrayCopy(idx1,idx2)->let*!ty1,field1=lookup_array_typectxidx1inlet*!ty2,field2=lookup_array_typectxidx2inifnotfield1.mutthenError.immutablectx.modul.diagnostics~location:i.info"array";ifnot(storage_subtypectx.modul.subtyping_infofield1.typfield2.typ)thenError.incompatible_array_elementctx.modul.diagnostics~location:i.info;let*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inlet*()=popctxloc~expected_source:(named_ref_null_sourceidx2)(Ref{nullable=true;typ=Typety2})inlet*()=pop_knownctxlocI32inpopctxloc~expected_source:(named_ref_null_sourceidx1)(Ref{nullable=true;typ=Typety1})|ArrayInitData(idx,idx')->let*!ty,field=lookup_array_typectxidxinignore(get_datactxidx');ifnotfield.mutthenError.immutablectx.modul.diagnostics~location:i.info"array";(matchfield.typwith|Packed_|Value(I32|I64|F32|F64|V128)->()|Value(Ref_)->Error.numeric_array_requiredctx.modul.diagnostics~location:i.info);let*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inpopctxloc~expected_source:(named_ref_null_sourceidx)(Ref{nullable=true;typ=Typety})|ArrayInitElem(idx,idx')->let*!ty,field=lookup_array_typectxidxinlet*!ty',_=get_elemctxidx'inifnotfield.mutthenError.immutablectx.modul.diagnostics~location:i.info"array";(matchfield.typwith|ValuetywhenTypes.val_subtypectx.modul.subtyping_info(Refty')ty->()|_->Error.incompatible_array_elementctx.modul.diagnostics~location:i.info);let*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inpopctxloc~expected_source:(named_ref_null_sourceidx)(Ref{nullable=true;typ=Typety})|RefI31->let*()=pop_knownctxlocI32inpush_known(Someloc)(Ref{nullable=false;typ=I31})|I31Get_->let*()=pop_knownctxloc(Ref{nullable=true;typ=I31})inpush_known(Someloc)I32|Const(I32_)->push_known(Someloc)I32|Const(I64_)->push_known(Someloc)I64|Const(F32_)->push_known(Someloc)F32|Const(F64_)->push_known(Someloc)F64|UnOp(I32op)->letexpected,returned=int_un_op_typeI32opinlet*()=pop_knownctxlocexpectedinpush_known(Someloc)returned|UnOp(I64op)->letexpected,returned=int_un_op_typeI64opinlet*()=pop_knownctxlocexpectedinpush_known(Someloc)returned|UnOp(F32op)->letexpected=float_un_op_typeF32opinlet*()=pop_knownctxlocexpectedinpush_known(Someloc)F32|UnOp(F64op)->letexpected=float_un_op_typeF64opinlet*()=pop_knownctxlocexpectedinpush_known(Someloc)F64|BinOp(I32op)->let*()=pop_knownctxlocI32inlet*()=pop_knownctxlocI32inpush_known(Someloc)(int_bin_op_typeI32op)|BinOp(I64op)->let*()=pop_knownctxlocI64inlet*()=pop_knownctxlocI64inpush_known(Someloc)(int_bin_op_typeI64op)|BinOp(F32op)->let*()=pop_knownctxlocF32inlet*()=pop_knownctxlocF32inpush_known(Someloc)(float_bin_op_typeF32op)|BinOp(F64op)->let*()=pop_knownctxlocF64inlet*()=pop_knownctxlocF64inpush_known(Someloc)(float_bin_op_typeF64op)|Add128|Sub128->let*()=pop_knownctxlocI64inlet*()=pop_knownctxlocI64inlet*()=pop_knownctxlocI64inlet*()=pop_knownctxlocI64inlet*()=push_known(Someloc)I64inpush_known(Someloc)I64|MulWide_->let*()=pop_knownctxlocI64inlet*()=pop_knownctxlocI64inlet*()=push_known(Someloc)I64inpush_known(Someloc)I64|I32WrapI64->let*()=pop_knownctxlocI64inpush_known(Someloc)I32|I64ExtendI32_->let*()=pop_knownctxlocI32inpush_known(Someloc)I64|F32DemoteF64->let*()=pop_knownctxlocF64inpush_known(Someloc)F32|F64PromoteF32->let*()=pop_knownctxlocF32inpush_known(Someloc)F64|ExternConvertAny->let*tt=pop_anyctxlocinletnullable=convert_operand_nullablectxloctt~typ:Anyinpush_known(Someloc)(Ref{nullable;typ=Extern})|AnyConvertExtern->let*tt=pop_anyctxlocinletnullable=convert_operand_nullablectxloctt~typ:Externinpush_known(Someloc)(Ref{nullable;typ=Any})|Folded(i,l)->let*()=instructionsctxlininstructionctxi|String(Someidx,s)->let*!ty,field=lookup_array_typectxidxin(matchfield.typwith|PackedI8->()|PackedI16->lets=Wax_utils.Ast.concat_descsinifnot(String.is_valid_utf_8s)thenError.string_not_unicodectx.modul.diagnostics~location:i.info|Value_->Error.string_array_requiredctx.modul.diagnostics~location:i.info);push~source:(exact_ref_sourcectxidx)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExacttyelseTypety);})|String(None,_)->leti=string_type_referencectx.modul.typesinletcomptype=Ast.Text.Array{mut=true;typ=PackedI8}inpush~source:(Inline_refcomptype)(Someloc)(Ref{nullable=false;typ=(ifallocation_is_exactctxthenExactielseTypei);})|Char_->push_known(Someloc)I32(* Conditional annotations are spliced out by [specialize] before a
configuration is validated, so none can remain at this point. *)|If_annotation_->assertfalse(* Wraps {!instruction_core} to feed the editor type sink (§ [recorded_types]).
Two adjustments, both no-ops when the sink is off:
- a folded instruction [(op … operands)] reads as one unit, so the type its
head produces is relocated from the operator token to the whole folded
span; nested operands keep their own (smaller) folded spans, so hovering an
operand still shows its own type;
- an instruction that leaves nothing on the stack ([drop], [local.set],
[nop], [br], a call to a void function, …) records a void marker at its
span, so hover shows nothing there rather than falling through to the
enclosing instruction's type. *)andinstructionctxist=match!recorded_typeswith|None->instruction_corectxist|Somer->(matchi.descwith|Folded(head,_)->letst',()=instruction_corectxistin(* The head is validated last, so its entries are at the front. Pop
them off the operator span and re-record them at the folded span,
keeping each entry's configuration index. *)letrectakeacc=match!rwith|(l0,cfg,t)::tlwhenl0=head.info->r:=tl;take((cfg,t)::acc)|_->accinList.iter(fun(cfg,t)->r:=(i.info,cfg,t)::!r)(take[]);(st',())|_->letbefore=!rinletst',()=instruction_corectxistinletproduced=(not(!r==before))&&match!rwith(l0,_,_)::_->l0=i.info|[]->falseinifnotproducedthenr:=(i.info,!sink_config,No_result)::!r;(st',()))andinstructionsctxl=matchlwith|[]->return()|i::r->let*()=instructionctxiininstructionsctxrandblockctxloclabel~used~param_source~result_source~br_source~params~results~br_paramsblock=with_empty_stackctx.modulloc(let*()=push_results~sink:false~loc~source:param_sourceparamsinlet*()=instructions{ctxwithcontrol_types=(Option.map(funl->l.Ast.desc)label,br_params,br_source,used)::ctx.control_types;}blockinpop_argsctx~kind:`Outputloc~source:result_sourceresults)(*** Constant expressions ***)letreccheck_constant_instructionctx(i:_Ast.Text.instr)=matchi.descwith|GlobalGetidx->(* Resolved silently: an unbound global is reported by the stack
validation of this same expression (via [get_global]). *)let*?ty,_=Sequence.findctx.globalsidxinifty.mutthenError.non_constant_globalctx.diagnostics~location:idx.infoidx|RefFunci->(* Record the referenced function by INDEX, not by its type: a ref.func in
a body is valid only if that SAME function occurs outside any body, so
keying by type would wrongly accept any other same-typed function.
Resolved silently: an unbound function is reported by the stack
validation (via [get_function]). *)let*?_=Sequence.findctx.functionsiinHashtbl.replacectx.refs(Sequence.get_indexctx.functionsi)()|RefNull_|StructNew_|StructNewDefault_|StructNewDesc_|StructNewDefaultDesc_|ArrayNew_|ArrayNewDefault_|ArrayNewFixed_(* [cont.new] allocates a fresh continuation from a (constant) function
reference, so it is itself a constant expression. The stack-switching spec
and reference tools do not list it yet; this tracks the open spec PR. *)|ContNew_|RefI31|Const_|BinOp(I32(Add|Sub|Mul)|I64(Add|Sub|Mul))|ExternConvertAny|AnyConvertExtern|VecConst_|String_|Char_->()|Folded(i,l)->check_constant_instructionctxi;check_constant_instructionsctxl|Block_|Loop_|If_|TryTable_|Try_|Unreachable|Nop|Throw_|ThrowRef|ContBind_|Suspend_|Resume_|ResumeThrow_|ResumeThrowRef_|Switch_|Br_|Br_if_|Br_table_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Return|Call_|CallRef_|CallIndirect_|ReturnCall_|ReturnCallRef_|ReturnCallIndirect_|Drop|Select_|LocalGet_|LocalSet_|LocalTee_|GlobalSet_|Load_|LoadS_|Store_|StoreS_|Atomic_|AtomicFence|MemorySize_|MemoryGrow_|MemoryFill_|MemoryCopy_|MemoryInit_|DataDrop_|TableGet_|TableSet_|TableSize_|TableGrow_|TableFill_|TableCopy_|TableInit_|ElemDrop_|RefIsNull|RefAsNonNull|RefEq|RefTest_|RefCast_|RefCastDescEq_|RefGetDesc_|StructGet_|StructSet_|ArrayNewData_|ArrayNewElem_|ArrayGet_|ArraySet_|ArrayLen|ArrayFill_|ArrayCopy_|ArrayInitData_|ArrayInitElem_|I31Get_|UnOp_|Add128|Sub128|MulWide_|BinOp(F32_|F64_|I32(Div_|Rem_|And|Or|Xor|Shl|Shr_|Rotl|Rotr|Eq|Ne|Lt_|Gt_|Le_|Ge_)|I64(Div_|Rem_|And|Or|Xor|Shl|Shr_|Rotl|Rotr|Eq|Ne|Lt_|Gt_|Le_|Ge_))|I32WrapI64|I64ExtendI32_|F32DemoteF64|F64PromoteF32|VecBitselect|VecUnOp_|VecBinOp_|VecTest_|VecShift_|VecBitmask_|VecLoad_|VecStore_|VecLoadLane_|VecStoreLane_|VecLoadSplat_|VecExtract_|VecReplace_|VecSplat_|VecShuffle_|VecTernOp_->Error.constant_expression_requiredctx.diagnostics~location:i.info(* Spliced out by [specialize] before validation; cannot occur here. *)|If_annotation_->assertfalseandcheck_constant_instructionsctxl=List.iter(funi->check_constant_instructionctxi)l(* Forward reference to [lint_body] (defined below), so [constant_expression] can
lint a constant expression with the same walk used for function bodies. *)letlint_constant_expr=ref(fun__->())letconstant_expressionctx~location~expected_sourcetyexpr=check_constant_instructionsctxexpr;with_empty_stackctxlocation(letctx={locals=Sequence.make"local";control_types=[];return_types=[||];return_source=[||];modul=ctx;initialized_locals=IntSet.empty;used_locals=refIntSet.empty;poisoned_locals=IntSet.empty;label_decls=ref[];}inlet*()=instructionsctxexprin(* Lint the constant expression too (a data/elem offset, a global or field
initializer): the Wax typer lints these, so without this the wat/wasm
side misses a redundant [0 + 42] offset the Wax side reports. Via a
forward reference, as [lint_body] is defined below. *)ifctx.modul.warn_unusedthen!lint_constant_exprctxexpr;popctxlocation~expected_sourcety)(*** Type registration and the module environment ***)letadd_typedctxty=Array.iteri(funie->letlabel,(sub:Ast.Text.subtype)=e.Ast.descin(* These forward references are placeholders during the rec group's own
resolution and are replaced (or dropped) below; the composite type is
not consulted meanwhile, but carrying it keeps the field total. *)Hashtbl.replacectx.index_mapping(Uint32.of_int(ctx.last_index+i))(Types.Reci,[],sub.typ,Somee);Option.iter(funlabel->Hashtbl.replacectx.label_mappinglabel.Ast.desc(Types.Reci,[],sub.typ,Somee))label)ty;matchrectypedctxtywith|None->(* Resolution of this rec group failed. Drop the placeholder mappings but
poison these text indices / names, and still advance [last_index]: later
type definitions keep their positions and numeric references stay
aligned, while a reference to a poisoned member resolves to [None]
silently (the real error was reported by [rectype]). *)Array.iteri(funie->letlabel=fste.Ast.descinletidx=Uint32.of_int(ctx.last_index+i)inHashtbl.removectx.index_mappingidx;Hashtbl.replacectx.poisoned_indexidx();Option.iter(funlabel->Hashtbl.removectx.label_mappinglabel.Ast.desc;Hashtbl.replacectx.poisoned_labellabel.Ast.desc())label)ty;ctx.last_index<-ctx.last_index+Array.lengthty|Somety'->(* Well-formedness of [descriptor] / [describes] clauses, which must link
two struct types within the same recursion group. In [ty'] a [Rec]
reference names a member of this group; a [Def] denotes an
already-defined type outside it. *)Array.iteri(funi(sub:Types.Normalized.subtype)->letlocation=ty.(i).Ast.infoin(matchsub.descriptorwith|None->()|Some(Def_)->Error.descriptor_outside_rec_groupd~location~described:false|Some(Recpos)->((* This type is described by [ty'.(pos)]; that descriptor must
describe this type back, and share its finality: an open type
whose descriptor is final (or the reverse) could never be
extended, since a subtype would need a descriptor extending a
final one. Reported here only, on the described type, so the
reciprocal pair yields a single error. *)matchty'.(pos).describeswith|Some(Reco)wheno=i->ifsub.final<>ty'.(pos).finalthenError.descriptor_finality_mismatchd~location|_->Error.descriptor_not_reciprocald~location~described:false));(matchsub.describeswith|None->()|Some(Def_)->Error.descriptor_outside_rec_groupd~location~described:true|Some(Recpos)->(ifpos>=ithenError.forward_use_of_describedd~location;(* This type is the descriptor of [ty'.(pos)], which must name this
type as its descriptor. *)matchty'.(pos).descriptorwith|Some(Recdd)whendd=i->()|_->Error.descriptor_not_reciprocald~location~described:true));if(sub.descriptor<>None||sub.describes<>None)&&matchsub.typwithStruct_->false|_->truethenError.descriptor_not_structd~location~described:(sub.describes<>None))ty';leti'=Types.add_rectypectx.typesty'inArray.iteri(funie->letlabel,typ=e.Ast.descinletfields=match(typ:Ast.Text.subtype).typwith|Structfields->Array.mapi(funie->matchfste.Ast.descwith|Someid->Some(id.Ast.desc,i)|None->None)fields|>Array.to_list|>List.filter_mapFun.id|_->[]inHashtbl.replacectx.index_mapping(Uint32.of_int(ctx.last_index+i))(Types.Def(Types.Id.addi'i),fields,typ.typ,Somee);letdef_idx=letdesc=matchlabelwith|Somel->Ast.Text.Idl.Ast.desc|None->Ast.Text.Num(Uint32.of_int(ctx.last_index+i))in{Ast.desc;info=e.Ast.info}inletcont_ref=match(typ:Ast.Text.subtype).typwith|Contr->Somer|Func_|Struct_|Array_->NoneinHashtbl.replacectx.type_defs(ctx.last_index+i)(def_idx,cont_ref);Option.iter(funnode->Hashtbl.replacectx.descriptor_source(Types.Id.addi'i)node)(typ:Ast.Text.subtype).descriptor;Option.iter(funlabel->Hashtbl.replacectx.label_mappinglabel.Ast.desc(Types.Def(Types.Id.addi'i),fields,typ.typ,Somee))label)ty;ctx.last_index<-ctx.last_index+Array.lengthtyletregister_exportsctxlst=List.iter(fun(name:Ast.Text.name)->matchHashtbl.find_optctx.exportsname.descwith|Someprev_loc->Error.duplicated_exportctx.diagnostics~location:name.info~prev_locname|None->Hashtbl.addctx.exportsname.descname.info)lstletlimitsctxkind{Ast.desc={mi;ma;address_type;page_size_log2;shared};info=location;}max_fn=(matchpage_size_log2with|None|Some(0|16)->()|Some_->Error.invalid_page_sizectx.diagnostics~location);(* A shared memory must declare a maximum size. *)ifshared&&ma=NonethenError.shared_memory_without_maxctx.diagnostics~location;letmax=max_fnaddress_typepage_size_log2inmatchmawith|None->ifUint64.comparemimax>0thenError.limit_too_largectx.diagnostics~locationkindmax|Somema->ifUint64.comparemima>0thenError.limit_mismatchctx.diagnostics~locationkind;ifUint64.comparemamax>0thenError.limit_too_largectx.diagnostics~locationkindmax(* The maximum number of pages: [min(2^bits - 1, 2^(bits - p))] where [bits] is
32 (i32) or 64 (i64) and [2^p] is the page size (default 2^16). The byte span
gives the [2^(bits - p)] term; the [2^bits - 1] cap bounds the page index
itself (so e.g. a page size of 1 allows 2^32 - 1 pages, not 2^32). With the
default page size this is the familiar 65536 / 2^48 pages. *)letmax_memory_sizeaddress_typepage_size_log2=letp=matchpage_size_log2withNone->16|Somep->pinletbits,index_max=matchaddress_typewith|`I32->(32,Uint64.of_string"0xffff_ffff")|`I64->(64,Uint64.of_string"0xffff_ffff_ffff_ffff")inlete=bits-pinletby_page=ife>=64thenindex_maxelseife<=0thenUint64.zeroelseUint64.of_int64(Int64.shift_left1Le)inifUint64.compareindex_maxby_page<=0thenindex_maxelseby_pageletmax_table_sizeaddress_type_page_size_log2=matchaddress_typewith|`I32->Uint64.of_string"0xffff_ffff"|`I64->Uint64.of_string"0xffff_ffff_ffff_ffff"(* Collect the implicit function types denoted by inline signatures (function
and tag definitions, imports, block types and [call_indirect]). Following
the text format, such a type reuses a structurally-equal type if one already
exists, and is otherwise appended to the end of the type index space, where
it can be referred to by index. We must do this before resolving any type
reference so that those indices are bound, and before computing the
subtyping information so that it covers every type. Relies on
{!Types.add_rectype} deduplicating: a [typeuse] encountered later during
validation then resolves to the type collected here instead of growing the
type table. Diagnostics are muted: a signature that does not resolve is
skipped here and reported by the pass that owns the construct (an import or
tag by [build_initial_env], a function by [functions], a block type or
[call_indirect] by the body validation). *)letcollect_implicit_typesdctxfields=letd=muteddinletcollectsign=let>@ft=n_functypedctxsigninletbefore=Types.last_indexctx.typesinletidx=Types.add_rectypectx.types[|{typ=Funcft;supertype=None;final=true;descriptor=None;describes=None;};|]inifTypes.last_indexctx.types>beforethen(Hashtbl.replacectx.index_mapping(Uint32.of_intctx.last_index)(Types.Defidx,[],Funcsign,None);ctx.last_index<-ctx.last_index+1)inletcollect_instr(i:_Ast.Text.instr)=matchi.descwith|Block{typ=Some(Typeuse(None,Someft));_}|Loop{typ=Some(Typeuse(None,Someft));_}|If{typ=Some(Typeuse(None,Someft));_}|Try{typ=Some(Typeuse(None,Someft));_}|TryTable{typ=Some(Typeuse(None,Someft));_}->collectft|CallIndirect(_,(None,Someft))|ReturnCallIndirect(_,(None,Someft))->collectft|If_annotation_->(* Spliced out by [specialize] before validation; cannot occur here. *)assertfalse|_->()in(* The canonical walk descends into every nesting instruction, branch hints
included, so inline types buried there are interned like any other. *)letcollect_instrsl=List.iter(Ast_utils.iter_instrcollect_instr)lin(* A defined function's own signature and the inline block types in its body are
references *it* makes, so attribute them to it — otherwise interning them here
would make a dead function's types look externally referenced, and the Wax
typer (which resolves them per function) would disagree. The counter mirrors
the function-index allocation of [build_initial_env] over this same expanded
list. An import's or tag's signature stays a root: neither is reached through
a function. *)letindex=ref0inList.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->(matchfield.descwith|Func_->ctx.origin<-From_function!index|_->());(matchfield.descwith|Import{desc=Func{typ=None,Somesign;_};_}|Import{desc=Tag(None,Somesign);_}|Func{typ=None,Somesign;_}|Tag{typ=None,Somesign;_}->collectsign|_->());(matchfield.descwith|Func{instrs;_}->collect_instrsinstrs|_->());ctx.origin<-Root;matchfield.descwith|Import{desc=Func_;_}|Func_->incrindex|_->())(List.concat_mapAst_utils.expand_import_groupfields)letbuild_initial_envctxfields=List.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->matchfield.descwith|Import{id;desc;exports;module_=_;name=_}->(register_exportsctxexports;(* Record the import as an [unused-import] candidate; an inline export
re-exports it, so mark it used. *)letlocation=matchidwithSomeid->id.Ast.info|None->field.infoinletmarkusedidx=ifexports<>[]thenHashtbl.replaceusedidx()inmatchdescwith|Func{exact;typ=tu}->(letidx=Sequence.next_indexctx.functionsinmatchtypeusectx.diagnosticsctx.typestuwith|None->(* The typeuse did not resolve (reported above). Claim the
index anyway so later functions stay aligned; skip the
[unused-import] candidate for a definition that failed. *)Sequence.register_failedctx.functionsid|Somety->Sequence.registerctx.functionsid(ty,fsttu,typeuse_functypectx.typestu,exact);ctx.imported_functions<-(idx,id,location)::ctx.imported_functions;markctx.used_functionsidx)|Memorylim->limitsctx"memory"limmax_memory_size;letidx=Sequence.next_indexctx.memoriesinSequence.registerctx.memoriesidlim.desc;ctx.imported_memories<-(idx,id,location)::ctx.imported_memories;markctx.used_memoriesidx|Tabletyp->(limitsctx"table"typ.limitsmax_table_size;letidx=Sequence.next_indexctx.tablesinletsrc=Plain(Ast.Text.Reftyp.reftype)inmatchtabletypectx.diagnosticsctx.typestypwith|None->Sequence.register_failedctx.tablesid|Sometyp->Sequence.registerctx.tablesid(typ,src);ctx.imported_tables<-(idx,id,location)::ctx.imported_tables;markctx.used_tablesidx)|Globalty->(letidx=Sequence.next_indexctx.globalsinletsrc=Plainty.typinmatchglobaltypectx.diagnosticsctx.typestywith|None->Sequence.register_failedctx.globalsid|Somety->Sequence.registerctx.globalsid(ty,src);ctx.imported_globals<-(idx,id,location)::ctx.imported_globals;markctx.used_globalsidx)|Tagtu->(letidx=Sequence.next_indexctx.tagsinmatchtypeusectx.diagnosticsctx.typestuwith|None->Sequence.register_failedctx.tagsid|Somety->letsign=typeuse_functypectx.typestuin(* A tag's function type is deliberately not required to have
empty results: the stack-switching proposal uses tags with
result types (for [suspend] / [resume]), so the
exception-handling restriction to no results is not
enforced. *)Sequence.registerctx.tagsid(ty,sign);ctx.imported_tags<-(idx,id,location)::ctx.imported_tags;markctx.used_tagsidx))|Func{id;typ;exports;instrs=_;locals=_;priority=_}->((* A function's own signature is a reference *it* makes, so attribute it
to the index this definition is about to claim rather than letting a
dead function's type look externally referenced. *)ctx.types.origin<-From_function(Sequence.next_indexctx.functions);(* Resolved with muted diagnostics: the [functions] pass resolves
this typeuse again and owns its errors. *)letresolved=typeuse(mutedctx.diagnostics)ctx.typestypinctx.types.origin<-Root;matchresolvedwith|None->(* The typeuse did not resolve. Claim the index (later functions
stay aligned) but do not record an [unused-field] candidate. *)Sequence.register_failedctx.functionsid|Somety->letsign=typeuse_functypectx.typestypin(* A module-defined function has exactly its declared type, so a
reference to it is exact — but exact reference types are part of
custom-descriptors, so without the feature it is the plain
inexact reference, as before the proposal. See
[allocation_is_exact]: the Wax typer gates the same decision, and
the two must agree. *)letexact=Wax_utils.Feature.is_enabledctx.types.featuresWax_utils.Feature.Custom_descriptorsinletidx=Sequence.next_indexctx.functionsinSequence.registerctx.functionsid(ty,fsttyp,sign,exact);(* Record it as an [unused-field] candidate; an inline export makes
it externally reachable, so mark it used. *)letlocation=matchidwithSomeid->id.Ast.info|None->field.infoinctx.defined_functions<-(idx,id,location)::ctx.defined_functions;ifexports<>[]thenHashtbl.replacectx.used_functionsidx())|Tag{id;typ;exports}->(letidx=Sequence.next_indexctx.tagsinmatchtypeusectx.diagnosticsctx.typestypwith|None->Sequence.register_failedctx.tagsid|Somety->letsign=typeuse_functypectx.typestypin(* A tag's function type is deliberately not required to have empty
results: the stack-switching proposal uses tags with result
types (for [suspend] / [resume]), so the exception-handling
restriction to no results is not enforced. *)register_exportsctxexports;Sequence.registerctx.tagsid(ty,sign);letlocation=matchidwithSomeid->id.Ast.info|None->field.infoinctx.defined_tags<-(idx,id,location)::ctx.defined_tags;ifexports<>[]thenHashtbl.replacectx.used_tagsidx())|_->())(List.concat_mapAst_utils.expand_import_groupfields)letcheck_type_definitionsctx=(* This sweeps every type index to check the definition's own well-formedness,
re-resolving each one; those resolutions are not source references, so
recording them would make every type look used. *)ctx.types.origin<-Ignored;fori=0toctx.types.last_index-1doletdef_idx,cont_ref=Option.value~default:(Ast.no_loc(Ast.Text.Num(Uint32.of_inti)),None)(Hashtbl.find_optctx.types.type_defsi)inletlocation=def_idx.Ast.infoinlet>@gidx,_,_,_=get_type_infoctx.diagnosticsctx.types(Ast.no_loc(Ast.Text.Num(Uint32.of_inti)))inletty=Types.get_subtypectx.subtyping_info(def_idgidx)in(* A continuation type must wrap a function type. *)(matchty.typwith|Contft->(match(Types.get_subtypectx.subtyping_infoft).typwith|Func_->()|Struct_|Array_|Cont_->(* Name the wrapped type as the source wrote it: the resolved index
[ft] is canonical, so identical types would otherwise be
indistinguishable. A [Cont] type is only ever registered by
[add_type], which records its wrapped-type source, so [cont_ref]
is necessarily [Some] here. *)letwrapped=matchcont_refwithSomer->r|None->assertfalseinError.expected_func_typectx.diagnostics~locationwrapped)|Func_|Struct_|Array_->());let*?j=ty.supertypeinletty'=Types.get_subtypectx.subtyping_infojinletinvalid()=Error.invalid_subtypectx.diagnostics~locationinifty'.finaltheninvalid()elsebegin(match(ty.typ,ty'.typ)with|Func{params;results},Func{params=params';results=results'}->ifArray.lengthparams<>Array.lengthparams'||Array.lengthresults<>Array.lengthresults'||not(Array.for_all2(funpp'->Types.val_subtypectx.subtyping_infop'p)paramsparams'&&Array.for_all2(funrr'->Types.val_subtypectx.subtyping_inforr')resultsresults')theninvalid()|Structfields,Structfields'->ifArray.lengthfields'>Array.lengthfields||not(Array.for_all2(field_subtypectx.subtyping_info)(Array.subfields0(Array.lengthfields'))fields')theninvalid()|Arrayfield,Arrayfield'->ifnot(field_subtypectx.subtyping_infofieldfield')theninvalid()|Contft,Contft'->ifnot(Types.heap_subtypectx.subtyping_info(Typeft)(Typeft'))theninvalid()|Func_,(Struct_|Array_|Cont_)|Struct_,(Func_|Array_|Cont_)|Array_,(Func_|Struct_|Cont_)|Cont_,(Func_|Struct_|Array_)->Error.supertype_mismatchctx.diagnostics~location);(* A subtype has a descriptor iff its supertype does, and the subtype's
descriptor must be a subtype of the supertype's. *)(match(ty.descriptor,ty'.descriptor)with|None,None->()|Someds,Somedp->ifnot(Types.heap_subtypectx.subtyping_info(Typeds)(Typedp))theninvalid()|Some_,None|None,Some_->invalid());(* A subtype has a described type iff its supertype does, and the
subtype's described type must be a subtype of the supertype's. *)match(ty.describes,ty'.describes)with|None,None->()|Someos,Someop->ifnot(Types.heap_subtypectx.subtyping_info(Typeos)(Typeop))theninvalid()|Some_,None|None,Some_->invalid()enddone;ctx.types.origin<-Root(*** Module-field validation passes ***)lettables_and_memoriesctxfields=List.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->(* As for a function or global, record the definition as an [unused-field]
candidate, with an inline export marking it externally reachable. *)letreport_locationid=matchidwithSomeid->id.Ast.info|None->field.infoinmatchfield.descwith|Memory{id;limits=lim;init=_;exports}->limitsctx"memory"limmax_memory_size;letidx=Sequence.next_indexctx.memoriesinSequence.registerctx.memoriesidlim.desc;ctx.defined_memories<-(idx,id,report_locationid)::ctx.defined_memories;ifexports<>[]thenHashtbl.replacectx.used_memoriesidx();register_exportsctxexports|Table{id;typ;init;exports}->(limitsctx"table"typ.limitsmax_table_size;letidx=Sequence.next_indexctx.tablesinletsrc=Plain(Ast.Text.Reftyp.reftype)inmatchtabletypectx.diagnosticsctx.typestypwith|None->Sequence.register_failedctx.tablesid|Sometyp->(matchinitwith(* An inline element list lowers to an ACTIVE element segment, which
fills the table at instantiation. The table itself is still
default-initialized, so the form constrains the element type
exactly as no initializer at all does. *)|Init_default|Init_segment_->ifnottyp.reftype.nullablethenError.non_nullable_table_typectx.diagnostics~location:field.info(*ZZZ*)|Init_expre->constant_expressionctx~location:field.info~expected_source:src(Reftyp.reftype)e);Sequence.registerctx.tablesid(typ,src);ctx.defined_tables<-(idx,id,report_locationid)::ctx.defined_tables;ifexports<>[]thenHashtbl.replacectx.used_tablesidx();register_exportsctxexports)|_->())fieldsletglobalsctxfields=List.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->matchfield.descwith|Global{id;typ;init;exports}->(letsrc=Plaintyp.typinmatchglobaltypectx.diagnosticsctx.typestypwith|None->Sequence.register_failedctx.globalsid|Sometyp->constant_expressionctx~location:field.info~expected_source:srctyp.typinit;letidx=Sequence.next_indexctx.globalsinSequence.registerctx.globalsid(typ,src);(* Record it as an [unused-field] candidate; an inline export makes
it externally reachable, so mark it used. *)letlocation=matchidwithSomeid->id.Ast.info|None->field.infoinctx.defined_globals<-(idx,id,location)::ctx.defined_globals;(* A [mut] global is also an [unnecessary-mut] candidate. *)iftyp.mutthenctx.mutable_globals<-(idx,id,location)::ctx.mutable_globals;ifexports<>[]thenbeginHashtbl.replacectx.used_globalsidx();(* An exported global is assignable by the host, so it counts as
assigned for [unnecessary-mut]. *)Hashtbl.replacectx.assigned_globalsidx()end;register_exportsctxexports)|String_global{id;typ;init}->(* A named array type is honoured (and must be an i8/i16 array, like
any string); with none, the global takes the default [<string>]
([mut i8]) type. *)letty,src=matchtypwith|None->(string_type_referencectx.types,Inline_ref(Ast.Text.Array{mut=true;typ=PackedI8}))|Someidx->(matchresolve_type_indexctx.diagnosticsctx.typesidxwith|None->(string_type_referencectx.types,Inline_ref(Ast.Text.Array{mut=true;typ=PackedI8}))|Somety->(match(Types.get_subtypectx.subtyping_infoty).typwith|Array{typ=PackedI8;_}->()|Array{typ=PackedI16;_}->lets=Wax_utils.Ast.concat_descinitinifnot(String.is_valid_utf_8s)thenError.string_not_unicodectx.diagnostics~location:idx.info|Array{typ=Value_;_}->Error.string_array_requiredctx.diagnostics~location:idx.info|_->Error.expected_array_typectx.diagnostics~location:idx.infoidx);(ty,named_ref_sourceidx))inlettyp={mut=false;typ=Ref{nullable=false;typ=Typety}}inSequence.registerctx.globals(Someid)(typ,src)|_->())fieldsletsegmentsctxfields=List.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->(* An active or declarative segment runs at instantiation, so it is used
whatever the bodies do; only a passive one is an [unused-field]
candidate, reachable solely through [memory.init]/[table.init] and
[data.drop]/[elem.drop]. *)matchfield.descwith|Memory{init;_}->let*?_=initin(* The implicit segment of a memory's inline data is active. *)Hashtbl.replacectx.used_data(Sequence.next_indexctx.data)();Sequence.registerctx.dataNone()|Data{id;init=_;mode}->letidx=Sequence.next_indexctx.datainletlocation=matchidwithSomeid->id.Ast.info|None->field.infoin(matchmodewith|Passive->ctx.defined_data<-(idx,id,location)::ctx.defined_data|Active(i,e)->Hashtbl.replacectx.used_dataidx();mark_referencectx(funi->Ref_memoryi)ctx.used_memoriesctx.memoriesi;let*?limits=Sequence.getctx.diagnosticsctx.memoriesiinletaty=address_type_to_valtypelimits.address_typeinconstant_expressionctx~location:field.info~expected_source:(source_of_valtypeaty)atye);Sequence.registerctx.dataid()|Table{typ;init;_}->(matchinitwith|Init_default|Init_expr_->()|Init_segmentlst->(letsrc=Plain(Ast.Text.Reftyp.reftype)in(* The implicit segment of a table's inline element list is
active. *)Hashtbl.replacectx.used_elem(Sequence.next_indexctx.elem)();matchreftypectx.diagnosticsctx.typestyp.reftypewith|None->Sequence.register_failedctx.elemNone|Sometyp->List.iter(fune->constant_expressionctx~location:field.info~expected_source:src(Reftyp)e)lst;Sequence.registerctx.elemNone(typ,src)))|Elem{id;typ;init;mode}->(letelem_source=Plain(Ast.Text.Reftyp)inletidx=Sequence.next_indexctx.eleminletlocation=matchidwithSomeid->id.Ast.info|None->field.infoinmatchreftypectx.diagnosticsctx.typestypwith|None->Sequence.register_failedctx.elemid|Sometyp->(matchmodewith|Passive->ctx.defined_elem<-(idx,id,location)::ctx.defined_elem|Declare->Hashtbl.replacectx.used_elemidx()|Active(i,e)->Hashtbl.replacectx.used_elemidx();mark_referencectx(funi->Ref_tablei)ctx.used_tablesctx.tablesi;let*?tabletype,table_source=Sequence.getctx.diagnosticsctx.tablesiinifnot(Types.val_subtypectx.subtyping_info(Reftyp)(Reftabletype.reftype))thenError.elem_segment_type_mismatchctx.diagnostics~location:field.info~elem_source~table_source;letaty=address_type_to_valtypetabletype.limits.address_typeinconstant_expressionctx~location:field.info~expected_source:(source_of_valtypeaty)atye);(* A DECLARATIVE segment installs nothing and runs nothing: it
exists so that a [ref.func] elsewhere validates, so the
references in its init expressions are not roots. The function
is reachable exactly when that other [ref.func] is — otherwise a
function only its own dead body takes a reference of would look
live here while the Wax typer, whose surface leaves the segment
implicit, correctly reports it dead (a lint-parity finding from
the wasm-smith campaign). An ACTIVE segment does install into a
table, and a PASSIVE one can be [table.init]ed, so both keep
rooting theirs. *)letouter=ctx.types.origininifmode=Declarethenctx.types.origin<-Ignored;List.iter(fune->constant_expressionctx~location:field.info~expected_source:elem_source(Reftyp)e)init;ctx.types.origin<-outer;Sequence.registerctx.elemid(typ,elem_source))|_->())fields(* An exported function is referenceable by [ref.func] (it is in the module's
[refs] set), like a function named in a global or element segment. Record
exported functions by index BEFORE bodies are validated — the dedicated
[exports] pass runs after [functions], too late for the [ref.func] check.
Both a standalone export field and an inline export on a function count; in a
binary all exports are standalone (the export section), inline being WAT
sugar. Function indices are counted positionally, imports first (their order
is enforced by [check_import_order]), matching how [build_initial_env]
registers them. *)letdeclared_func_exportsctxfields=letfi=ref0inList.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->matchfield.descwith|Import{desc=Func_;exports;_}->ifexports<>[]thenHashtbl.replacectx.refs!fi();incrfi|Import_->()|Func{exports;_}->ifexports<>[]thenHashtbl.replacectx.refs!fi();incrfi|Export{kind=Func;index;_}->Option.iter(funi->Hashtbl.replacectx.refsi())(Sequence.get_index_optctx.functionsindex)|_->())(List.concat_mapAst_utils.expand_import_groupfields)(*** Correctness lints over a function body (see {!Wax_utils.Warning}) ***)(* A constant operand tracked while linting. Crossing any non-constant
instruction clears the stack, so its entries mirror the most recent run of
constants on the real operand stack — the top is a binary operator's right
operand (its last-pushed value). Folded operands flatten into the same push
sequence, so folded and flat forms are handled alike. *)(* A value tracked on the lint stack: a known integer/float constant, a bare
[local.get]/[global.get] read (tracked by resolved index so two reads of the
same variable can be recognised as identical operands), or some other value
produced with no side effect and no trap ([LPure]). Constants and reads are
also pure. Anything else clears the stack. *)typelint_val=|LIntofint64|LFloatoffloat|LLocalofint|LGlobalofint|LPureletlint_int_values=lets=String.concat""(String.split_on_char'_'s)inmatchInt64.of_string_optswith|Some_asr->r(* An unsigned decimal past [2^63] (e.g. a shift count of [18446744073709551615]
= -1) overflows a signed parse; read it unsigned so it is still tracked. *)|None->Int64.of_string_opt("0u"^s)letlint_float_values=lets=String.concat""(String.split_on_char'_'s)inletbody=ifString.lengths>0&&(s.[0]='+'||s.[0]='-')thenString.subs1(String.lengths-1)elsesinifString.lengthbody>=3&&String.equal(String.subbody03)"nan"thenSomeFloat.nanelsefloat_of_string_opts(* Round an [f64] to the nearest representable [f32] (the [f32.demote_f64] the
runtime applies), via the single-precision bit layout. *)letround_to_f32f=Int32.float_of_bits(Int32.bits_of_floatf)(* Whether a trapping (toward-zero) float-to-integer conversion of [f] to the
given target/signage would trap: NaN/infinite, or out of range. *)letfloat_conversion_trapstargetsignagef=ifnot(Float.is_finitef)thentrueelselett=Float.truncfinletpow2n=Float.ldexp1.ninmatch(target,signage)with|`I32,Ast.Signed->t<-.pow231||t>=pow231|`I32,Ast.Unsigned->t<0.||t>=pow232|`I64,Ast.Signed->t<-.pow263||t>=pow263|`I64,Ast.Unsigned->t<0.||t>=pow264(* Report the constant-operand lints (shift count, division/remainder by zero,
out-of-range trapping conversion, tautological unsigned comparison, constant
condition, discarded constant) and dead code over a function body. *)letlint_bodyctxinstrs=letdiagnostics=ctx.modul.diagnosticsin(* The number of field operands of a [struct.new] on the type at [idx], or
[None] if the index does not resolve to a struct type. Looks the type up
silently (the body has already been validated, so a bad index has already
been reported — re-reporting here would duplicate the diagnostic). *)letstruct_arityidx=letm=ctx.modulinmatchtrymatchidx.Ast.descwith|Ast.Text.Numx->Some(Hashtbl.findm.types.index_mappingx)|Ast.Text.Idid->Some(Hashtbl.findm.types.label_mappingid)withNot_found->Nonewith|Some(gidx,_,_,_)->(match(Types.get_subtypem.subtyping_info(def_idgidx)).typwith|Structfields->Some(Array.lengthfields)|Func_|Array_|Cont_->None)|None->Nonein(* Two operands that are the same bare local/global read (with nothing impure
in between — an assignment would have cleared the stack). *)letsame_readab=match(a,b)with|LLocali,LLocalj|LGlobali,LGlobalj->i=j|_->falseinletcheck_int_binop(op:_Ast.Text.instr)(o:Ast.int_bin_op)widthst=lettautvalue=Error.tautological_comparisondiagnostics~location:op.info~valueinletno_effect()=Error.redundant_operationdiagnostics~location:op.info(Wax_utils.Message.text"This operation has no effect on its result.")inletalwaysv=Error.redundant_operationdiagnostics~location:op.infoWax_utils.Message.((text"This operation always yields"++int64v)^^text".")in(* [st] is [right :: left :: _]. The absorbing and identical-operand cases
require both operands tracked (so the whole expression is effect-free): a
constant on one side plus a second entry ([_ :: _]) for the other. The
no-effect identity cases do not — see their note below. *)match(o,st)with|(Shl|Shr_),LIntn::_whenInt64.unsigned_comparen(Int64.of_intwidth)>=0->Error.shift_overflowdiagnostics~location:op.info~widthn|(Div_|Rem_),LInt0L::_->Error.division_by_zerodiagnostics~location:op.info(* An unsigned comparison against a constant zero, on either side. *)|LtAst.Unsigned,LInt0L::_->tautfalse(* a <u 0 *)|GeAst.Unsigned,LInt0L::_->tauttrue(* a >=u 0 *)|GtAst.Unsigned,_::LInt0L::_->tautfalse(* 0 >u a *)|LeAst.Unsigned,_::LInt0L::_->tauttrue(* 0 <=u a *)(* Two identical integer operands: [a == a]/[a <= a]/[a >= a] hold, the
strict and inequality forms do not. All comparisons here are integer (the
float ones are a different opcode), so there is no NaN caveat. *)|(Eq|Le_|Ge_),a::b::_whensame_readab->tauttrue|(Ne|Lt_|Gt_),a::b::_whensame_readab->tautfalse(* Arithmetic identities: the result is the other operand unchanged. Unlike
the absorbing cases below, the *identity* constant makes the operation a
no-op whatever the other operand is — traps and effects of that operand
are preserved either way — so an identity on the top of the stack fires
even when the other operand is not tracked (an impure producer, e.g. a
call, that cleared the stack). This matches the Wax linter, which reports
these structurally. A left-identity ([0 + x]) still needs the top (its
[x]) tracked to be seen at all. *)|Add,(LInt0L::_|_::LInt0L::_)->no_effect()(* x + 0 *)|(Sub|Shl|Shr_|Rotl|Rotr),LInt0L::_->no_effect()(* x - 0, x << 0, … *)|Mul,(LInt1L::_|_::LInt1L::_)->no_effect()(* x * 1 *)|Div_,LInt1L::_->no_effect()(* x / 1 *)|(Or|Xor),(LInt0L::_|_::LInt0L::_)->no_effect()(* x | 0, x ^ 0 *)|(And|Or),a::b::_whensame_readab->no_effect()(* x & x, x | x *)(* Absorbing operands: the result is a constant, independent of the other. *)|Mul,(LInt0L::_::_|_::LInt0L::_)->always0L(* x * 0 *)|And,(LInt0L::_::_|_::LInt0L::_)->always0L(* x & 0 *)|Rem_,LInt1L::_::_->always0L(* x % 1 *)|(Sub|Xor),a::b::_whensame_readab->always0L(* x - x, x ^ x *)|_->()in(* Check the operator [op] against the constant stack [st] (top = right
operand / condition). *)letcheck_op(op:_Ast.Text.instr)st=matchop.descwith|BinOp(I32o)->check_int_binopopo32st|BinOp(I64o)->check_int_binopopo64st|UnOp(I32(Trunc(_,sign)))->(matchstwith|LFloatf::_whenfloat_conversion_traps`I32signf->Error.conversion_out_of_rangediagnostics~location:op.info|_->())|UnOp(I64(Trunc(_,sign)))->(matchstwith|LFloatf::_whenfloat_conversion_traps`I64signf->Error.conversion_out_of_rangediagnostics~location:op.info|_->())|Br_if_|If_|Select_->(matchstwith|LIntn::_->Error.constant_conditiondiagnostics~location:op.info~value:(n<>0L)|_->())|Drop->(matchstwith|(LInt_|LFloat_|LLocal_|LGlobal_|LPure)::_->Error.unused_resultdiagnostics~location:op.info|_->())(* A self-assignment [x = x]: the value written is a fresh read of the same
variable, with nothing impure in between (which would have cleared it). *)|LocalSetidx->(match(st,Sequence.get_index_optctx.localsidx)with|LLocalj::_,Someiwheni=j->Error.redundant_operationdiagnostics~location:op.info(Wax_utils.Message.text"This assignment writes the local back to itself.")|_->())|GlobalSetidx->(match(st,Sequence.get_index_optctx.modul.globalsidx)with|LGlobalj::_,Someiwheni=j->Error.redundant_operationdiagnostics~location:op.info(Wax_utils.Message.text"This assignment writes the global back to itself.")|_->())|_->()in(* An instruction after which control does not fall through. *)letrecis_diverging(i:_Ast.Text.instr)=matchi.descwith|Br_|Br_table_|Return|Unreachable|ReturnCall_|ReturnCallRef_|ReturnCallIndirect_|Throw_|ThrowRef->true|Folded(op,_)->is_divergingop|_->falsein(* How an instruction affects the lint's purity tracking. The match is
exhaustive so a newly added instruction must be classified rather than
silently defaulting. *)letclassify(d:_Ast.Text.instr_desc)=matchdwith(* Effect-free, non-trapping operators. Every value on the lint stack is
pure by construction (impure/unhandled producers clear it), so the
results are pure exactly when the stack is deep enough for the operands —
[Pure (consumed, produced)] pops [consumed] operands and pushes [produced]
pure values. *)|Const_|LocalGet_|GlobalGet_|RefNull_|RefFunc_|MemorySize_|TableSize_|VecConst_|StructNewDefault_->`Pure(0,1)|UnOp(I32(Trunc_)|I64(Trunc_))->`Impure(* trapping float→int conversion *)(* [struct.new_default] with a descriptor takes just the descriptor
operand, so its arity is fixed at 1 like the other unary pure ops. *)|UnOp_|I32WrapI64|I64ExtendI32_|F32DemoteF64|F64PromoteF32|ExternConvertAny|AnyConvertExtern|RefIsNull|RefTest_|RefI31|VecUnOp_|VecTest_|VecBitmask_|VecExtract_|VecSplat_|ArrayNewDefault_|StructNewDefaultDesc_->`Pure(1,1)|BinOp(I32(Div_|Rem_)|I64(Div_|Rem_))->`Impure(* integer division/remainder may trap *)|BinOp_|RefEq|VecBinOp_|VecShift_|VecReplace_|VecShuffle_|ArrayNew_->`Pure(2,1)|Select_|VecTernOp_|VecBitselect->`Pure(3,1)(* Wide integer arithmetic: pure, but produces a two-limb result. *)|Add128|Sub128->`Pure(4,2)|MulWide_->`Pure(2,2)(* Allocations are effect-free and non-trapping (a dropped allocation is
dead code): [array.new_fixed] takes its element count as an explicit
immediate — unlike [struct.new], whose arity comes from the type — and
[struct.new_default]/[array.new]/[array.new_default] above have a fixed
arity too. *)|ArrayNewFixed(_,n)->`Pure(Uint32.to_intn,1)(* [struct.new] takes one operand per field; the arity comes from the type,
looked up the same way folding does. [struct.new_desc] adds a descriptor
operand. An unresolvable type falls through to [`Unhandled]. *)|StructNewidx->(matchstruct_arityidxwith|Somen->`Pure(n,1)|None->`Unhandled)|StructNewDescidx->(matchstruct_arityidxwith|Somen->`Pure(n+1,1)|None->`Unhandled)(* [nop] neither pops nor pushes, so it leaves the tracked stack unchanged
rather than clearing it. *)|Nop->`Neutral(* Has a side effect, transfers control, or may trap — never pure. *)|Unreachable|Throw_|ThrowRef|Br_|Br_if_|Br_table_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Return|Call_|CallRef_|CallIndirect_|ReturnCall_|ReturnCallRef_|ReturnCallIndirect_|ContNew_|ContBind_|Suspend_|Resume_|ResumeThrow_|ResumeThrowRef_|Switch_|LocalSet_|LocalTee_|GlobalSet_|Load_|LoadS_|Store_|StoreS_|Atomic_|AtomicFence|MemoryGrow_|MemoryFill_|MemoryCopy_|MemoryInit_|DataDrop_|TableGet_|TableSet_|TableGrow_|TableFill_|TableCopy_|TableInit_|ElemDrop_|RefAsNonNull|RefCast_|RefCastDescEq_|RefGetDesc_|StructGet_|StructSet_|ArrayNewData_|ArrayNewElem_|ArrayGet_|ArraySet_|ArrayLen|ArrayFill_|ArrayCopy_|ArrayInitData_|ArrayInitElem_|I31Get_|VecLoad_|VecStore_|VecLoadLane_|VecStoreLane_|VecLoadSplat_->`Impure(* Possibly pure, but not modelled here; clears the stack conservatively,
kept distinct from [`Impure] to flag as future work. The block forms
would need a whole-body purity analysis (and reasoning about branches
escaping the block) to be treated as a value producer; [Drop]/[Folded]
are handled structurally in [step]; the rest are Wax extensions. *)|Block_|Loop_|If_|TryTable_|Try_|Drop|Folded_|String_|Char_|If_annotation_->`Unhandledinletrecdrop_nnl=ifn<=0thenlelsematchlwith[]->[]|_::t->drop_n(n-1)tinletrecwalkinstrs=(* Dead code: after the first unconditional divergence, the next statement
(if any) can never be reached. Reported once, at that statement. *)letrecdead=function|a::(b::_asrest)->ifis_divergingathenError.dead_codediagnostics~location:b.info~related:[{Wax_utils.Diagnostic.location=a.info;message=Wax_utils.Message.text"Control never returns from here.";};]elsedeadrest|_->()indeadinstrs;ignore(List.fold_leftstep[]instrs:lint_vallist)andstepst(i:_Ast.Text.instr)=matchi.descwith|Const(I32s)|Const(I64s)->(matchlint_int_valueswithSomen->LIntn::st|None->[])|Const(F32s)|Const(F64s)->(matchlint_float_valueswithSomef->LFloatf::st|None->[])(* A bare read is pure; track its resolved index so a comparison of two reads
of the same variable is recognised as identical operands. *)|LocalGetidx->(matchSequence.get_index_optctx.localsidxwith|Somei->LLocali::st|None->LPure::st)|GlobalGetidx->(matchSequence.get_index_optctx.modul.globalsidxwith|Somei->LGlobali::st|None->LPure::st)|Folded(op,operands)->(* Folded form wraps every instruction, even a leaf constant, as
[Folded (Const n, [])]. Flatten to "operands then head": process the
operands, then the head as if it were the next flat instruction (so a
folded constant pushes, and a folded operator checks and clears). *)letst'=List.fold_leftstepstoperandsinstepst'op(* Propagate a constant float through a demote/promote so a trapping
conversion of an out-of-f32-range constant ([<big> as f32 as i32_u]) is
still caught: [f32.demote_f64] rounds to f32, [f64.promote_f32] is exact. *)|F32DemoteF64->(matchstwith|LFloatf::rest->LFloat(round_to_f32f)::rest|_::rest->LPure::rest|[]->[])|F64PromoteF32->(matchstwith|(LFloat_asv)::rest->v::rest|_::rest->LPure::rest|[]->[])|_->(check_opist;recursei;(* A pure operator whose operands are all pure yields pure results (so a
later [drop] of one is flagged too); [local.get]/[global.get] and
other zero-arity producers push a pure marker; anything else clears. *)matchclassifyi.descwith|`Pure(consumed,produced)whenList.lengthst>=consumed->List.initproduced(fun_->LPure)@drop_nconsumedst|`Neutral->st|`Pure_|`Impure|`Unhandled->[])andrecurse(i:_Ast.Text.instr)=matchi.descwith|Block{block;_}|Loop{block;_}|TryTable{block;_}->walkblock.desc|If{if_block;else_block;_}->walkif_block.desc;walkelse_block.desc|Try{block;catches;catch_all;_}->walkblock.desc;List.iter(fun(_,b)->walkb.Ast.desc)catches;Option.iter(funb->walkb.Ast.desc)catch_all|_->()in(* The [eager-select] lint. A [select] evaluates both of its value operands,
so a trapping or effectful operation among them runs even when the
condition picks the other one (the footgun behind Wax's [?:]). Reuses
[classify]'s purity table: a hazard is any [`Impure] operator except the
casts already covered by other lints. Only handles the folded form, where
each value operand is a distinct operand subtree — an unfolded [select]
leaves its operands on the flat stream, out of reach here. *)letis_control(d:_Ast.Text.instr_desc)=matchdwith|Block_|Loop_|If_|TryTable_|Try_|Select_|Br_|Br_if_|Br_table_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Return|Folded_->true|_->falseinletis_eager_hazard(d:_Ast.Text.instr_desc)=matchdwith(* Plain casts / trapping numeric conversions are reported by
[cast-always-fails] and [constant-trap]; exclude them so the hazard set
matches the Wax typer's [find_eager_hazard]. *)|UnOp(I32(Trunc_)|I64(Trunc_))|RefCast_->false|_->(matchclassifydwith`Impure->true|_->false)in(* The location of a hazard reached on the eagerly-evaluated spine of a
[select] value operand, descending through pure operators but stopping at
any nested control construct. *)letrechas_hazard(i:_Ast.Text.instr)=matchi.descwith|Folded(op,operands)->ifis_controlop.descthenNoneelseifis_eager_hazardop.descthenSomeop.infoelseList.find_maphas_hazardoperands|d->if(not(is_controld))&&is_eager_hazarddthenSomei.infoelseNoneinletrecsel_walk(i:_Ast.Text.instr)=(matchi.descwith|Folded(({desc=Select_;_}assel),[v1;v2;_cond])->List.iter(funoperand->matchhas_hazardoperandwith|Somelocation->Error.eager_selectdiagnostics~location~select:sel.info|None->())[v1;v2]|_->());matchi.descwith|Folded(op,operands)->List.itersel_walkoperands;sel_walkop|Block{block;_}|Loop{block;_}|TryTable{block;_}->List.itersel_walkblock.desc|If{if_block;else_block;_}->List.itersel_walkif_block.desc;List.itersel_walkelse_block.desc|Try{block;catches;catch_all;_}->List.itersel_walkblock.desc;List.iter(fun(_,b)->List.itersel_walkb.Ast.desc)catches;Option.iter(funb->List.itersel_walkb.Ast.desc)catch_all|_->()inwalkinstrs;List.itersel_walkinstrs(* Wire the forward reference used by [constant_expression] above. *)let()=lint_constant_expr:=lint_bodyletfunctions?(warn_unused=true)ctxfields=(* The index of the function about to be validated. [build_initial_env] hands
out one function index per import-of-a-function and per [Func] field, in the
order of this same expanded field list (a failed definition still claims its
index), so counting them here stays aligned with it. *)letindex=ref0inList.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->matchfield.descwith|Import{desc=Func_;_}->incrindex|Func{id=_;typ;locals=locs;instrs;exports;priority=_}->(* Claimed before anything can fail below, so a definition whose type
does not resolve still advances the counter, as it does there. *)letself=!indexinincrindex;(* Attribute everything this definition resolves — its signature and
local types as much as its body — to the function itself, so nothing
a dead function names looks externally referenced. Reset once the
whole pass is done (an early return below would skip a per-field
reset, and the next [Func] sets it again anyway). *)ctx.types.origin<-From_functionself;let>@func_typ=let*@typ=typeusectx.diagnosticsctx.typestypinmatch(Types.get_subtypectx.subtyping_infotyp).typwith|Functyp->Sometyp|_->Error.not_function_typectx.diagnostics~location:field.info;Noneinletreturn_types=func_typ.resultsinletreturn_source=snd(functype_sources(typeuse_functypectx.typestyp))inletlocals=Sequence.make"local"inletinitialized_locals=refIntSet.emptyinleti=ref0in(matchtypwith|_,Some{params;_}->Array.iter(funp->letid,typ=p.Ast.descininitialized_locals:=IntSet.add!i!initialized_locals;incri;(* Resolved with muted diagnostics: this re-resolution only
runs when the typeuse above already resolved, so a broken
param type here was already reported — by [typeuse] for an
inline-only signature, by [check_syntax]'s inline check
when a named type is also given. *)letinterned=matchvaltype(mutedctx.diagnostics)ctx.typestypwith|None->(* Dummy value *)Ref{nullable=false;typ=None_}|Sometyp'->typ'inSequence.registerlocalsid(interned,Plaintyp))params|_->(* No inline parameter list: take the parameters' source types
from the referenced function type's definition. *)letparam_source=fst(functype_sources(typeuse_functypectx.typestyp))inArray.iteri(funjtyp->initialized_locals:=IntSet.add!i!initialized_locals;incri;letsource=param_source.(j)inSequence.registerlocalsNone(typ,source))func_typ.params);(* The locals declared by the function (not its parameters), recorded
as (index, optional name, declaration location) so an unread one
can be reported as unused after the body is validated. *)letdeclared_locals=ref[]inletpoisoned_locals=refIntSet.emptyinList.iter(fune->letid,typ=e.Ast.descinlettyp'=matchvaltypectx.diagnosticsctx.typestypwith|None->(* The declared type did not resolve (already reported).
Poison this local so a use does not cascade a second
mismatch against the dummy; the dummy value is unused. *)poisoned_locals:=IntSet.add!i!poisoned_locals;Ref{nullable=true;typ=Any}|Sometyp->typinifis_defaultabletyp'theninitialized_locals:=IntSet.add!i!initialized_locals;(* Point a named local's warning at its name; an unnamed one at
the whole declaration. *)letlocation=matchidwithSomeid->id.Ast.info|None->e.Ast.infoindeclared_locals:=(!i,Option.map(funid->id.Ast.desc)id,location)::!declared_locals;incri;Sequence.registerlocalsid(typ',Plaintyp))locs;letctx={locals;control_types=[(None,return_types,return_source,reffalse)];return_types;return_source;modul=ctx;initialized_locals=!initialized_locals;used_locals=refIntSet.empty;poisoned_locals=!poisoned_locals;label_decls=ref[];}inwith_empty_stackctx.modulfield.info(let*()=instructionsctxinstrsinpop_argsctx~kind:`Outputfield.info~source:return_sourcereturn_types);(* A named local whose name starts with [_] is intentionally unused;
unnamed locals are always reported. *)ifwarn_unusedthenList.iter(fun(idx,name,location)->if(not(IntSet.memidx!(ctx.used_locals)))&¬(matchnamewith|Somen->String.lengthn>0&&n.[0]='_'|None->false)thenError.unused_localctx.modul.diagnostics~locationname)(List.rev!declared_locals);(* A named block label never branched to. A name starting with [_] is
intentionally unused. *)ifwarn_unusedthenList.iter(fun((name:Ast.Text.name),used)->if(not!used)&¬(String.lengthname.desc>0&&name.desc.[0]='_')thenError.unused_labelctx.modul.diagnostics~location:name.infoname.desc)(List.rev!(ctx.label_decls));ifwarn_unusedthenlint_bodyctxinstrs;register_exportsctx.modulexports|_->())(List.concat_mapAst_utils.expand_import_groupfields);ctx.types.origin<-Root(* Report a "Trojan Source" bidirectional control character in any string the
module carries — an export/import name, a data segment, a string literal, a
feature or conditional string. A subset of what [wasm-tools] rejects in
string literals; gated by [warn_unused] like the other lints. *)letlint_confusablectxfields=letd=ctx.diagnosticsinletcheck(s:Ast.Text.name)=matchWax_utils.Unicode.first_confusables.Ast.descwith|Someu->Error.confusable_unicoded~location:s.Ast.infou|None->()inletcheck_str~locations=matchWax_utils.Unicode.first_confusableswith|Someu->Error.confusable_unicoded~locationu|None->()inletreccheck_cond(c:Ast.cond)=matchcwith|Cond_strings->checks|Cond_var_|Cond_version_->()|Cond_andl|Cond_orl->List.itercheck_condl|Cond_notc->check_condc|Cond_cmp(_,a,b)->check_conda;check_condbinletcheck_instr(i:_Ast.Text.instr)=matchi.descwith|String(_,pieces)->List.itercheckpieces|If_annotation{cond;_}->check_condcond|_->()inletcheck_bodyinstrs=List.iter(Ast_utils.iter_instrcheck_instr)instrsinletcheck_datainit=List.iter(fun(e:(Ast.Text.datavalelem,Ast.location)Ast.annotated)->matche.Ast.descwith|Strs->check_str~location:e.Ast.infos|Numlist_|V128list_->())initinletrecwalkfields=List.iter(fun(field:(_Ast.Text.modulefield,Ast.location)Ast.annotated)->matchfield.Ast.descwith|Export{name;_}->checkname|Import{module_;name;exports;_}->checkmodule_;checkname;List.itercheckexports|Import_group1{module_;items}->checkmodule_;List.iter(fun(n,_,_)->checkn)items|Import_group2{module_;items;_}->checkmodule_;List.itercheckitems|Func{instrs;exports;_}->List.itercheckexports;check_bodyinstrs|Global{init;exports;_}->List.itercheckexports;check_bodyinit|Elem{init;_}->List.itercheck_bodyinit|Memory{init;exports;_}->List.itercheckexports;Option.itercheck_datainit|Table{exports;_}|Tag{exports;_}->List.itercheckexports|Data{init;_}->check_datainit|String_global{init;_}->List.itercheckinit|Feature_annotationn->checkn|Module_if_annotation{cond;then_fields;else_fields}->check_condcond;walkthen_fields.Ast.desc;Option.iter(fun(f:(_,_)Ast.annotated)->walkf.Ast.desc)else_fields|Types_|Start_->())fieldsinwalkfieldsletexportsctxfields=List.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->matchfield.descwith|Export{name;kind;index}->(register_exportsctx[name];(* An exported field is externally reachable, so mark it used for the
[unused-field] warning. *)letmarkusedseq=Option.iter(funi->Hashtbl.replaceusedi())(Sequence.get_index_optseqindex)inmatchkindwith|Func->ignore(Sequence.getctx.diagnosticsctx.functionsindex);markctx.used_functionsctx.functions|Memory->ignore(Sequence.getctx.diagnosticsctx.memoriesindex);markctx.used_memoriesctx.memories|Table->ignore(Sequence.getctx.diagnosticsctx.tablesindex);markctx.used_tablesctx.tables|Tag->ignore(Sequence.getctx.diagnosticsctx.tagsindex);markctx.used_tagsctx.tags|Global->ignore(Sequence.getctx.diagnosticsctx.globalsindex);markctx.used_globalsctx.globals;(* The host may assign an exported mutable global. *)markctx.assigned_globalsctx.globals)|_->())fieldsletstartctxfields=List.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->matchfield.descwith|Startidx->(let*?ty,_,_,_=Sequence.getctx.diagnosticsctx.functionsidxin(* The start function is externally reachable. *)Option.iter(funi->Hashtbl.replacectx.used_functionsi())(Sequence.get_index_optctx.functionsidx);match(Types.get_subtypectx.subtyping_infoty).typwith|Struct_|Array_|Cont_->Error.not_function_typectx.diagnostics~location:idx.info|Func{params;results}->ifnot(params=[||]&&results=[||])thenError.start_function_signaturectx.diagnostics~location:idx.info)|_->())fields(* The functions that can actually run: those referenced from a root context
(recorded in [used_functions] — an export, the start function, a global or
table initializer, an element segment), plus everything they transitively
call or take a [ref.func] of.
Reachability, rather than "is referenced somewhere", is what makes the lint
see a dead *cycle*: two functions that only call each other reference one
another, so a presence check finds both used, yet neither can ever run. The
same goes for anything only such a cycle reaches. An escaping [ref.func] is
treated as a call, since where the reference ends up is not tracked — so the
analysis stays conservative: it never reports a function that might run. *)letreachable_functionsctx=letcalls=Hashtbl.create16inList.iter(fun(caller,target)->matchtargetwith|Ref_functioncallee->Hashtbl.addcallscallercallee|Ref_global_|Ref_memory_|Ref_table_|Ref_tag_|Ref_data_|Ref_elem_->())ctx.body_references;letlive=Hashtbl.create16inletrecvisitf=ifnot(Hashtbl.memlivef)thenbeginHashtbl.replacelivef();List.itervisit(Hashtbl.find_allcallsf)endinHashtbl.iter(funf()->visitf)ctx.used_functions;live(* The type definitions that anything reachable names. Seeded by the type
references made from a module-level context or from a function that can run,
then closed over the references a type definition makes through its own
components — so a rec group nothing outside it names is dead as a whole, its
mutual references notwithstanding.
A reference was recorded as written, so map it back to the definition it names:
a numeric one is that index, a symbolic one goes through the name the
definition was declared with. Deduplication is why references are not
canonicalised earlier — two structurally equal named types share one canonical
index, and a reference to either would otherwise mark both. *)letreachable_typesctx~live_functions=letindex_of_name=Hashtbl.create16inHashtbl.iter(funi((def_idx:Ast.Text.idx),_)->matchdef_idx.descwith|Idname->Hashtbl.replaceindex_of_namenamei|Num_->())ctx.types.type_defs;lettarget=function|By_indexi->Somei|By_namen->Hashtbl.find_optindex_of_namenin(* Every source definition sharing a canonical index, for the by-canonical
references (see [canonical_type_references]). *)letindices_of_canonical=Hashtbl.create16inHashtbl.iter(funi_->matchHashtbl.find_optctx.types.index_mapping(Uint32.of_inti)with|Some(Types.Defid,_,_,_)->Hashtbl.addindices_of_canonicalidi|Some(Types.Rec_,_,_,_)|None->())ctx.types.type_defs;letcomponents=Hashtbl.create16inletseeds=ref[]in(* A reference made by a type definition is an edge from it; one made from a
module-level context, or from a function that can run, is a seed. *)letrecordorigintargets=matchoriginwith|From_typesrc->List.iter(Hashtbl.addcomponentssrc)targets|Root->seeds:=targets@!seeds|From_functionf->ifHashtbl.memlive_functionsfthenseeds:=targets@!seeds|Ignored->()inList.iter(fun(origin,r)->recordorigin(Option.to_list(targetr)))ctx.types.type_references;List.iter(fun(origin,id)->recordorigin(Hashtbl.find_allindices_of_canonicalid))ctx.types.canonical_type_references;letlive=Hashtbl.create16inletrecvisitt=ifnot(Hashtbl.memlivet)thenbeginHashtbl.replacelivet();List.itervisit(Hashtbl.find_allcomponentst)endinList.itervisit!seeds;live(* Report module-defined fields that nothing reachable references, exported, or
used as the start function (the module-level analog of an unused local), and
likewise for imports; then the mutable globals that are never assigned.
References are collected during validation — roots into the [used_*] tables,
body references into [body_references] — and a name starting with [_] is
intentionally unused. Runs after every other pass so all references have been
seen. *)letunused_fieldsctx=ifnotctx.warn_unusedthen()elseletlive_functions=reachable_functionsctxin(* A field is used if a root context references it, or if a function that can
actually run does. A reference from dead code keeps nothing alive. *)letused_inrootsselect=lett=Hashtbl.copyrootsinList.iter(fun(caller,target)->ifHashtbl.memlive_functionscallerthenOption.iter(funi->Hashtbl.replaceti())(selecttarget))ctx.body_references;tinletused_globals=used_inctx.used_globals(functionRef_globali->Somei|_->None)andused_memories=used_inctx.used_memories(functionRef_memoryi->Somei|_->None)andused_tables=used_inctx.used_tables(functionRef_tablei->Somei|_->None)andused_tags=used_inctx.used_tags(functionRef_tagi->Somei|_->None)andused_data=used_inctx.used_data(functionRef_datai->Somei|_->None)andused_elem=used_inctx.used_elem(functionRef_elemi->Somei|_->None)inletintentional_name=function|Somen->String.lengthn>0&&n.[0]='_'|None->falseinletintentional(name:Ast.Text.nameoption)=intentional_name(Option.map(fun(n:Ast.Text.name)->n.desc)name)inletreportemitusedkinddecls=List.iter(fun(idx,(name:Ast.Text.nameoption),location)->if(not(Hashtbl.memusedidx))&¬(intentionalname)thenemitctx.diagnostics~locationkind(Option.map(fun(n:Ast.Text.name)->n.desc)name))(List.revdecls)inreportError.unused_fieldlive_functions"function"ctx.defined_functions;reportError.unused_fieldused_globals"global"ctx.defined_globals;reportError.unused_fieldused_memories"memory"ctx.defined_memories;reportError.unused_fieldused_tables"table"ctx.defined_tables;reportError.unused_fieldused_tags"tag"ctx.defined_tags;reportError.unused_fieldused_data"data segment"ctx.defined_data;reportError.unused_fieldused_elem"element segment"ctx.defined_elem;reportError.unused_importlive_functions"function"ctx.imported_functions;reportError.unused_importused_globals"global"ctx.imported_globals;reportError.unused_importused_memories"memory"ctx.imported_memories;reportError.unused_importused_tables"table"ctx.imported_tables;reportError.unused_importused_tags"tag"ctx.imported_tags;(* A type definition nothing reachable names, reported at the definition and
in source (index) order like the rest. Only source definitions are
candidates: the implicit function types interned for an inline block
signature have no [type_defs] entry, so they are never reported. *)letlive_types=reachable_typesctx~live_functionsinList.iter(fun(i,(def_idx:Ast.Text.idx))->letname=matchdef_idx.descwithIdn->Somen|Num_->Noneinif(not(Hashtbl.memlive_typesi))&¬(intentional_namename)thenError.unused_fieldctx.diagnostics~location:def_idx.info"type"name)(List.sort(fun(a,_)(b,_)->compareab)(Hashtbl.fold(funi(def_idx,_)acc->(i,def_idx)::acc)ctx.types.type_defs[]));(* A mutable global never assigned could be immutable. A global that is not
used at all is already reported as [unused-field], so do not pile a second
diagnostic on the same declaration. *)List.iter(fun(idx,(name:Ast.Text.nameoption),location)->ifHashtbl.memused_globalsidx&&(not(Hashtbl.memctx.assigned_globalsidx))&¬(intentionalname)thenError.unnecessary_mutctx.diagnostics~location(Option.map(fun(n:Ast.Text.name)->n.desc)name))(List.revctx.mutable_globals)(*** Whole-module validation ***)(* Syntactic well-formedness checks that the stack-based validation does not
cover: duplicate identifiers in each namespace, an inline type annotation
that disagrees with the type it names, duplicate parameter/local names, and a
second start function. Type references are resolved through [ctx], the type
context the rest of validation has already built, which handles recursive and
forward references correctly. *)letcheck_syntaxctxlst=lettypes=Hashtbl.create16inletfunctions=Hashtbl.create16inletmemories=Hashtbl.create16inlettables=Hashtbl.create16inletglobals=Hashtbl.create16inlettags=Hashtbl.create16inletelems=Hashtbl.create16inletdatas=Hashtbl.create16inletcheck_unboundtblkindid=let>@id:Ast.Text.name=idinmatchHashtbl.find_opttblid.descwith|Someprev_loc->Error.index_already_boundctx.diagnostics~location:id.info~prev_lockindid|None->Hashtbl.addtblid.descid.infoinletiter_instrsfinstrs=List.iter(Ast_utils.iter_instr(funi->fi.desc))instrsin(* An inline type annotation [(type idx) (param ...) (result ...)] must name a
function type whose signature equals the inline one. The reference is
resolved with muted diagnostics — an unbound or non-function type is
reported by the pass that owns the typeuse ([build_initial_env] for an
import or tag, [functions] for a function, the body validation for a block
or [call_indirect]); only the inline/named disagreement is this check's to
report. The inline signature itself is built loudly: when a reference is
also given, [typeuse] ignores the inline form, so an unbound type inside it
is reported nowhere else. *)letcheck_inline_typeidxtarget=let>@gidx=resolve_type_index(mutedctx.diagnostics)ctx.typesidxinmatch(Types.get_subtypectx.subtyping_infogidx).typwith|Funcf->(matchfunctypectx.diagnosticsctx.typestargetwith|Somef'->iff<>f'then(* Render the definition's declared source signature — its concrete
references have no source name to reconstruct from the resolved
[f] (see [inline_function_type_mismatch]). The index resolved to
a [Func] above, so its source comptype is a [Func] too. *)letparams,results=matchlookup_source_comptypectx.typesidxwith|Some(Funcsft)->functype_sourcessft|_->assertfalseinError.inline_function_type_mismatchctx.diagnostics~location:idx.Ast.info~params~results|None->())|Struct_|Array_|Cont_->()inletcheck_instr_inlinedesc=letcheck_typeuse=function|Ast.Text.Typeuse(Someidx,Someft)->check_inline_typeidxft|_->()inmatchdescwith|Ast.Text.Block{typ=Somet;_}|Ast.Text.Loop{typ=Somet;_}|Ast.Text.If{typ=Somet;_}|Ast.Text.Try{typ=Somet;_}|Ast.Text.TryTable{typ=Somet;_}->check_typeuset|CallIndirect(_,(Someidx,Someft))->check_inline_typeidxft|ReturnCallIndirect(_,(Someidx,Someft))->check_inline_typeidxft|_->()inletcheck_duplicate_localstyplocals=letparam_ids=matchsndtypwith|Some{Ast.Text.params;_}->Array.to_list(Array.map(funp->fstp.Ast.desc)params)|None->[]inletlocal_ids=List.map(fune->fste.Ast.desc)localsinletseen=Hashtbl.create16inList.iter(funid->let*?id:Ast.Text.name=idinmatchHashtbl.find_optseenid.descwith|Someprev_loc->Error.duplicate_localctx.diagnostics~location:id.Ast.info~prev_locid.desc|None->Hashtbl.addseenid.descid.Ast.info)(param_ids@local_ids)inletcheck_importid(desc:Ast.Text.importdesc)=lettbl,kind=matchdescwith|Func_->(functions,"function")|Memory_->(memories,"memory")|Table_->(tables,"table")|Global_->(globals,"global")|Tag_->(tags,"tag")incheck_unboundtblkindid;matchdescwith|Func{typ=Someidx,Somesign;_}->check_inline_typeidxsign|Tag(Someidx,Somesign)->check_inline_typeidxsign|_->()inList.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->matchfield.descwith|Typeslst->Array.iter(fune->check_unboundtypes"type"(fste.Ast.desc);match(snde.Ast.desc).Ast.Text.typwith|Ast.Text.Types.Func_|Array_|Cont_->()|Structlst->letfields=Hashtbl.create16inArray.iter(fune->check_unboundfields"field"(fste.Ast.desc))lst)lst|Import{id;desc;_}->check_importiddesc|Import_group1{items;_}->List.iter(fun(_,id,desc)->check_importiddesc)items|Import_group2{desc;items;_}->List.iter(fun_->check_importNonedesc)items|Func{id;typ;locals;instrs;_}->check_unboundfunctions"function"id;(matchtypwith|Someidx,Somesign->check_inline_typeidxsign|_->());check_duplicate_localstyplocals;iter_instrscheck_instr_inlineinstrs|Memory{id;_}->check_unboundmemories"memory"id|Table{id;_}->check_unboundtables"table"id|Tag{id;typ=Someidx,Somesign;_}->check_unboundtags"tag"id;check_inline_typeidxsign|Tag{id;_}->check_unboundtags"tag"id|Global{id;_}->check_unboundglobals"global"id|Export_|Start_->()|Elem{id;_}->check_unboundelems"elem"id|Data{id;_}->check_unbounddatas"data"id|String_global{id;_}->check_unboundglobals"global"(Someid)|Feature_annotation_|Module_if_annotation_->())lst;matchList.filter(funfield->matchfield.Ast.descwithAst.Text.Start_->true|_->false)lstwith|first::second::_->Error.multiple_startctx.diagnostics~location:second.Ast.info~prev_loc:first.Ast.info|_->()letvalidate_configuration?(warn_unused=true)?(features=Wax_utils.Feature.default())diagnostics(_,fields)=lettype_context={types=Types.create();last_index=0;index_mapping=Hashtbl.create16;label_mapping=Hashtbl.create16;poisoned_index=Hashtbl.create16;poisoned_label=Hashtbl.create16;type_defs=Hashtbl.create16;descriptor_source=Hashtbl.create16;features;origin=Root;type_references=[];canonical_type_references=[];record_references=warn_unused;}inList.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->matchfield.descwith|Typesrectype->add_typediagnosticstype_contextrectype|_->())fields;collect_implicit_typesdiagnosticstype_contextfields;(* Make the type context available to [push] for go-to-type-definition
recording (editor mode only; reset in [f]). *)if!recorded_types<>Nonethensink_type_context:=Sometype_context;(* Register the implicit [<string>] array type ([mut i8]) up front, so that
validating an unnamed [@string] — which looks the type up via [string_type]
([add_rectype], idempotent) — gets an index within [subtyping_info] instead
of one appended past the snapshot taken here. *)ignore(string_typetype_context:Types.Id.t);letctx={diagnostics;types=type_context;subtyping_info=Types.subtyping_infotype_context.types;functions=Sequence.make"function";memories=Sequence.make"memory";tables=Sequence.make"table";globals=Sequence.make"global";tags=Sequence.make"tag";data=Sequence.make"data segment";elem=Sequence.make"elem segment";exports=Hashtbl.create16;refs=Hashtbl.create16;used_functions=Hashtbl.create16;used_globals=Hashtbl.create16;used_memories=Hashtbl.create16;used_tables=Hashtbl.create16;used_tags=Hashtbl.create16;used_data=Hashtbl.create16;used_elem=Hashtbl.create16;body_references=[];assigned_globals=Hashtbl.create16;defined_functions=[];defined_globals=[];defined_memories=[];defined_tables=[];defined_tags=[];defined_data=[];defined_elem=[];mutable_globals=[];imported_functions=[];imported_globals=[];imported_memories=[];imported_tables=[];imported_tags=[];warn_unused;}incheck_type_definitionsctx;build_initial_envctxfields;letctx={ctxwithsubtyping_info=Types.subtyping_infotype_context.types}incheck_syntaxctxfields;tables_and_memoriesctxfields;globalsctxfields;segmentsctxfields;declared_func_exportsctxfields;functions~warn_unusedctxfields;exportsctxfields;startctxfields;ifwarn_unusedthenlint_confusablectxfields;unused_fieldsctx(* Path-sensitive validation of conditional annotations.
A module containing [(@if ...)] conditionals denotes one concrete module per
"configuration" (a choice of branch at every reachable conditional). An
exhaustive {!Cond_plan} enumerates every reachable configuration as a run;
the module is projected onto each — splicing in the branches the run selects
to obtain a conditional-free module — validated with
{!validate_configuration}, and each distinct error is reported once
({!Cond_explore.report}), annotated with the minimal assumption under which
it occurs. *)(*** Conditional compilation and entry point ***)(* Project a module onto one configuration: every conditional resolved to the
side [select] gives for its span ([true]: the then-branch), spliced in.
Exhaustive over [modulefield] and [instr_desc]: every instruction list a
field or instruction can carry is walked (including the offset expression
of an active [data]/[elem] segment), and a new variant is a compile error
rather than a silent miss — the same lists {!Cond_plan.text_shape} reads. *)letprojectselectfields=letrecsfieldsfl=List.concat_mapsfieldflandsfield(f:(_Ast.Text.modulefield,_)Ast.annotated)=matchf.descwith|Module_if_annotation{then_fields;else_fields;_}->ifselectf.infothensfieldsthen_fields.descelseOption.fold~none:[]~some:(fun(e:(_list,_)Ast.annotated)->sfieldse.desc)else_fields|Func{id;typ;locals;instrs;exports;priority}->letdesc:_Ast.Text.modulefield=Func{id;typ;locals;instrs=sinstrsinstrs;exports;priority}in[{fwithdesc}]|Global{id;typ;init;exports}->letdesc:_Ast.Text.modulefield=Global{id;typ;init=sinstrsinit;exports}in[{fwithdesc}]|Table{id;typ;init;exports}->letinit:_Ast.Text.tableinit=matchinitwith|Init_default->Init_default|Init_expre->Init_expr(sinstrse)|Init_segmentsegs->Init_segment(List.mapsinstrssegs)inletdesc:_Ast.Text.modulefield=Table{id;typ;init;exports}in[{fwithdesc}]|Elem{id;typ;init;mode}->letmode:_Ast.Text.elemmode=matchmodewith|Active(idx,e)->Active(idx,sinstrse)|(Passive|Declare)asmode->modeinletdesc:_Ast.Text.modulefield=Elem{id;typ;init=List.mapsinstrsinit;mode}in[{fwithdesc}]|Data{id;init;mode}->letmode:_Ast.Text.datamode=matchmodewith|Active(idx,e)->Active(idx,sinstrse)|Passiveasmode->modein[{fwithdesc=Data{id;init;mode}}]|Types_|Import_|Import_group1_|Import_group2_|Memory_|Tag_|Export_|Start_|String_global_|Feature_annotation_->[f]andsinstrsl=List.concat_mapsinstrlandsinstr(i:_Ast.Text.instr)=matchi.descwith|If_annotation{then_body;else_body;_}->ifselecti.infothensinstrsthen_body.descelseOption.fold~none:[]~some:(fun(e:(_list,_)Ast.annotated)->sinstrse.desc)else_body|desc->[{iwithdesc=sstructureddesc}]andsstructured(desc:_Ast.Text.instr_desc)=matchdescwith|Blockb->Block{bwithblock={b.blockwithdesc=sinstrsb.block.desc}}|Loopb->Loop{bwithblock={b.blockwithdesc=sinstrsb.block.desc}}|Ifb->If{bwithif_block={b.if_blockwithdesc=sinstrsb.if_block.desc};else_block={b.else_blockwithdesc=sinstrsb.else_block.desc};}|TryTableb->TryTable{bwithblock={b.blockwithdesc=sinstrsb.block.desc}}|Tryb->Try{bwithblock={b.blockwithdesc=sinstrsb.block.desc};catches=List.map(fun(idx,l)->(idx,{lwithAst.desc=sinstrsl.Ast.desc}))b.catches;catch_all=Option.map(funb->{bwithAst.desc=sinstrsb.Ast.desc})b.catch_all;}|Folded(h,l)->Folded({hwithdesc=sstructuredh.desc},sinstrsl)(* Every instruction that carries no nested instruction is returned as-is.
Enumerated rather than caught by a wildcard so a future instruction that
nests others is a compile error here instead of silently escaping the
projection. *)|(Unreachable|Nop|Throw_|ThrowRef|ContNew_|ContBind_|Suspend_|Resume_|ResumeThrow_|ResumeThrowRef_|Switch_|Br_|Br_if_|Br_table_|Br_on_null_|Br_on_non_null_|Br_on_cast_|Br_on_cast_fail_|Br_on_cast_desc_eq_|Br_on_cast_desc_eq_fail_|Return|Call_|CallRef_|ReturnCall_|ReturnCallRef_|Drop|Select_|LocalGet_|LocalSet_|LocalTee_|GlobalGet_|GlobalSet_|Load_|LoadS_|Store_|StoreS_|Atomic_|AtomicFence|MemorySize_|MemoryGrow_|MemoryFill_|MemoryCopy_|MemoryInit_|DataDrop_|TableGet_|TableSet_|TableSize_|TableGrow_|TableFill_|TableCopy_|TableInit_|ElemDrop_|RefNull_|RefFunc_|RefIsNull|RefAsNonNull|RefEq|RefTest_|RefCast_|RefCastDescEq_|RefGetDesc_|StructNew_|StructNewDefault_|StructNewDesc_|StructNewDefaultDesc_|StructGet_|StructSet_|ArrayNew_|ArrayNewDefault_|ArrayNewFixed_|ArrayNewData_|ArrayNewElem_|ArrayGet_|ArraySet_|ArrayLen|ArrayFill_|ArrayCopy_|ArrayInitData_|ArrayInitElem_|RefI31|I31Get_|Const_|UnOp_|BinOp_|Add128|Sub128|MulWide_|I32WrapI64|I64ExtendI32_|F32DemoteF64|F64PromoteF32|ExternConvertAny|AnyConvertExtern|VecBitselect|VecConst_|VecUnOp_|VecBinOp_|VecTest_|VecShift_|VecBitmask_|VecLoad_|VecStore_|VecLoadLane_|VecStoreLane_|VecLoadSplat_|VecExtract_|VecReplace_|VecSplat_|VecShuffle_|VecTernOp_|Char_|CallIndirect_|ReturnCallIndirect_|String_|If_annotation_)asdesc->descinsfieldsfields(* WebAssembly requires every import to precede all non-import definitions
(functions, tables, memories, globals, tags). Report any import that follows
such a definition. *)letcheck_import_orderdiagnosticsfields=ignore(List.fold_left(funcan_import(field:(_Ast.Text.modulefield,_)Ast.annotated)->match(can_import,field.desc)with|(Some(previous,prev_loc),(Import_|Import_group1_|Import_group2_))->Error.import_after_definitiondiagnostics~location:field.info~prev_locprevious;can_import|None,Func_->Some("function",field.info)|None,Memory_->Some("memory",field.info)|None,Table_->Some("table",field.info)|None,Tag_->Some("tag",field.info)|None,Global_->Some("global",field.info)|None,String_global_->Some("string",field.info)|(Some_,(Func_|Memory_|Table_|Tag_|Global_|String_global_))|None,(Import_|Import_group1_|Import_group2_)|(_,(Types_|Export_|Start_|Elem_|Data_|Feature_annotation_|Module_if_annotation_))->can_import)Nonefields)(* Apply the module's [(@feature "…")] declarations to [features]: each
declared feature is enabled, in union with the command-line configuration —
unless the command line explicitly disabled it, which is a conflict reported
once, at the annotation. Runs at the entry point, before anything consults
[is_enabled]. Only top-level annotations count: the annotation states a fact
about the whole module, so it is not conditional. Mirrors the Wax typer's
[apply_declared_features]. *)letapply_declared_featuresdiagnosticsfeaturesfields=List.iter(fun(field:(_Ast.Text.modulefield,_)Ast.annotated)->matchfield.descwith|Ast.Text.Feature_annotationname->(letlocation=name.Ast.infoinmatchWax_utils.Feature.of_namename.Ast.descwith|None->Error.unknown_featurediagnostics~locationname.Ast.desc|Somefeature->ifWax_utils.Feature.explicitly_disabledfeaturesfeaturethenError.feature_conflictdiagnostics~locationfeature;(* Enable it even on a conflict: the error has been reported
once, at the annotation; without this every gated construct
below would error too. *)Wax_utils.Feature.declarefeaturesfeature)|_->())fieldsletf?(warn_unused=true)?(features=Wax_utils.Feature.default())?record_typesdiagnostics((name,fields)asmodul)=Wax_utils.Debug.timed"validate"@@fun()->recorded_types:=record_types;sink_config:=0;Fun.protect~finally:(fun()->recorded_types:=None;sink_type_context:=None)@@fun()->apply_declared_featuresdiagnosticsfeaturesfields;check_import_orderdiagnosticsfields;matchCond_plan.text_shapefieldswith|[]->validate_configuration~warn_unused~featuresdiagnosticsmodul|shape->letplan=Cond_plan.make~exhaustive:truediagnosticsshapeinifwarn_unusedthenList.iter(fun(location,side)->Error.dead_branchdiagnostics~location~side)(Cond_plan.dead_branchesplan);letconfigurations=List.map(funrun->(* Each configuration is validated in its own collector, derived
from the parent so it inherits its error-recovery mode. Its
recorded types are tagged with the run's index, so a
config-varying span's alternatives stay separable from a single
configuration's multi-result tuple. *)letcctx=Wax_utils.Diagnostic.collector~parent:diagnostics()insink_config:=run;validate_configuration~warn_unused~featurescctx(name,project(Cond_plan.selectplanrun)fields);(Wax_utils.Diagnostic.collectedcctx,Cond_plan.assumptionplanrun))(Cond_plan.runsplan)inCond_explore.reportdiagnostics?truncation_location:(matchfieldswithf::_->Somef.Ast.info|[]->None)~explain:(Cond_plan.explainplan)~truncated:(Cond_plan.truncatedplan)configurations