123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762(* Claude Code
*
* Copyright (C) 2026 Yoann Padioleau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* (LGPL) as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*)(* See St_compile.mli *)openSt_astmoduleM=St_memorymoduleB=St_bytecodemoduleC=St_classtypeoop=M.oopexceptionErrorofint*string(*****************************************************************************)(* Code, in pieces *)(*****************************************************************************)(* the bytecodes of a piece, and its sends (pc, start, stop): a
* conditional's branches are compiled first, into pieces of their own,
* so that the jump over them knows how long it is *)typecode={buf:Buffer.t;mutablesends:(int*int*int)list}letnew_code():code={buf=Buffer.create64;sends=[]}letpc(c:code):int=Buffer.lengthc.bufletemit(c:code)(b:int):unit=Buffer.add_charc.buf(Char.chrb)letappend(dst:code)(src:code):unit=letoff=pcdstinBuffer.add_bufferdst.bufsrc.buf;dst.sends<-List.map(fun(p,a,b)->(p+off,a,b))src.sends@dst.sends(*****************************************************************************)(* The compiler's state *)(*****************************************************************************)(* a temporary's name where it is declared: its block's place in the
* text (the method's is (0, 0)), and the name *)typekey=pos*string(* a context's temporaries as the compiler lays them out: the method's,
* or -- with closures -- a block's, each activation having its own *)typeframe={block:pos;(* whose *)outer:frameoption;mutablenslots:int;(* arguments, copied values, temporaries *)mutablevector:int;(* the slot of its temp vector, -1 without *)mutablenremote:int;(* the temporaries in the vector *)copied:(copy*int)list;(* what a block copies when it is made, and the slot of each *)}(* an outer temporary's value, or an outer frame's temp vector *)andcopy=Valueofkey|Vectorofpos(* where a temporary is: a slot of its frame, or an index in its
* frame's temp vector *)typeplace=Directofint|Remoteofinttypetemp={key:key;frame:frame;place:place}(* what the first pass of the closure compiler learns for the second
* (St_compile.mli) *)typefacts={learning:bool;(* the first pass *)captured:(key,unit)Hashtbl.t;(* used by a block inside the one that declares it *)changed:(key,unit)Hashtbl.t;(* assigned when a block may already hold it *)free:(pos,keylist)Hashtbl.t;(* a block's outer temporaries, in the order met *)uninline:(pos,unit)Hashtbl.t;(* the to:do: loops whose variable is captured *)mutableagain:bool;(* a loop was added to uninline: learn again *)}typest={m:M.t;cls:oop;declare:bool;inst_vars:stringlist;facts:factsoption;(* closures, or the Blue Book's blocks *)mutableliterals:ooplist;(* reversed *)mutablenlits:int;mutablescope:(string*temp)list;(* the temporaries in sight, innermost first *)mutableargs:stringlist;(* which of them cannot be stored into *)mutableframe:frame;(* the one being compiled *)mutablenames:stringlist;(* the method's temporaries', by index, reversed *)mutabledepth:int;mutablemax_depth:int;mutableneed:int;(* the biggest frame a closure asks for: its slots and its stack *)mutableloops:int;(* the inlined loops around *)}leterror((start,_):pos)(msg:string)=raise(Error(start,msg))letpush_depth(st:st)(n:int):unit=st.depth<-st.depth+n;ifst.depth>st.max_depththenst.max_depth<-st.depth(* the index of a literal, the same one for the same oop (a symbol, an
* Association, a SmallInteger) *)letliteral(st:st)(o:oop):int=letrecfindi=function[]->None|x::rest->ifx=othenSomeielsefind(i-1)restinmatchfind(st.nlits-1)st.literalswith|Somei->i|None->st.literals<-o::st.literals;st.nlits<-st.nlits+1;st.nlits-1letnew_slot(st:st)(pos:pos)(name:string):int=letf=st.frameinleti=f.nslotsinifi>=63thenerrorpos"Too many temporaries";f.nslots<-i+1;(matchf.outerwithNone->st.names<-name::st.names|Some_->());i(* a temporary declared by the block at [pos], in the frame being
* compiled: in a slot, or in the frame's temp vector (made when its
* first temporary is) if a block captures it and it changes *)letnew_temp(st:st)(pos:pos)(name:string):unit=letf=st.frameinletkey=(pos,name)inletremote=matchst.factswith|Somefacts->(notfacts.learning)&&Hashtbl.memfacts.capturedkey&&Hashtbl.memfacts.changedkey|None->falseinletplace=ifremotethenbeginiff.vector<0thenf.vector<-new_slotstpos" vector";iff.nremote>=127thenerrorpos"Too many temporaries";f.nremote<-f.nremote+1;Remote(f.nremote-1)endelseDirect(new_slotstposname)inst.scope<-(name,{key;frame=f;place})::st.scope(*****************************************************************************)(* Literals *)(*****************************************************************************)letrecliteral_object(m:M.t)(l:literal):oop=letk=M.knownminmatchlwith|L_inti->M.of_inti|L_large(neg,bytes)->letb=Bytes.of_string(String.init(List.lengthbytes)(funi->Char.chr(List.nthbytesi)))inM.allocm~cls:(ifnegthenk.large_negativeelsek.large_positive)(M.Bytesb)|L_floatf->M.new_floatmf|L_charc->k.characters.(Char.codec)|L_strings->M.new_stringms|L_symbols->M.symbolms|L_arrayl->M.new_arraym(Array.of_list(List.map(literal_objectm)l))|L_nil->M.nil|L_true->k.true_|L_false->k.false_(*****************************************************************************)(* Pushes and stores *)(*****************************************************************************)(* 128 jjkkkkkk, for an index past the short forms *)letextended(st:st)(c:code)(op:int)(kind:int)(i:int)(pos:pos):unit=ifi>63thenerrorpos"Too many literals or variables";emitcop;emitc((kindlsl6)lori);ignorestletpush_literal_index(st:st)(c:code)(i:int)(pos:pos):unit=ifi<32thenemitc(32+i)elseextendedstc1282iposletpush_literal(st:st)(c:code)(l:literal)(pos:pos):unit=(matchlwith|L_int-1->emitc116|L_int0->emitc117|L_int1->emitc118|L_int2->emitc119|_->push_literal_indexstc(literalst(literal_objectst.ml))pos);push_depthst1(* a temporary in a temp vector: its index there, and the slot of the
* vector *)typevar=Tempofint|Remote_tempofint*int|Instofint|Assocofoop(* the first pass: [v] is used in frame [f], inside its own; every
* block from f out to v's own has to copy it *)letcapture(facts:facts)(f:frame)(v:temp):unit=Hashtbl.replacefacts.capturedv.key();letrecup(g:frame)=ifg!=v.framethenbeginletfree=Option.value(Hashtbl.find_optfacts.freeg.block)~default:[]inifnot(List.memv.keyfree)thenHashtbl.replacefacts.freeg.block(free@[v.key]);Option.iterupg.outerendinupfletcopied_slot(st:st)(c:copy):int=Option.value(List.assoc_optcst.frame.copied)~default:0(* a temporary as the frame being compiled reaches it: its own, or
* through what the block copied *)lettemp_var(st:st)(v:temp):var=letf=st.frameinifv.frame==fthenmatchv.placewithDirecti->Tempi|Remotej->Remote_temp(j,f.vector)elsebegin(matchst.factswithSomefactswhenfacts.learning->capturefactsfv|_->());matchv.placewith|Direct_->Temp(copied_slotst(Valuev.key))|Remotej->Remote_temp(j,copied_slotst(Vectorv.frame.block))endletresolve(st:st)(name:string)(pos:pos):var=matchList.assoc_optnamest.scopewith|Somev->temp_varstv|None->(letrecindexi=function[]->None|v::rest->ifv=namethenSomeielseindex(i+1)restin(* the last of two same names wins: a subclass's own *)matchindex0(List.revst.inst_vars)with|Somei->Inst(List.lengthst.inst_vars-1-i)|None->(matchC.class_varst.mst.clsnamewith|Somea->Assoca|None->(matchC.globalst.mnamewith|Somea->Assoca|None->ifst.declarethenAssoc(C.declare_globalst.mnameM.nil)elseerrorpos("Undeclared variable "^name))))letpush_var(st:st)(c:code)(name:string)(pos:pos):unit=(matchnamewith|"self"|"super"->emitc112|"true"->emitc113|"false"->emitc114|"nil"->emitc115|"thisContext"->emitc137|_->(matchresolvestnameposwith|Tempi->ifi<16thenemitc(16+i)elseextendedstc1281ipos|Remote_temp(j,vector)->emitc140;emitcj;emitcvector|Insti->ifi<16thenemitcielseextendedstc1280ipos|Assoca->leti=literalstainifi<32thenemitc(64+i)elseextendedstc1283ipos));push_depthst1(* store the top into a variable, popping it or not. The first pass
* notes a temporary that changes when a block may already hold its
* value: assigned from a block inside its own, or after a block that
* uses it, or in a loop *)letstore(st:st)(c:code)(name:string)(pos:pos)~(pop:bool):unit=(match(st.facts,List.assoc_optnamest.scope)with|Somefacts,Somevwhenfacts.learning->ifv.frame!=st.frame||st.loops>0||Hashtbl.memfacts.capturedv.keythenHashtbl.replacefacts.changedv.key()|_->());(matchresolvestnameposwith|Tempi->ifpop&&i<8thenemitc(104+i)elseextendedstc(ifpopthen130else129)1ipos|Remote_temp(j,vector)->emitc(ifpopthen142else141);emitcj;emitcvector|Insti->ifpop&&i<8thenemitc(96+i)elseextendedstc(ifpopthen130else129)0ipos|Assoca->extendedstc(ifpopthen130else129)3(literalsta)pos);ifpopthenpush_depthst(-1)(* what the program may store into: not an argument *)letstore_var(st:st)(c:code)(name:string)(pos:pos)~(pop:bool):unit=ifList.memname["self";"super";"true";"false";"nil";"thisContext"]||List.memnamest.argsthenerrorpos("Cannot store into "^name);storestcnamepos~popletpush_temp(st:st)(c:code)(i:int)(pos:pos):unit=ifi<16thenemitc(16+i)elseextendedstc1281ipos;push_depthst1(*****************************************************************************)(* Sends and jumps *)(*****************************************************************************)letsend(st:st)(c:code)(sel:string)(nargs:int)~(super:bool)(pos:pos):unit=letstart,stop=posinc.sends<-(pcc,start,stop)::c.sends;letrecfindi=ifi>=Array.lengthB.special_selectorsthenNoneelseifB.special_selectors.(i)=selthenSomeielsefind(i+1)inletspecial=ifsuperthenNoneelsefind0in(matchspecialwith|Somei->emitc(176+i)|None->leti=literalst(M.symbolst.msel)inifsuperthenifnargs<8&&i<32thenbeginemitc133;emitc((nargslsl5)lori)endelsebeginemitc134;emitcnargs;emitciendelseifnargs<=2&&i<16thenemitc(208+(nargs*16)+i)elseifnargs<8&&i<32thenbeginemitc131;emitc((nargslsl5)lori)endelsebeginifi>255thenerrorpos"Too many literals";emitc132;emitcnargs;emitciend);push_depthst(-nargs)letjump_size(n:int):int=ifn>=1&&n<=8then1else2letjump(c:code)(n:int):unit=ifn>=1&&n<=8thenemitc(143+n)elsebeginemitc(164+(nasr8));emitc(nland255)endletjump_on_false(c:code)(n:int):unit=ifn>=1&&n<=8thenemitc(151+n)elsebeginemitc(172+(nlsr8));emitc(nland255)endletjump_on_true(c:code)(n:int):unit=emitc(168+(nlsr8));emitc(nland255)(* backwards, always the long form, 2 bytes *)letjump_back(c:code)(target:int):unit=letoff=target-(pcc+2)inemitc(164+(offasr8));emitc(offland255)(*****************************************************************************)(* Expressions *)(*****************************************************************************)letis_block0(x:expr):bool=matchx.ewithBlock([],_,_)->true|_->false(* a to:do: whose block captures the loop's variable is sent, not
* inlined: each turn then has its own *)letloop_inlined(st:st)(b:expr):bool=matchst.factswithSomef->not(Hashtbl.memf.uninlineb.pos)|None->trueletrecgen(st:st)(c:code)(x:expr):unit=matchx.ewith|Litl->push_literalstclx.pos|Varv->push_varstcvx.pos|Assign(v,y)->genstcy;store_varstcvx.pos~pop:false|Send(r,sel,args)->gen_sendstcrselargsx.pos|Cascade(r,msgs)->letsuper=matchr.ewithVar"super"->true|_->falseingenstcr;letn=List.lengthmsgsinList.iteri(funi(sel,args,pos)->ifi<n-1thenbeginemitc136;push_depthst1end;List.iter(genstc)args;sendstcsel(List.lengthargs)~superpos;ifi<n-1thenbeginemitc135;push_depthst(-1)end)msgs|Block(args,temps,body)->gen_blockstcargstempsbodyx.posandgen_send(st:st)(c:code)(r:expr)(sel:string)(args:exprlist)(pos:pos):unit=letd=st.depthinmatch(sel,args)with|("ifTrue:ifFalse:"|"ifFalse:ifTrue:"),[a;b]whenis_block0a&&is_block0b->letyes,no=ifsel="ifTrue:ifFalse:"then(a,b)else(b,a)ingenstcr;push_depthst(-1);letc1=new_code()ininline_blockstc1yes;st.depth<-d;letc2=new_code()ininline_blockstc2no;jump_on_falsec(pcc1+jump_size(pcc2));appendcc1;jumpc(pcc2);appendcc2|("ifTrue:"|"ifFalse:"),[a]whenis_block0a->genstcr;push_depthst(-1);letc1=new_code()ininline_blockstc1a;ifsel="ifTrue:"thenjump_on_falsec(pcc1+1)elsejump_on_truec(pcc1+1);appendcc1;jumpc1;emitc115|("and:"|"or:"),[a]whenis_block0a->genstcr;push_depthst(-1);letc1=new_code()ininline_blockstc1a;ifsel="and:"thenjump_on_falsec(pcc1+1)elsejump_on_truec(pcc1+1);appendcc1;jumpc1;emitc(ifsel="and:"then114else113)|("whileTrue:"|"whileFalse:"),[a]whenis_block0r&&is_block0a->letstart=pccinst.loops<-st.loops+1;inline_blockstcr;push_depthst(-1);letc1=new_code()ininline_blockstc1a;emitc1135;push_depthst(-1);ifsel="whileTrue:"thenjump_on_falsec(pcc1+2)elsejump_on_truec(pcc1+2);appendcc1;st.loops<-st.loops-1;jump_backcstart;emitc115;push_depthst1|("whileTrue"|"whileFalse"),[]whenis_block0r->letstart=pccinst.loops<-st.loops+1;inline_blockstcr;st.loops<-st.loops-1;push_depthst(-1);ifsel="whileTrue"thenjump_on_falsec2elsejump_on_truec2;jump_backcstart;emitc115;push_depthst1|"to:do:",[stop;({e=Block([_],_,_);_}asb)]whenloop_inlinedstb->gen_to_dostcrstop1bpos|"to:by:do:",[stop;{e=Lit(L_intstep);_};({e=Block([_],_,_);_}asb)]whenstep<>0&&loop_inlinedstb->gen_to_dostcrstopstepbpos|_->letsuper=matchr.ewithVar"super"->true|_->falseingenstcr;List.iter(genstc)args;sendstcsel(List.lengthargs)~superpos(* "1 to: n do: [:i | ...]" as a loop over a temporary, no block made:
* Squeak's compiler does it; the Blue Book's sent to:do: to the number.
* The limit is computed once, into a hidden temporary. *)andgen_to_do(st:st)(c:code)(start:expr)(stop:expr)(step:int)(b:expr)(pos:pos):unit=matchb.ewith|Block([var],temps,body)->letsaved_scope=st.scopeandsaved_args=st.argsinletlimit=" limit"innew_tempstb.posvar;new_tempstb.poslimit;genstcstart;storestcvarpos~pop:true;genstcstop;storestclimitpos~pop:true;st.args<-var::st.args;List.iter(new_tempstb.pos)temps;lettop=pccinpush_varstcvarpos;push_varstclimitpos;sendstc(ifstep>0then"<="else">=")1~super:falsepos;push_depthst(-1);st.loops<-st.loops+1;letc1=new_code()instatementsstc1body~value:false;push_varstc1varpos;push_literalstc1(L_intstep)pos;sendstc1"+"1~super:falsepos;storestc1varpos~pop:true;st.loops<-st.loops-1;jump_on_falsec(pcc1+2);appendcc1;jump_backctop;emitc115;push_depthst1;st.scope<-saved_scope;st.args<-saved_args;(* claude: the loop's variable held by a block: learn again, the
* loop sent this time *)(matchst.factswith|Somefwhenf.learning&&Hashtbl.memf.captured(b.pos,var)->Hashtbl.replacef.uninlineb.pos();f.again<-true|_->())|_->assertfalse(* a literal block's statements, in place: its temporaries are the
* method's, its value the last statement's *)andinline_block(st:st)(c:code)(b:expr):unit=matchb.ewith|Block(_,temps,body)->letsaved=st.scopeinList.iter(new_tempstb.pos)temps;statementsstcbody~value:true;st.scope<-saved|_->genstcbandgen_block(st:st)(c:code)(args:stringlist)(temps:stringlist)(body:stmtlist)(pos:pos):unit=matchst.factswithNone->gen_block_contextstcargstempsbodypos|Somefacts->gen_closurestfactscargstempsbodypos(* the Blue Book's: push thisContext, push the arguments' count, send
* blockCopy:, jump over the body; the body pops its arguments into
* their temporaries *)andgen_block_context(st:st)(c:code)(args:stringlist)(temps:stringlist)(body:stmtlist)(pos:pos):unit=emitc137;push_depthst1;push_literalstc(L_int(List.lengthargs))pos;emitc200;push_depthst(-1);letsaved_scope=st.scopeandsaved_args=st.argsandouter=st.depthinList.iter(new_tempstpos)args;List.iter(new_tempstpos)temps;(* the block's own stack: its arguments, pushed by value: *)st.depth<-0;push_depthst(List.lengthargs);letb=new_code()inList.iter(funa->storestbapos~pop:true)(List.revargs);st.args<-args@st.args;statementsstbbody~value:true;emitb125;st.scope<-saved_scope;st.args<-saved_args;st.depth<-outer;letn=pcbinifn>1023thenerrorpos"Block too long";emitc(164+(nasr8));emitc(nland255);appendcb(* a closure (Squeak, 2008): push what the block copies, then "push
* closure" (143) with their count, the arguments' and the body's
* length. The body is a frame of its own: the arguments, the copied
* values, then its temporaries, which it pushes itself (nil, or its
* temp vector) before its statements *)andgen_closure(st:st)(facts:facts)(c:code)(args:stringlist)(temps:stringlist)(body:stmtlist)(pos:pos):unit=letouter=st.frameandnargs=List.lengthargsanddepth=st.depthinifnargs>15thenerrorpos"Too many arguments";lettemp_ofkey=snd(List.find(fun(_,(v:temp))->v.key=key)st.scope)inletcopies=List.fold_left(funacckey->letcp=match(temp_ofkey).placewithRemote_->Vector(temp_ofkey).frame.block|Direct_->ValuekeyinifList.memcpaccthenaccelseacc@[cp])[](iffacts.learningthen[]elseOption.value(Hashtbl.find_optfacts.freepos)~default:[])inletncopied=List.lengthcopiesinifncopied>15thenerrorpos"Too many variables used by a block";List.iter(funcp->letslot=matchcpwith|Valuekey->(matchtemp_varst(temp_ofkey)withTempi->i|_->assertfalse)|Vectorblock->ifouter.block=blockthenouter.vectorelsecopied_slotstcpinpush_tempstcslotpos)copies;letf={block=pos;outer=Someouter;nslots=0;vector=-1;nremote=0;copied=List.mapi(funicp->(cp,nargs+i))copies}inletsaved_scope=st.scopeandsaved_args=st.argsandmax_depth=st.max_depthandloops=st.loopsinst.frame<-f;List.iter(new_tempstpos)args;f.nslots<-nargs+ncopied;st.args<-args@st.args;List.iter(new_tempstpos)temps;st.depth<-0;st.max_depth<-0;st.loops<-0;letb=new_code()instatementsstbbody~value:true;emitb125;(* its temporaries, known now that the body is compiled *)letp=new_code()inforslot=nargs+ncopiedtof.nslots-1doifslot=f.vectorthenbeginemitp138;emitpf.nremoteendelseemitp115done;appendpb;st.need<-maxst.need(f.nslots+st.max_depth);st.frame<-outer;st.scope<-saved_scope;st.args<-saved_args;st.max_depth<-max_depth;st.loops<-loops;st.depth<-depth;push_depthst1;letn=pcpinifn>65535thenerrorpos"Block too long";emitc143;emitc((ncopiedlsl4)lornargs);emitc(nlsr8);emitc(nland255);appendcp(* the statements; with [~value], the last one's value is left on the
* stack (nil if there are none) *)andstatements(st:st)(c:code)(body:stmtlist)~(value:bool):unit=letrecgo=function|[]->ifvaluethenbeginemitc115;push_depthst1end|[Return(x,_)]->gen_returnstcx;(* what follows a return is never run, but the branches that
* contain it must still look as if they pushed their value *)ifvaluethenpush_depthst1|Return(x,_)::_->gen_returnstcx|[Exprx]whenvalue->genstcx|Exprx::rest->for_effectstcx;ifrest=[]&&valuethenbeginemitc115;push_depthst1endelsegorestingobodyandgen_return(st:st)(c:code)(x:expr):unit=matchx.ewith|Var"self"->emitc120|Var"true"->emitc121|Var"false"->emitc122|Var"nil"->emitc123|_->genstcx;emitc124;push_depthst(-1)(* an expression whose value is not wanted (claude: for_effect, effect
* being a keyword since OCaml 5.3) *)andfor_effect(st:st)(c:code)(x:expr):unit=matchx.ewith|Assign(v,y)->genstcy;store_varstcvx.pos~pop:true|_->genstcx;emitc135;push_depthst(-1)(*****************************************************************************)(* Methods *)(*****************************************************************************)(* a method's bytecodes, and what the compiler knows at the end *)letgenerate(m:M.t)~(cls:oop)~(declare:bool)(facts:factsoption)(meth:method_):st*code=letframe={block=(0,0);outer=None;nslots=0;vector=-1;nremote=0;copied=[]}inletst={m;cls;declare;inst_vars=C.inst_var_namesmcls;facts;literals=[];nlits=0;scope=[];args=meth.args;frame;names=[];depth=0;max_depth=0;need=0;loops=0;}inList.iter(new_tempst(0,0))meth.args;List.iter(new_tempst(0,0))meth.temps;letc=new_code()in(* the statements, then return self unless the last one returned *)letreturns=matchList.revmeth.bodywithReturn_::_->true|_->falseinstatementsstcmeth.body~value:false;ifnotreturnsthenemitc120;ifframe.vector<0then(st,c)elsebegin(* the method's temp vector, made before anything else *)letp=new_code()inemitp138;emitpframe.nremote;ifframe.vector<8thenemitp(104+frame.vector)elseextendedstp1301frame.vector(0,0);st.max_depth<-maxst.max_depth1;appendpc;(st,p)endletcompile(m:M.t)~(cls:oop)~(source:string)?(declare=false)(meth:method_):oop=letfacts=if(M.knownm).block_closure=M.nilthenNoneelsebegin(* the first pass, again as long as it finds a loop to send *)letuninline=Hashtbl.create4inletreclearn()=letf={learning=true;captured=Hashtbl.create16;changed=Hashtbl.create16;free=Hashtbl.create16;uninline;again=false}inignore(generatem~cls~declare(Somef)meth);iff.againthenlearn()elsefinSome{(learn())withlearning=false}endinletst,c=generatem~cls~declarefactsmethinletntemps=st.frame.nslotsinletheader={B.primitive=Option.valuemeth.primitive~default:0;num_args=List.lengthmeth.args;num_temps=ntemps;frame_size=min255(max(ntemps+st.max_depth)st.need+1);}inB.new_methodm~header~literals:(Array.of_list(List.revst.literals))~bytecodes:(Buffer.to_bytesc.buf)~selector:(M.symbolmmeth.selector)~cls~source~pcmap:(List.revc.sends)~temp_names:(List.revst.names)letcompile_and_install(m:M.t)~(cls:oop)~(category:string)?(declare=false)(source:string):string=letmeth=trySt_parse.parse_methodsourcewithSt_parse.Error(pos,msg)->raise(Error(pos,msg))inletcm=compilem~cls~source~declaremethinC.installmcls(M.symbolmmeth.selector)cm~category;meth.selectorletrecrecompile(m:M.t)(cls:oop):(string*string)list=leterrors=C.selectorsmcls|>List.filter_map(funsel->matchC.local_methodmcls(M.symbolmsel)with|None->None|Somemeth->(letsource=B.sourcemmethinletcategory=Option.value(C.category_ofmclssel)~default:"as yet unclassified"intryignore(compile_and_installm~cls~categorysource);NonewithError(_,msg)->Some(C.namemcls^">>"^sel,msg)))inerrors@List.concat_map(recompilem)(C.subclassesmcls)letcompile_doit(m:M.t)~(receiver_class:oop)(source:string):oop=letmeth=trySt_parse.parse_doitsourcewithSt_parse.Error(pos,msg)->raise(Error(pos,msg))in(* the last statement's value is the answer: a Workspace's print it *)letbody=matchList.revmeth.bodywith|Exprx::rest->List.rev(Return(x,x.pos)::rest)|_->meth.bodyincompilem~cls:receiver_class~source~declare:true{methwithbody}