123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715(* 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_interp.mli *)moduleM=St_memorymoduleB=St_bytecodemoduleC=St_classtypeoop=M.ooptypehost={transcript:string->unit;milliseconds:unit->int;inspect:oop->unit;mouse:unit->int*int*int;}typeprocess_state=Runnable|Suspendedofstring|Finishedofoop|Terminatedtypeprocess={id:int;mutabletop:oop;mutablestate:process_state}typevm={m:M.t;mutablehost:host;prims:primitiveoptionarray;mutableextra_roots:unit->ooplist;mutableprocesses:processlist;(* the ones alive: the collector's roots *)mutablenext_id:int;(* the registers, cached from the active context *)mutableactive:oop;mutableslots:ooparray;(* the active context's fields *)mutablehome:oop;mutabletemps:ooparray;(* the home's fields *)mutablemeth:oop;mutablelits:ooparray;(* the method's fields: the header, then literal i at i + 1 *)mutablecode:Bytes.t;mutablereceiver:oop;mutableip:int;mutablesp:int;(* the index in slots of the top of the stack *)mutablestop:stringoption;mutablefinished:oopoption;(* the method cache *)cache_cls:intarray;cache_sel:intarray;cache_meth:intarray;mutablehits:int;mutablemisses:int;mutablecount:int;(* the contexts that returned and that nothing refers to, by size,
* to be used again (St_interp.mli, "Contexts recycled") *)pool:ooplistarray;}andprimitive=vm->int->boolexceptionFatalofstringletc_sender=0letc_ip=1letc_sp=2letc_method=3letc_closure=4letc_receiver=5letc_home=5letc_temps=6letcache_size=1024letcreate(m:M.t)(host:host):vm={m;host;prims=Array.make512None;extra_roots=(fun()->[]);processes=[];next_id=1;active=M.nil;slots=[||];home=M.nil;temps=[||];meth=M.nil;lits=[||];code=Bytes.empty;receiver=M.nil;ip=0;sp=0;stop=None;finished=None;cache_cls=Array.makecache_size(-1);cache_sel=Array.makecache_size(-1);cache_meth=Array.makecache_size0;hits=0;misses=0;count=0;pool=Array.make(c_temps+256)[];}letmemory(vm:vm):M.t=vm.mlethost(vm:vm):host=vm.hostletset_host(vm:vm)(h:host):unit=vm.host<-hletprimitives(vm:vm)=vm.primsletset_extra_roots(vm:vm)(f:unit->ooplist):unit=vm.extra_roots<-fletflush_cache(vm:vm):unit=Array.fillvm.cache_cls0cache_size(-1);Array.fillvm.cache_sel0cache_size(-1)letcache_stats(vm:vm):int*int=(vm.hits,vm.misses)letbytecodes_run(vm:vm):int=vm.count(*****************************************************************************)(* The registers *)(*****************************************************************************)letis_block_context(vm:vm)(ctx:oop):bool=M.class_ofvm.mctx=(M.knownvm.m).block_contextletis_closure_context(vm:vm)(ctx:oop):bool=(not(is_block_contextvmctx))&&M.fetchvm.mctxc_closure<>M.nil(* a closure's activation is a MethodContext that names its closure:
* its home is the closure's outer context's, up to a method's *)letrecclosure_home(m:M.t)(ctx:oop):oop=letclosure=M.fetchmctxc_closureinifclosure=M.nilthenctxelseclosure_homem(M.fetchmclosure0)letcontext_home(vm:vm)(ctx:oop):oop=ifis_block_contextvmctxthenM.fetchvm.mctxc_homeelseclosure_homevm.mctxletcontext_method(vm:vm)(ctx:oop):oop=M.fetchvm.m(context_homevmctx)c_methodletload(vm:vm)(ctx:oop):unit=letslots=M.fieldsvm.mctxinvm.active<-ctx;vm.slots<-slots;lethome=ifis_block_contextvmctxthenslots.(c_home)elsectxinvm.home<-home;vm.temps<-M.fieldsvm.mhome;vm.meth<-vm.temps.(c_method);vm.lits<-M.fieldsvm.mvm.meth;vm.code<-B.bytecodesvm.mvm.meth;vm.receiver<-vm.temps.(c_receiver);vm.ip<-M.int_ofslots.(c_ip);vm.sp<-M.int_ofslots.(c_sp)letsave(vm:vm):unit=ifvm.active<>M.nilthenbeginvm.slots.(c_ip)<-M.of_intvm.ip;vm.slots.(c_sp)<-M.of_intvm.spendletactive_context(vm:vm):oop=vm.activelethome_context(vm:vm):oop=vm.homeletip(vm:vm):int=vm.ipletactivate_context(vm:vm)(ctx:oop):unit=savevm;loadvmctx(*****************************************************************************)(* Contexts recycled *)(*****************************************************************************)letis_context(vm:vm)(o:oop):bool=(not(M.is_into))&&o<>M.nil&&letcls=M.class_ofvm.moandk=M.knownvm.mincls=k.method_context||cls=k.block_context(* someone holds this context now (thisContext, a block's home, a
* closure's outer context, a sender read): not to be used again when
* it returns *)letescape(vm:vm)(o:oop):unit=ifis_contextvmothenM.escapevm.mo(* the debugger is about to look at a process: its whole stack *)letescape_stack(vm:vm)(top:oop):unit=(* a Blue Book block that called itself is its own sender *)letseen=Hashtbl.create64inletrecupctx=ifis_contextvmctx&¬(Hashtbl.memseenctx)thenbeginHashtbl.replaceseenctx();M.escapevm.mctx;up(M.fetchvm.mctxc_sender)endinuptop(* a MethodContext of [size] fields, the first [c_temps + temps] nil:
* one from the pool, or a new one.
*
* claude: before the pool, every send was
* M.alloc vm.m ~cls:method_context (M.Pointers (Array.make size M.nil))
* an array OCaml's collector had to promote (the object table is old)
* and ours to sweep: a third of a send's time. *)letnew_context(vm:vm)(size:int)~(temps:int):oop=matchvm.pool.(size)with|ctx::rest->vm.pool.(size)<-rest;Array.fill(M.fieldsvm.mctx)0(c_temps+temps)M.nil;ctx|[]->M.allocvm.m~cls:(M.knownvm.m).method_context(M.Pointers(Array.makesizeM.nil))(* a context that returned: into the pool, unless someone may hold it
* (or it is a BlockContext, which is the block itself) *)letrelease(vm:vm)(ctx:oop):unit=letm=vm.minif(not(M.escapedmctx))&&M.class_ofmctx=(M.knownm).method_contextthenbeginletn=M.sizemctxinifn<Array.lengthvm.poolthenvm.pool.(n)<-ctx::vm.pool.(n)end(*****************************************************************************)(* The stack *)(*****************************************************************************)letpush(vm:vm)(v:oop):unit=vm.sp<-vm.sp+1;vm.slots.(vm.sp)<-vletstack(vm:vm)(i:int):oop=vm.slots.(vm.sp-i)letpop(vm:vm)(n:int):unit=vm.sp<-vm.sp-nletpop_top(vm:vm):oop=letv=vm.slots.(vm.sp)invm.sp<-vm.sp-1;vletbool(vm:vm)(b:bool):oop=ifbthen(M.knownvm.m).true_else(M.knownvm.m).false_letrequest_suspend(vm:vm)(label:string):unit=vm.stop<-Somelabel(*****************************************************************************)(* Sends *)(*****************************************************************************)letlookup(vm:vm)(cls:oop)(sel:oop):oopoption=leth=((clslxor(sellsl3))lsr1)land(cache_size-1)inifvm.cache_cls.(h)=cls&&vm.cache_sel.(h)=selthenbeginvm.hits<-vm.hits+1;Somevm.cache_meth.(h)endelsebeginvm.misses<-vm.misses+1;matchC.lookupvm.mclsselwith|Somemeth->vm.cache_cls.(h)<-cls;vm.cache_sel.(h)<-sel;vm.cache_meth.(h)<-meth;Somemeth|None->Noneend(* a new MethodContext for [meth], its receiver and arguments taken off
* the stack, and made active *)letactivate_method(vm:vm)(meth:oop)(header:int)(nargs:int):unit=ifB.num_args_ofheader<>nargsthenraise(Fatal"wrong number of arguments");letnum_temps=B.num_temps_ofheaderinletctx=new_contextvm(c_temps+B.frame_size_ofheader)~temps:num_tempsinleta=M.fieldsvm.mctxina.(c_sender)<-vm.active;a.(c_ip)<-M.of_int0;a.(c_sp)<-M.of_int(c_temps+num_temps-1);a.(c_method)<-meth;a.(c_receiver)<-vm.slots.(vm.sp-nargs);fori=0tonargs-1doa.(c_temps+i)<-vm.slots.(vm.sp-nargs+1+i)done;vm.sp<-vm.sp-nargs-1;savevm;loadvmctx(* claude: the header was decoded twice a send, here and in
* activate_method, each time into a record of four fields
* (B.header vm.m meth); now read once, as the SmallInteger it is *)letrecexecute(vm:vm)(meth:oop)(nargs:int):unit=letheader=M.int_of(M.fetchvm.mmeth0)inletprimitive=B.primitive_ofheaderinletok=primitive<>0&&matchvm.prims.(primitive)withSomep->pvmnargs|None->falseinifnotokthenactivate_methodvmmethheadernargsandsend_to_class(vm:vm)(cls:oop)(sel:oop)(nargs:int):unit=matchlookupvmclsselwith|Somemeth->executevmmethnargs|None->(* a Message with the selector and the arguments, sent to the
* receiver with #doesNotUnderstand: *)letargs=Array.initnargs(funi->vm.slots.(vm.sp-nargs+1+i))inletmsg=M.allocvm.m~cls:(M.knownvm.m).message(M.Pointers[|sel;M.new_arrayvm.margs|])inpopvmnargs;pushvmmsg;letdnu=M.symbolvm.m"doesNotUnderstand:"inifsel=dnuthenraise(Fatal("recursive doesNotUnderstand: in "^C.namevm.mcls));send_to_classvmclsdnu1letsend(vm:vm)(sel:oop)(nargs:int):unit=send_to_classvm(M.class_ofvm.m(stackvmnargs))selnargsletsuper_send(vm:vm)(sel:oop)(nargs:int):unit=send_to_classvm(C.superclassvm.m(B.method_classvm.mvm.meth))selnargs(*****************************************************************************)(* Returns *)(*****************************************************************************)letdead(vm:vm)(ctx:oop):bool=M.fetchvm.mctxc_ip=M.nilletkill(vm:vm)(ctx:oop):unit=M.storevm.mctxc_senderM.nil;M.storevm.mctxc_ipM.nilletcannot_return(vm:vm)(v:oop):unit=escapevmvm.active;pushvmvm.active;pushvmv;sendvm(M.symbolvm.m"cannotReturn:")1(* from the home's method: to the home's sender *)letreturn_from_method(vm:vm)(v:oop):unit=lethome=ifvm.temps.(c_closure)=M.nilthenvm.homeelseclosure_homevm.mvm.homeinlettarget=M.fetchvm.mhomec_senderinifdeadvmhomethencannot_returnvmvelseiftarget=M.nilthenbegin(* the bottom of the process *)killvmhome;ifvm.active<>homethenkillvmvm.active;vm.finished<-Somevendelseifdeadvmtargetthencannot_returnvmvelsebeginifvm.active=homethenbeginkillvmhome;releasevmhomeendelsebegin(* a block's "^": the contexts between it and its home have
* returned too, if the home is among its senders *)letm=vm.min(* (bounded: a Blue Book block that called itself is its own
* sender, a chain without an end) *)letrecbelowctxn=ctx<>M.nil&&n>0&&(ctx=home||below(M.fetchmctxc_sender)(n-1))inletbelowctx=belowctx100_000inletrecunwindctx=letsender=M.fetchmctxc_senderinkillvmctx;releasevmctx;ifctx<>homethenunwindsenderinifbelowvm.activethenunwindvm.activeelsebeginkillvmhome;killvmvm.activeendend;loadvmtarget;pushvmvend(* from a block, to its caller *)letreturn_from_block(vm:vm)(v:oop):unit=letcaller=vm.slots.(c_sender)inifcaller=M.nil||deadvmcallerthencannot_returnvmvelsebeginkillvmvm.active;releasevmvm.active;loadvmcaller;pushvmvend(*****************************************************************************)(* The bytecodes *)(*****************************************************************************)letspecial_arity=[|1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;2;0;0;1;0;1;0;1;0;1;1;0;1;0;0|]letfloor_divab=if(a<0)<>(b<0)&&amodb<>0then(a/b)-1elsea/bletfloor_modab=a-(b*floor_divab)(* the arithmetic special selectors on two SmallIntegers, done by the
* bytecode (Blue Book: "primitive" in the interpreter's own loop) *)letarith(vm:vm)(i:int):bool=leta=stackvm1andb=stackvm0inifnot(M.is_inta&&M.is_intb)thenfalseelseletx=M.int_ofaandy=M.int_ofbinletintr=ifM.fitsrthenbeginpopvm2;pushvm(M.of_intr);trueendelsefalseinletbooleanr=popvm2;pushvm(boolvmr);trueinmatchiwith|0->int(x+y)|1->int(x-y)|2->boolean(x<y)|3->boolean(x>y)|4->boolean(x<=y)|5->boolean(x>=y)|6->boolean(x=y)|7->boolean(x<>y)|8->(* claude: the product checked in floats, exact below 2^53, so
* that 32-bit ints on the web cannot wrap unseen *)letp=float_of_intx*.float_of_intyinifFloat.absp<=1073741823.thenint(x*y)elsefalse|9->ify<>0&&xmody=0thenint(x/y)elsefalse|10->ify<>0thenint(floor_modxy)elsefalse|13->ify<>0thenint(floor_divxy)elsefalse|14->int(xlandy)|15->int(xlory)|_->false(* claude: at: and at:put: on an Array, at: on a String, done by their
* bytecodes (192, 193) as the arithmetic ones are: no lookup, no
* primitive called through its closure. Only for those two classes
* exactly, and an index in range; anything else is sent. As for +, a
* method Array>>at: written in the Browser would not be called. *)letat(vm:vm):bool=letm=vm.minletr=stackvm1andi=stackvm0inifM.is_intr||not(M.is_inti)thenfalseelseletk=M.knownmandcls=M.class_ofmrandj=M.int_ofi-1inifcls=k.arraythenmatchM.bodymrwith|M.Pointersawhenj>=0&&j<Array.lengtha->popvm2;pushvma.(j);true|_->falseelseifcls=k.stringthenmatchM.bodymrwith|M.Bytesswhenj>=0&&j<Bytes.lengths->popvm2;pushvmk.characters.(Char.code(Bytes.getsj));true|_->falseelsefalseletat_put(vm:vm):bool=letm=vm.minletr=stackvm2andi=stackvm1inifM.is_intr||(not(M.is_inti))||M.class_ofmr<>(M.knownm).arraythenfalseelseletj=M.int_ofi-1inmatchM.bodymrwith|M.Pointersawhenj>=0&&j<Array.lengtha->letv=stackvm0ina.(j)<-v;popvm3;pushvmv;true|_->falseletjump_if(vm:vm)(cond:bool)(off:int):unit=letv=pop_topvminletk=M.knownvm.minifv=(ifcondthenk.true_elsek.false_)thenvm.ip<-vm.ip+offelseifv=(ifcondthenk.false_elsek.true_)then()elsebeginpushvmv;sendvm(M.symbolvm.m"mustBeBoolean")0endletnext_byte(vm:vm):int=letb=Char.code(Bytes.unsafe_getvm.codevm.ip)invm.ip<-vm.ip+1;bletstep(vm:vm):unit=letm=vm.minletb=next_bytevminifb<16thenpushvm(M.fieldsmvm.receiver).(b)elseifb<32thenpushvmvm.temps.(c_temps+b-16)elseifb<64thenpushvmvm.lits.(1+b-32)elseifb<96thenpushvm(M.fetchmvm.lits.(1+b-64)1)elseifb<104then(M.fieldsmvm.receiver).(b-96)<-pop_topvmelseifb<112thenvm.temps.(c_temps+b-104)<-pop_topvmelseifb<120thenbeginletk=M.knownminpushvm(matchbwith|112->vm.receiver|113->k.true_|114->k.false_|115->M.nil|_->M.of_int(b-117))endelseifb>=208thenbeginletnargs=(b-208)/16insendvmvm.lits.(1+(bland15))nargsendelseifb>=176thenbeginleti=b-176inletk=M.knownminifi<16&&arithvmithen()elseifi=16&&atvmthen()elseifi=17&&at_putvmthen()elseifi=22thenbeginletr=stackvm0=stackvm1inpopvm2;pushvm(boolvmr)endelseifi=23thenbeginletc=M.class_ofm(stackvm0)inpopvm1;pushvmcendelsesendvmk.special_selectors.(i)special_arity.(i)endelsematchbwith|120->return_from_methodvmvm.receiver|121->return_from_methodvm(M.knownm).true_|122->return_from_methodvm(M.knownm).false_|123->return_from_methodvmM.nil|124->return_from_methodvm(pop_topvm)|125->return_from_blockvm(pop_topvm)|128|129|130->(lete=next_bytevminleti=eland63inmatch(b,elsr6)with|128,0->pushvm(M.fieldsmvm.receiver).(i)|128,1->pushvmvm.temps.(c_temps+i)|128,2->pushvmvm.lits.(1+i)|128,_->pushvm(M.fetchmvm.lits.(1+i)1)|_,kind->letv=stackvm0inifb=130thenpopvm1;(matchkindwith|0->(M.fieldsmvm.receiver).(i)<-v|1->vm.temps.(c_temps+i)<-v|3->M.storemvm.lits.(1+i)1v|_->raise(Fatal"store into a literal")))|131->lete=next_bytevminsendvmvm.lits.(1+(eland31))(elsr5)|132->letnargs=next_bytevminleti=next_bytevminsendvmvm.lits.(1+i)nargs|133->lete=next_bytevminsuper_sendvmvm.lits.(1+(eland31))(elsr5)|134->letnargs=next_bytevminleti=next_bytevminsuper_sendvmvm.lits.(1+i)nargs|135->popvm1|136->pushvm(stackvm0)|137->M.escapemvm.active;pushvmvm.active|138->lete=next_bytevminletn=eland127inleta=Array.makenM.nilinife>=128thenbeginArray.blitvm.slots(vm.sp-n+1)a0n;popvmnend;pushvm(M.new_arrayma)|140|141|142->leti=next_bytevminletvector=vm.temps.(c_temps+next_bytevm)inifb=140thenpushvm(M.fetchmvectori)elsebeginM.storemvectori(stackvm0);ifb=142thenpopvm1end|143->(* a BlockClosure: 0 outerContext 1 startpc 2 numArgs, then
* what it copies, popped off the stack *)lete=next_bytevminletcopied=elsr4inletsize=next_bytevminletsize=(size*256)+next_bytevminleta=Array.make(3+copied)M.nilinM.escapemvm.active;a.(0)<-vm.active;a.(1)<-M.of_intvm.ip;a.(2)<-M.of_int(eland15);Array.blitvm.slots(vm.sp-copied+1)a3copied;popvmcopied;pushvm(M.allocm~cls:(M.knownm).block_closure(M.Pointersa));vm.ip<-vm.ip+size|_whenb>=144&&b<=151->vm.ip<-vm.ip+(b-143)|_whenb>=152&&b<=159->jump_ifvmfalse(b-151)|_whenb>=160&&b<=167->lete=next_bytevminvm.ip<-vm.ip+(((b-164)*256)+e)|_whenb>=168&&b<=171->lete=next_bytevminjump_ifvmtrue(((b-168)*256)+e)|_whenb>=172&&b<=175->lete=next_bytevminjump_ifvmfalse(((b-172)*256)+e)|_->raise(Fatal(Printf.sprintf"unknown bytecode %d"b))(*****************************************************************************)(* Processes *)(*****************************************************************************)letcollect(vm:vm):unit=savevm;letroots=(vm.active::List.map(funp->p.top)vm.processes)@vm.extra_roots()in(* the pool's contexts are garbage like any other *)Array.fillvm.pool0(Array.lengthvm.pool)[];ignore(M.gcvm.m~roots);flush_cachevmletnew_process(vm:vm)(ctx:oop):process=letp={id=vm.next_id;top=ctx;state=Runnable}invm.next_id<-vm.next_id+1;vm.processes<-p::vm.processes;pletspawn_method(vm:vm)(meth:oop)(receiver:oop):process=leth=B.headervm.mmethinleta=Array.make(c_temps+h.frame_size)M.nilina.(c_ip)<-M.of_int0;a.(c_sp)<-M.of_int(c_temps+h.num_temps-1);a.(c_method)<-meth;a.(c_receiver)<-receiver;new_processvm(M.allocvm.m~cls:(M.knownvm.m).method_context(M.Pointersa))(* a process sending one message: its bottom context runs a little
* method made for it, "push the receiver and the arguments, send,
* return the answer" -- so that the send goes through everything a
* send does: primitives, doesNotUnderstand: *)letspawn(vm:vm)(receiver:oop)(selector:string)(args:ooplist):process=letn=List.lengthargsinletcode=Bytes.create(n+4)infori=0tondoBytes.setcodei(Char.chr(16+i))done;Bytes.setcode(n+1)(Char.chr131);Bytes.setcode(n+2)(Char.chr(nlsl5));Bytes.setcode(n+3)(Char.chr124);letheader={B.primitive=0;num_args=n+1;num_temps=n+1;frame_size=(2*n)+4}inletsel=M.symbolvm.mselectorinletmeth=B.new_methodvm.m~header~literals:[|sel|]~bytecodes:code~selector:(M.symbolvm.m"send")~cls:M.nil~source:""~pcmap:[(n+1,0,0)]~temp_names:(List.init(n+1)(funi->ifi=0then"receiver"else"arg"^string_of_inti))inletp=spawn_methodvmmethM.nilinleta=M.fieldsvm.mp.topinList.iteri(funiv->a.(c_temps+i)<-v)(receiver::args);pletforget(vm:vm)(p:process):unit=vm.processes<-List.filter(funq->q!=p)vm.processesletrun?stop_when(vm:vm)(p:process)~(budget:int):unit=matchp.statewith|Suspended_|Finished_|Terminated->()|Runnable->vm.stop<-None;vm.finished<-None;(* a debugger stepping holds contexts (St_debug.mli) *)ifstop_when<>Nonethenescape_stackvmp.top;loadvmp.top;letn=ref0in(trywhile!n<budget&&vm.stop=None&&vm.finished=Nonedomatchstop_whenwith|Somefwhen!n>0&&fvm->vm.stop<-Some"Step"|_->stepvm;incrn;if!nland1023=0&&M.allocatedvm.m>100_000+(M.livevm.m/2)thencollectvmdonewith|Fatalmsg->vm.stop<-Some("Virtual machine: "^msg)|Invalid_argumentmsg->vm.stop<-Some("Virtual machine: "^msg));vm.count<-vm.count+!n;(matchvm.finishedwith|Somev->p.state<-Finishedv;p.top<-M.nil;forgetvmp|None->(savevm;p.top<-vm.active;matchvm.stopwith|Somelabel->p.state<-Suspendedlabel;escape_stackvmp.top|None->()));vm.active<-M.nilletresume(p:process):unit=matchp.statewithSuspended_->p.state<-Runnable|_->()letsuspend(p:process)(label:string):unit=matchp.statewithRunnable->p.state<-Suspendedlabel|_->()letterminate(vm:vm)(p:process):unit=p.state<-Terminated;forgetvmpletfinish(vm:vm)(p:process)~(budget:int):(oop,string)result=runvmp~budget;letr=matchp.statewith|Finishedv->Okv|Suspendedlabel->Errorlabel|Runnable->Error"Too long: stopped"|Terminated->Error"Terminated"inforgetvmp;rletcall(vm:vm)?(budget=20_000_000)(receiver:oop)(selector:string)(args:ooplist):(oop,string)result=finishvm(spawnvmreceiverselectorargs)~budgetletprint_string(vm:vm)(o:oop):string=matchcallvm~budget:5_000_000o"printString"[]with|OkswhenM.class_ofvm.ms=(M.knownvm.m).string->M.string_ofvm.ms|Ok_->"<printString not a String>"|Errore->"<printString failed: "^e^">"letevaluate(vm:vm)?(budget=20_000_000)?(receiver=M.nil)(text:string):(oop,string)result=matchSt_compile.compile_doitvm.m~receiver_class:(M.class_ofvm.mreceiver)textwith|meth->finishvm(spawn_methodvmmethreceiver)~budget|exceptionSt_compile.Error(_,msg)->Errormsg