From 500383702e1c5d912db6c57335ecb27f1a48c2fe Mon Sep 17 00:00:00 2001 From: roytam1 Date: Sat, 12 Sep 2020 12:54:45 +0800 Subject: [PATCH] =?UTF-8?q?import=20changes=20from=20`dev'=20branch=20of?= =?UTF-8?q?=20rmottola/Arctic-Fox:=20-=20Bug=201043863=20-=20OpenedConnect?= =?UTF-8?q?ion.prototype.executeBeforeShutdown.=20r=3Dmak=20(35dec49b1)=20?= =?UTF-8?q?-=20Bug=201157946=20-=20Early=20return=20if=20aResponse.sources?= =?UTF-8?q?=20is=20undefined;r=3Dfitzgen=20(c730d5693)=20-=20Bug=201157914?= =?UTF-8?q?=20-=20Don't=20re-render=20the=20graphs=20until=20selection=20i?= =?UTF-8?q?s=20done;=20r=3Djsantell=20(6af87a63b)=20-=20Bug=201160332=20-?= =?UTF-8?q?=20Correctly=20check=20that=20the=20mouse=20is=20active=20in=20?= =?UTF-8?q?graphs=20to=20clean=20up=20rerender=20jank,=20a=20rebase=20erro?= =?UTF-8?q?r=20from=201157914.=20r=3Dvp=20(7d761d64a)=20-=20Bug=201150011?= =?UTF-8?q?=20-=20Fix=20GC=20hash=20table=20checks=20to=20work=20in=20rele?= =?UTF-8?q?ase=20builds=20r=3Dnbp=20(b7f1436a4)=20-=20Bug=20996982=20-=20F?= =?UTF-8?q?ix=20Debugger=20script=20delazification=20logic=20to=20account?= =?UTF-8?q?=20for=20relazified=20clones.=20(r=3Dbz)=20(df51ca106)=20-=20Bu?= =?UTF-8?q?g=201157963=20-=20Don't=20delazify=20functions=20about=20to=20b?= =?UTF-8?q?e=20finalized.=20(r=3Djimb)=20(a72665ff0)=20-=20Bug=201155618?= =?UTF-8?q?=20-=20Fix=20some=20places=20where=20OOM=20errors=20are=20not?= =?UTF-8?q?=20reported=20to=20th=E2=80=A6e=20context=20r=3Dterrence=20(37a?= =?UTF-8?q?0f3a0b)=20-=20Bug=201069979=20-=20Don't=20throw=20away=20timezo?= =?UTF-8?q?ne-strings=20with=20dots=20in=20Date()=20r=3Devilpie=20(9443f61?= =?UTF-8?q?75)=20-=20Bug=201160356=20-=20Make=20Date.UTC=20conform=20to=20?= =?UTF-8?q?ES3-6=20in=20converting=20*all*=20arguments=20to=20number=20bef?= =?UTF-8?q?ore=20computing=20the=20return=20value.=20r=3Devilpie=20(f5d7c9?= =?UTF-8?q?fba)=20-=20Bug=201160356=20-=20Reorganize=20the=20code=20for=20?= =?UTF-8?q?the=20Date=20function/constructor=20into=20three=20separate=20m?= =?UTF-8?q?ethods,=20to=20be=20more=20consistent=20with=20ES6's=20definiti?= =?UTF-8?q?on=20of=20it.=20Don't=20change=20the=20actual=20algorithm=20yet?= =?UTF-8?q?=20--=20this=20is=20just=20code=20motion.=20r=3Devilpie=20(9f4e?= =?UTF-8?q?d070f)=20-=20Bug=201160356=20-=20Make=20new=20Date(arg1,=20arg2?= =?UTF-8?q?,=20...)=20conform=20to=20ES3-6=20in=20converting=20*all*=20arg?= =?UTF-8?q?uments=20to=20number=20before=20computing=20the=20return=20valu?= =?UTF-8?q?e.=20r=3Devilpie=20(92da1cef7)=20-=20pointer=20style=20(d433254?= =?UTF-8?q?89)=20-=20Bug=201160535=20part=201=20-=20Give=20JSFunction=20it?= =?UTF-8?q?s=20own=20AllocKind.=20r=3Dterrence=20(952baab50)=20-=20Bug=201?= =?UTF-8?q?160535=20part=202=20-=20Do=20function=20relazification=20as=20p?= =?UTF-8?q?art=20of=20a=20new=20GC=20phase=20instead=20of=20during=20marki?= =?UTF-8?q?ng.=20r=3Dterrence=20(3fa0357e6)=20-=20Bug=201160535=20part=203?= =?UTF-8?q?=20-=20Make=20the=20LazyScript=20->=20JSScript=20pointer=20weak?= =?UTF-8?q?.=20r=3Djonco,terrence=20(cf1276e6a)=20-=20Bug=201160535=20part?= =?UTF-8?q?=204=20-=20Remove=20an=20assert=20in=20XDRInterpretedFunction?= =?UTF-8?q?=20that's=20now=20bogus.=20r=3Dnbp=20(8220d24b0)=20-=20Bug=2011?= =?UTF-8?q?60535=20part=205=20-=20Remove=20the=20now=20bogus=20!maybeScrip?= =?UTF-8?q?t=20check=20in=20CreateLazyScriptsForCompartment.=20r=3Dshu=20(?= =?UTF-8?q?f833671cd)=20-=20Bug=201163091=20-=20Handle=20unboxed=20arrays?= =?UTF-8?q?=20in=20jsarray.cpp=20fast=20paths,=20r=3Djandem.=20(daaa46019)?= =?UTF-8?q?=20-=20reapply=20(better)=20to=20jsarray.cpp=20Bug=201389974=20?= =?UTF-8?q?Fix=20False=20positive=20rooting=20(bf526deb2)=20-=20Bug=201165?= =?UTF-8?q?904=20-=20Don't=20call=20methods=20on=20null=20pointers=20to=20?= =?UTF-8?q?fix=20some=20-fsanitize=3Dnull=20errors.=20r=3Dterrence=20(0d48?= =?UTF-8?q?886e6)=20-=20Bug=201162622=20-=20Check=20fewer=20traced=20thing?= =?UTF-8?q?s=20as=20it's=20too=20slow=20to=20check=20all=20edges;=20r=3Dsf?= =?UTF-8?q?ink=20(0cd9b9634)=20-=20Bug=201162296=20-=20Use=20generic=20val?= =?UTF-8?q?ue=20traversal=20when=20scanning=20unboxed=20memory;=20r=3Djonc?= =?UTF-8?q?o=20(57904891a)=20-=20Bug=201162590=20-=20Change=20the=20name?= =?UTF-8?q?=20of=20the=202-arg=20traverse=20to=20traverseEdge;=20r=3Dsfink?= =?UTF-8?q?=20(fd4d9fe3c)=20-=20Bug=201150639=20-=20Use=20a=20stricter=20o?= =?UTF-8?q?ff-thread=20check=20in=20triggerZoneGC;=20r=3Dbhackett=20(fad3d?= =?UTF-8?q?db51)=20-=20Bug=201149752=20-=20Cancel=20GC=20caused=20by=20use?= =?UTF-8?q?r=20inactivity=20if=20the=20user=20becomes=20active=20again=20r?= =?UTF-8?q?=3Dterrence=20r=3Dsmaug=20(43129e9ec)=20-=20Bug=201154441=20-?= =?UTF-8?q?=20imported=20patch=20budget,=20r=3Dterrence=20(b3cbb9a23)=20-?= =?UTF-8?q?=20pointer=20style=20(8cc66c5eb)=20-=20pointer=20style=20(dc350?= =?UTF-8?q?9b86)=20-=20Bug=201155455=20-=20Relax=20assertion=20to=20take?= =?UTF-8?q?=20account=20of=20breakpoints=20added=20during=20incremental=20?= =?UTF-8?q?sweeping=20r=3Dterrence=20(8a70639be)=20-=20Bug=201150253=20-?= =?UTF-8?q?=20Part=201:=20SpiderMonkey=20should=20call=20an=20embedder-pro?= =?UTF-8?q?vided=20callback=20(57af26988)=20-=20Bug=201137536,=20part=203?= =?UTF-8?q?=20-=20Move=20the=20top=20level=20DeferredFinalize=20functions?= =?UTF-8?q?=20into=20their=20own=20file.=20r=3Dsmaug=20(ff9931f27)=20-=20B?= =?UTF-8?q?ug=201150253=20-=20Part=202:=20Gecko=20should=20provide=20a=20c?= =?UTF-8?q?allback=20for=20SpiderMonkey=20to=20enqueue=20the=20onGarbageCo?= =?UTF-8?q?llection=20hook=20runnable;=20r=3Dmccr8=20(80006d63b)=20-=20Bug?= =?UTF-8?q?=201150253=20-=20Part=203:=20Migrate=20onGarbageCollection=20te?= =?UTF-8?q?sts;=20r=3Dsfink=20--HG--=20rename=20:=20js/src/jit-test/tests/?= =?UTF-8?q?debug/Memory-onGarbageCollection-04.js=20=3D>=20js/xpconnect/te?= =?UTF-8?q?sts/unit/test=5FonGarbageCollection-04.js=20(4e55ef74e)=20-=20B?= =?UTF-8?q?ug=201160567=20-=20Assert=20that=20object=20derived=20types=20a?= =?UTF-8?q?re=20not=20exposed=20in=20the=20API;=20r=3Djonco=20(f45b01740)?= =?UTF-8?q?=20-=20Bug=201160163=20-=20Refactor=20arena=20decommit=20so=20w?= =?UTF-8?q?e=20don't=20have=20to=20pass=20dummy=20thing=20kind=20to=20allo?= =?UTF-8?q?cateArena()=20r=3Dterrence=20(11bf82efb)=20-=20Bug=201157382=20?= =?UTF-8?q?-=20Fix=20possible=20data=20race=20caused=20by=20accessing=20th?= =?UTF-8?q?e=20mark=20bits=20of=20cells=20in=20another=20runtime=20r=3Dter?= =?UTF-8?q?rence=20(ed6e851d0)=20-=20Bug=201163059=20-=20Add=20a=20more=20?= =?UTF-8?q?convenient=20wrapper=20for=20isAtomsZone;=20r=3Dsfink=20(7f2c43?= =?UTF-8?q?52a)=20-=20Bug=201163790=20-=20Part=201:=20Share=20unboxed=20tr?= =?UTF-8?q?ace=20list=20traversal=20between=20tenuring=20and=20marking;=20?= =?UTF-8?q?r=3Dbhackett=20(be44e89d3)=20-=20pointer=20style=20(ab055fdfa)?= =?UTF-8?q?=20-=20Bug=201163790=20-=20Part=202:=20Share=20inlined=20Class?= =?UTF-8?q?=20tracing=20between=20marking=20and=20tenuring;=20r=3Dbhackett?= =?UTF-8?q?=20(8af0179a9)=20-=20poitner=20style=20and=20whitespace=20clean?= =?UTF-8?q?up=20(7e2c17317)=20-=20Bug=20891107=20-=20Part=201:=20Show=20in?= =?UTF-8?q?formation=20about=20value,=20type,=20function,=20and=20argument?= =?UTF-8?q?=20number=20in=20type=20conversion=20error=20messages=20in=20js?= =?UTF-8?q?-ctypes.=20r=3Djorendorff=20(2e461d5d3)=20-=20Bug=201143281=20-?= =?UTF-8?q?=20Check=20argument=20type=20in=20StructType.prototype.addressO?= =?UTF-8?q?fField.=20r=3Djorendorff=20(2d218c368)=20-=20Bug=20891107=20-?= =?UTF-8?q?=20Part=202:=20Report=20argument=20length=20error=20as=20TypeEr?= =?UTF-8?q?ror=20in=20js-ctypes.=20r=3Djorendorff=20(0e2fa7f9a)=20-=20Bug?= =?UTF-8?q?=20891107=20-=20Part=203:=20Report=20argument=20type=20error=20?= =?UTF-8?q?as=20TypeError=20in=20js-ctypes.=20r=3Djorendorff=20(19d4604da)?= =?UTF-8?q?=20-=20Bug=20987514,=20part=201=20-=20Move=20the=20contents=20o?= =?UTF-8?q?f=20jsreflect.h=20into=20the=20sole=20file=20that=20includes=20?= =?UTF-8?q?it=20(jsreflect.cpp).=20No=20change=20in=20behavior.=20r=3DWald?= =?UTF-8?q?o.=20(b5d7dd976)=20-=20pointer=20style=20(949c6d6b9)=20-=20Bug?= =?UTF-8?q?=201157415=20-=20Tweak=20XPConnect=20stack=20size=20for=2032bit?= =?UTF-8?q?=20Windows.=20(r=3Dbholley)=20(59a75dcbf)=20-=20Bug=201158223?= =?UTF-8?q?=20-=20Tweak=20XPConnect=20stack=20size=20on=2064bit=20Windows.?= =?UTF-8?q?=20(r=3Dbholley)=20(e6ccaf2a9)=20-=20No=20bug=20-=20Bump=20the?= =?UTF-8?q?=20Windows=20stack=20frame=20size.=20r=3Dshu=20(3fcdcb6d1)=20-?= =?UTF-8?q?=20Bug=201010556=20-=20Bump=20ASAN=20kTrustedScriptBuffer=20con?= =?UTF-8?q?stant,=20to=20account=20for=20the=20new=20frame=20size.=20r=3Db?= =?UTF-8?q?holley=20(3b9ae31f6)=20-=20Bug=201067610=20-=20Refactor=20backt?= =?UTF-8?q?racking=20allocator=20to=20handle=20grouped=20registers=20bette?= =?UTF-8?q?r,=20r=3Dsunfish.=20(a4ea2aa7e)=20-=20Bug=201167815=20-=20Switc?= =?UTF-8?q?h=20toMoveOperand=20to=20pass=20by=20value.=20r=3Dbhackett=20(0?= =?UTF-8?q?156abb47)=20-=20pointer=20style=20(0ebc66f85)=20-=20Bug=2011688?= =?UTF-8?q?07=20-=20Move=20MacroAssemblerSpecific::framePushed=5F=20fields?= =?UTF-8?q?=20to=20the=20generic=20MacroAssembler.=20r=3Djandem=20(28caaac?= =?UTF-8?q?48)=20-=20=20Bug=201168750=20-=20SharedStubs:=20(part1)=20Renam?= =?UTF-8?q?e=20BaselineRegisters.h=20and=20BaselineHelpers.h,=20r=3Djandem?= =?UTF-8?q?=20(ac876a39d)=20-=20Bug=201155618=20-=20Report=20allocation=20?= =?UTF-8?q?failure=20to=20context=20for=20baseline=20ICStubs=20r=3Djandem?= =?UTF-8?q?=20(04ef0592b)=20-=20Bug=201157231=20-=20Optimize=20calls=20to?= =?UTF-8?q?=20own=20property=20setters.=20r=3Defaust=20(734e9a9ff)=20-=20B?= =?UTF-8?q?ug=201141865=20-=20Part=201:=20Make=20two=20ICCall=5FFallback,?= =?UTF-8?q?=20one=20for=20constructing=20invocations.=20(r=3Djandem)=20(70?= =?UTF-8?q?7c2db57)=20-=20pointer=20style=20(f73948d66)=20-=20Bug=20115762?= =?UTF-8?q?4:=20A=20few=20AsmJSValidate=20cleanups;=20r=3Dluke=20(1f801ed0?= =?UTF-8?q?d)=20-=20Bug=201157624:=20Remove=20asm.js=20ternary=20optimizat?= =?UTF-8?q?ions=20and=20activate=20the=20FoldTest=20optimization=20pass=20?= =?UTF-8?q?for=20asm.js;=20r=3Dluke=20(3be4020ca)=20-=20Bug=201157624:=20A?= =?UTF-8?q?=20few=20free=20asm.js=20tests;=20r=3Dluke=20(63a07c8c8)=20-=20?= =?UTF-8?q?Bug=201155211=20-=20SIMD:=20rename=20lane=20mutators=20-=20load?= =?UTF-8?q?/store.=20r=3Dbbouvier=20(e0ded7107)=20-=20Bug=201147403=20part?= =?UTF-8?q?=201=20-=20Move=20Sprinter=20into=20its=20own=20header=20and=20?= =?UTF-8?q?add=20a=20FILE=20&=20LifoAlloc=20variants.=20r=3Dh4writer=20(2d?= =?UTF-8?q?80c984b)=20-=20pointer=20style=20(b2dffa01f)=20-=20Bug=20115989?= =?UTF-8?q?9:=20IonMonkey:=20Fix=20folding=20of=20~~x,=20r=3Dnbp=20(f77eae?= =?UTF-8?q?89d)=20-=20Bug=201154971=20-=20ValueNumbering:=20Skip=20finding?= =?UTF-8?q?=20the=20leader=20if=20the=20simplified=20instruction=20existed?= =?UTF-8?q?=20before=20the=20simplification.=20r=3Dsunfish=20(59262ac7f)?= =?UTF-8?q?=20-=20coding=20style=20and=20reshuffle=20(6b9ad2920)=20-=20Bug?= =?UTF-8?q?=201147403=20part=202=20-=20IonMonkey:=20Use=20GenericPrinter&?= =?UTF-8?q?=20instead=20of=20FILE*=20for=20*::dump=20functions.=20r=3Dh4wr?= =?UTF-8?q?iter=20(a7f0e88fb)=20-=20pointer=20style=20(62208cb4e)=20-=20Bu?= =?UTF-8?q?g=20979094=20-=20Fix=20ending=20location=20of=20variable=20decl?= =?UTF-8?q?aration.=20r=3Djimb=20(af438060c)=20-=20Bug=20748550=20-=20Remo?= =?UTF-8?q?ve=20support=20for=20|for=20(...=20=3D=20...=20in/of=20...)|=20?= =?UTF-8?q?now=20that=20ES6=20has=20removed=20it.=20r=3Djorendorff=20(2368?= =?UTF-8?q?3a4c8)=20-=20Bug=201155472=20-=20Reorder=20the=20various=20stat?= =?UTF-8?q?ement=20items=20in=20Parser::statement=20to=20correspond=20to?= =?UTF-8?q?=20the=20ordering=20in=20the=20Statement=20grammar=20production?= =?UTF-8?q?.=20r=3Defaust=20(d828017c1)=20-=20Bug=201151931=20-=20Part=201?= =?UTF-8?q?:=20Avoid=20warning=20about=20unreachable=20code=20after=20retu?= =?UTF-8?q?rn=20statement=20in=20some=20asm.js=20tests.=20r=3DWaldo=20(ab8?= =?UTF-8?q?5fb859)=20-=20Bug=201153656=20-=20Test=20class=20only=20if=20av?= =?UTF-8?q?ailable=20in=20semicolon-less-return.js.=20r=3Defaust=20(0b0e6b?= =?UTF-8?q?bc6)=20-=20Bug=201151931=20-=20Part=202:=20Warn=20about=20unrea?= =?UTF-8?q?chable=20code=20after=20return=20statement.=20r=3DWaldo=20(a2ec?= =?UTF-8?q?0aa5f)=20-=20Bug=201158547=20-=20Removes=20the=20useless=20decl?= =?UTF-8?q?aration=20in=20Parser.cpp.=20r=3Djorendorff=20(9ac67193b)=20-?= =?UTF-8?q?=20pointer=20style=20(f72b5c598)=20-=20Bug=201155472=20-=20Add?= =?UTF-8?q?=20the=20ES6=20grammar=20parametrization=20to=20all=20the=20Par?= =?UTF-8?q?ser=20methods,=20so=20that=20the=20permissibility=20of=20|in|,?= =?UTF-8?q?=20|yield|=20as=20keyword,=20&c.=20is=20specified=20directly,?= =?UTF-8?q?=20not=20by=20inspecting=20instantaneous=20statefulness.=20Don'?= =?UTF-8?q?t=20change=20the=20statefulness=20yet,=20tho=20--=20stop=20rely?= =?UTF-8?q?ing=20on=20it=20(where=20appropriate)=20in=20a=20later=20patch.?= =?UTF-8?q?=20r=3Defaust=20(589e7d8c1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dom/base/nsJSEnvironment.cpp | 12 + dom/bindings/BindingUtils.h | 4 +- dom/xbl/nsXBLBinding.cpp | 2 +- js/public/Debug.h | 24 +- js/public/GCAPI.h | 71 +- js/public/HeapAPI.h | 4 + js/public/RootingAPI.h | 9 +- js/public/SliceBudget.h | 10 +- js/src/asmjs/AsmJSFrameIterator.cpp | 2 + js/src/asmjs/AsmJSLink.cpp | 6 +- js/src/asmjs/AsmJSModule.cpp | 9 +- js/src/asmjs/AsmJSValidate.cpp | 191 +- js/src/builtin/Object.cpp | 2 +- .../ReflectParse.cpp} | 85 +- js/src/builtin/SIMD.cpp | 1 + js/src/builtin/SIMD.h | 52 +- js/src/builtin/SymbolObject.cpp | 2 +- js/src/builtin/TestingFunctions.cpp | 22 +- js/src/builtin/TypedObject.h | 28 +- js/src/ctypes/CTypes.cpp | 1073 ++++-- js/src/ctypes/CTypes.h | 28 + js/src/ctypes/Library.cpp | 5 +- js/src/ctypes/ctypes.msg | 27 +- js/src/frontend/BytecodeCompiler.cpp | 5 +- js/src/frontend/FullParseHandler.h | 21 + js/src/frontend/Parser.cpp | 785 +++-- js/src/frontend/Parser.h | 135 +- js/src/frontend/SyntaxParseHandler.h | 35 +- js/src/frontend/TokenStream.cpp | 27 - js/src/frontend/TokenStream.h | 9 - js/src/gc/Allocator.cpp | 5 +- js/src/gc/Barrier.h | 1 + js/src/gc/GCRuntime.h | 1 + js/src/gc/Heap.h | 38 +- js/src/gc/Marking.cpp | 301 +- js/src/gc/Marking.h | 6 +- js/src/gc/Nursery-inl.h | 1 - js/src/gc/Nursery.h | 1 - js/src/gc/Statistics.cpp | 19 +- js/src/gc/Statistics.h | 14 +- js/src/gc/Zone.cpp | 43 +- js/src/gc/Zone.h | 3 + .../irregexp/NativeRegExpMacroAssembler.cpp | 2 + js/src/jit-test/lib/asserts.js | 32 + js/src/jit-test/lib/class.js | 1 + js/src/jit-test/tests/SIMD/load.js | 96 +- js/src/jit-test/tests/SIMD/store.js | 61 +- js/src/jit-test/tests/asm.js/testBasic.js | 2 +- .../jit-test/tests/asm.js/testControlFlow.js | 14 +- .../jit-test/tests/asm.js/testExpressions.js | 35 +- js/src/jit-test/tests/asm.js/testFFI.js | 2 +- js/src/jit-test/tests/asm.js/testFloat32.js | 2 + js/src/jit-test/tests/asm.js/testResize.js | 2 +- .../tests/asm.js/testSIMD-load-store.js | 176 +- js/src/jit-test/tests/asm.js/testZOOB.js | 244 ++ .../jit-test/tests/auto-regress/bug590772.js | 2 +- .../jit-test/tests/auto-regress/bug596817.js | 8 +- ...ss-return.js => statement-after-return.js} | 204 +- js/src/jit-test/tests/closures/bug540136.js | 17 - js/src/jit-test/tests/closures/bug540348.js | 3 - .../jit-test/tests/ctypes/AddressOfField.js | 12 + .../tests/ctypes/argument-length-abi.js | 9 + .../tests/ctypes/argument-length-array.js | 15 + .../tests/ctypes/argument-length-cdata.js | 15 + .../tests/ctypes/argument-length-ctypes.js | 11 + .../tests/ctypes/argument-length-finalizer.js | 16 + .../tests/ctypes/argument-length-function.js | 11 + .../tests/ctypes/argument-length-int64.js | 36 + .../tests/ctypes/argument-length-pointer.js | 11 + .../tests/ctypes/argument-length-primitive.js | 11 + .../tests/ctypes/argument-length-struct.js | 17 + .../tests/ctypes/argument-type-array.js | 17 + .../tests/ctypes/argument-type-ctypes.js | 13 + .../tests/ctypes/argument-type-function.js | 9 + .../tests/ctypes/argument-type-int64.js | 28 + .../tests/ctypes/argument-type-pointer.js | 9 + .../tests/ctypes/argument-type-struct.js | 17 + .../jit-test/tests/ctypes/conversion-array.js | 36 + .../jit-test/tests/ctypes/conversion-error.js | 14 + .../tests/ctypes/conversion-finalizer.js | 61 + .../tests/ctypes/conversion-function.js | 33 + .../jit-test/tests/ctypes/conversion-int64.js | 20 + .../ctypes/conversion-native-function.js | 37 + .../tests/ctypes/conversion-pointer.js | 29 + .../tests/ctypes/conversion-primitive.js | 44 + .../tests/ctypes/conversion-struct.js | 36 + .../tests/ctypes/conversion-to-primitive.js | 20 + .../debug/Memory-onGarbageCollection-01.js | 57 - .../debug/Memory-onGarbageCollection-02.js | 69 - .../debug/Memory-onGarbageCollection-03.js | 18 - .../debug/Script-getAllColumnOffsets-02.js | 2 +- .../debug/Script-getAllColumnOffsets-04.js | 2 +- .../debug/Script-getAllColumnOffsets-05.js | 2 +- .../jit-test/tests/debug/Script-lineCount.js | 23 + js/src/jit-test/tests/gc/bug-1155455.js | 17 + js/src/jit-test/tests/gc/incremental-abort.js | 49 + .../tests/heap-analysis/byteSize-of-object.js | 2 +- js/src/jit-test/tests/ion/bug1154971.js | 10 + js/src/jit-test/tests/ion/bug1159899.js | 5 + .../tests/ion/recover-lambdas-bug1118911.js | 2 +- js/src/jit-test/tests/jaeger/bug583684.js | 8 - js/src/jit-test/tests/jaeger/bug719758.js | 10 - .../saved-stacks/async-max-frame-count.js | 9 +- js/src/jit-test/tests/xdr/lazy.js | 4 + js/src/jit/AliasAnalysis.cpp | 27 +- js/src/jit/BacktrackingAllocator.cpp | 3095 ++++++++++------- js/src/jit/BacktrackingAllocator.h | 751 +++- js/src/jit/BaselineBailouts.cpp | 2 +- js/src/jit/BaselineCompiler.cpp | 2 +- js/src/jit/BaselineDebugModeOSR.cpp | 2 +- js/src/jit/BaselineFrameInfo.h | 2 +- js/src/jit/BaselineIC.cpp | 235 +- js/src/jit/BaselineIC.h | 390 ++- js/src/jit/BaselineInspector.cpp | 7 +- js/src/jit/C1Spewer.cpp | 177 +- js/src/jit/C1Spewer.h | 13 +- js/src/jit/CodeGenerator.cpp | 136 +- js/src/jit/EffectiveAddressAnalysis.h | 2 + js/src/jit/InlineList.h | 22 +- js/src/jit/Ion.cpp | 15 +- js/src/jit/IonAnalysis.cpp | 29 +- js/src/jit/IonAnalysis.h | 3 +- js/src/jit/IonBuilder.cpp | 5 +- js/src/jit/IonCaches.cpp | 1 + js/src/jit/JSONSpewer.cpp | 140 +- js/src/jit/JSONSpewer.h | 8 +- js/src/jit/JitCompartment.h | 14 +- js/src/jit/JitFrames.h | 7 +- js/src/jit/JitSpewer.cpp | 21 +- js/src/jit/JitSpewer.h | 7 +- js/src/jit/JitcodeMap.cpp | 2 +- js/src/jit/LIR-Common.h | 24 +- js/src/jit/LIR.cpp | 131 +- js/src/jit/LIR.h | 12 +- js/src/jit/LiveRangeAllocator.cpp | 996 ------ js/src/jit/LiveRangeAllocator.h | 758 ---- js/src/jit/Lowering.cpp | 23 +- js/src/jit/MCallOptimize.cpp | 24 +- js/src/jit/MIR.cpp | 295 +- js/src/jit/MIR.h | 48 +- js/src/jit/MIRGraph.cpp | 73 +- js/src/jit/MIRGraph.h | 7 +- js/src/jit/MacroAssembler-inl.h | 117 + js/src/jit/MacroAssembler.cpp | 70 +- js/src/jit/MacroAssembler.h | 130 +- js/src/jit/RangeAnalysis.cpp | 96 +- js/src/jit/RangeAnalysis.h | 5 +- js/src/jit/RegisterAllocator.cpp | 12 +- js/src/jit/Safepoints.cpp | 14 +- .../{BaselineHelpers.h => SharedICHelpers.h} | 16 +- ...aselineRegisters.h => SharedICRegisters.h} | 16 +- js/src/jit/Snapshots.cpp | 48 +- js/src/jit/Snapshots.h | 6 +- js/src/jit/StupidAllocator.cpp | 10 +- js/src/jit/VMFunctions.cpp | 59 +- js/src/jit/ValueNumbering.cpp | 9 +- js/src/jit/arm/BaselineIC-arm.cpp | 2 +- js/src/jit/arm/CodeGenerator-arm.cpp | 7 +- js/src/jit/arm/CodeGenerator-arm.h | 18 +- js/src/jit/arm/MacroAssembler-arm.cpp | 63 +- js/src/jit/arm/MacroAssembler-arm.h | 54 +- js/src/jit/arm/MoveEmitter-arm.cpp | 2 + ...ineHelpers-arm.h => SharedICHelpers-arm.h} | 8 +- ...egisters-arm.h => SharedICRegisters-arm.h} | 6 +- js/src/jit/arm/Trampoline-arm.cpp | 4 +- js/src/jit/mips/BaselineIC-mips.cpp | 2 +- js/src/jit/mips/CodeGenerator-mips.cpp | 25 +- js/src/jit/mips/CodeGenerator-mips.h | 32 +- js/src/jit/mips/MacroAssembler-mips.cpp | 63 +- js/src/jit/mips/MacroAssembler-mips.h | 43 +- js/src/jit/mips/MoveEmitter-mips.cpp | 2 + ...eHelpers-mips.h => SharedICHelpers-mips.h} | 8 +- ...isters-mips.h => SharedICRegisters-mips.h} | 6 +- js/src/jit/mips/Trampoline-mips.cpp | 4 +- js/src/jit/none/CodeGenerator-none.h | 3 +- js/src/jit/none/MacroAssembler-none.h | 9 +- ...eHelpers-none.h => SharedICHelpers-none.h} | 6 +- ...isters-none.h => SharedICRegisters-none.h} | 6 +- js/src/jit/none/Trampoline-none.cpp | 19 +- js/src/jit/shared/BaselineCompiler-shared.cpp | 14 + js/src/jit/shared/BaselineCompiler-shared.h | 11 +- js/src/jit/shared/CodeGenerator-shared-inl.h | 98 +- js/src/jit/shared/CodeGenerator-shared.cpp | 1 + js/src/jit/shared/CodeGenerator-shared.h | 56 +- js/src/jit/x64/BaselineIC-x64.cpp | 2 +- js/src/jit/x64/MacroAssembler-x64.cpp | 43 +- js/src/jit/x64/MacroAssembler-x64.h | 26 - ...ineHelpers-x64.h => SharedICHelpers-x64.h} | 8 +- ...egisters-x64.h => SharedICRegisters-x64.h} | 6 +- js/src/jit/x64/Trampoline-x64.cpp | 2 +- .../jit/x86-shared/BaselineIC-x86-shared.cpp | 2 +- .../x86-shared/CodeGenerator-x86-shared.cpp | 7 +- .../jit/x86-shared/CodeGenerator-x86-shared.h | 16 +- .../x86-shared/MacroAssembler-x86-shared.cpp | 14 +- .../x86-shared/MacroAssembler-x86-shared.h | 37 - .../jit/x86-shared/MoveEmitter-x86-shared.cpp | 2 + js/src/jit/x86/BaselineIC-x86.cpp | 2 +- js/src/jit/x86/MacroAssembler-x86.cpp | 50 +- js/src/jit/x86/MacroAssembler-x86.h | 26 - ...ineHelpers-x86.h => SharedICHelpers-x86.h} | 8 +- ...egisters-x86.h => SharedICRegisters-x86.h} | 6 +- js/src/jit/x86/Trampoline-x86.cpp | 2 +- js/src/js.msg | 5 +- js/src/jsapi.cpp | 6 +- js/src/jsapi.h | 10 +- js/src/jsarray.cpp | 797 +++-- js/src/jscntxtinlines.h | 2 +- js/src/jscompartment.cpp | 88 +- js/src/jscompartment.h | 4 +- js/src/jsdate.cpp | 281 +- js/src/jsexn.cpp | 42 +- js/src/jsexn.h | 3 + js/src/jsfriendapi.cpp | 10 +- js/src/jsfun.cpp | 135 +- js/src/jsfun.h | 60 +- js/src/jsfuninlines.h | 4 +- js/src/jsgc.cpp | 219 +- js/src/jsgc.h | 12 +- js/src/jsgcinlines.h | 2 +- js/src/jsiter.cpp | 7 +- js/src/jsiter.h | 2 +- js/src/jsobj.cpp | 4 +- js/src/jsobjinlines.h | 4 +- js/src/jsopcode.cpp | 334 -- js/src/jsopcode.h | 93 +- js/src/jspubtd.h | 2 +- js/src/jsreflect.h | 95 - js/src/jsscript.cpp | 29 +- js/src/jsscript.h | 11 +- js/src/jsstr.cpp | 21 +- js/src/jsstr.h | 28 +- js/src/moz.build | 4 +- js/src/shell/js.cpp | 17 + .../ecma_5/Date/UTC-convert-all-arguments.js | 70 + .../Date/constructor-convert-all-arguments.js | 70 + js/src/tests/ecma_7/SIMD/load.js | 68 +- js/src/tests/ecma_7/SIMD/store.js | 6 +- js/src/tests/js1_5/Regress/regress-252892.js | 31 +- .../tests/js1_5/extensions/regress-226078.js | 2 +- js/src/tests/js1_8/regress/regress-459185.js | 2 +- js/src/tests/js1_8/regress/regress-459186.js | 2 +- .../js1_8_1/regress/regress-452498-052.js | 2 +- .../js1_8_1/regress/regress-452498-053.js | 2 +- .../js1_8_1/regress/regress-452498-117.js | 2 +- .../js1_8_1/regress/regress-452498-123.js | 2 +- .../tests/js1_8_5/regress/regress-672892.js | 2 +- js/src/vm/ArrayObject-inl.h | 3 + js/src/vm/CharacterEncoding.cpp | 4 +- js/src/vm/Debugger.cpp | 223 +- js/src/vm/Debugger.h | 89 +- js/src/vm/GlobalObject.cpp | 2 +- js/src/vm/GlobalObject.h | 2 +- js/src/vm/NativeObject-inl.h | 32 +- js/src/vm/NativeObject.cpp | 38 +- js/src/vm/NativeObject.h | 38 +- js/src/vm/ObjectGroup.cpp | 12 +- js/src/vm/Printer.cpp | 609 ++++ js/src/vm/Printer.h | 223 ++ js/src/vm/ReceiverGuard.cpp | 3 + js/src/vm/RegExpObject.cpp | 5 +- js/src/vm/Runtime-inl.h | 5 +- js/src/vm/Runtime.h | 7 +- js/src/vm/SavedStacks.cpp | 2 +- js/src/vm/SelfHosting.cpp | 14 +- js/src/vm/Shape.cpp | 12 +- js/src/vm/SharedTypedArrayObject.cpp | 2 +- js/src/vm/Stack-inl.h | 4 +- js/src/vm/TypeInference.cpp | 6 - js/src/vm/TypedArrayObject.cpp | 4 +- js/src/vm/UnboxedObject-inl.h | 607 ++++ js/src/vm/UnboxedObject.cpp | 191 +- js/src/vm/UnboxedObject.h | 32 +- js/src/vm/Xdr.h | 4 +- js/xpconnect/src/XPCJSRuntime.cpp | 12 +- js/xpconnect/src/XPCWrappedJS.cpp | 1 + js/xpconnect/src/XPCWrappedNative.cpp | 1 + js/xpconnect/tests/chrome/chrome.ini | 1 + js/xpconnect/tests/chrome/test_bug732665.xul | 5 + .../tests/chrome/test_bug732665_meta.js | 26 + .../chrome/test_onGarbageCollection.html | 35 + js/xpconnect/tests/unit/head_ongc.js | 41 + .../tests/unit/test_onGarbageCollection-01.js | 64 + .../tests/unit/test_onGarbageCollection-02.js | 94 + .../tests/unit/test_onGarbageCollection-03.js | 35 + .../unit/test_onGarbageCollection-04.js} | 50 +- js/xpconnect/tests/unit/xpcshell.ini | 8 + .../ctypes/tests/unit/test_jsctypes.js | 134 +- .../xpcshell/test_osfile_async_largefiles.js | 4 +- .../devtools/debugger/debugger-controller.js | 2 +- .../devtools/performance/modules/graphs.js | 18 +- toolkit/devtools/performance/test/browser.ini | 1 + .../test/browser_perf-details-06.js | 61 + .../views/details-abstract-subview.js | 15 +- .../performance/views/details-js-call-tree.js | 2 +- .../views/details-js-flamegraph.js | 2 + .../views/details-memory-flamegraph.js | 2 + .../performance/views/details-waterfall.js | 2 +- .../devtools/performance/views/overview.js | 11 + toolkit/devtools/shared/widgets/Graphs.jsm | 8 + toolkit/modules/Sqlite.jsm | 150 +- xpcom/base/CycleCollectedJSRuntime.cpp | 20 + xpcom/base/CycleCollectedJSRuntime.h | 6 +- xpcom/base/DebuggerOnGCRunnable.cpp | 47 + xpcom/base/DebuggerOnGCRunnable.h | 35 + xpcom/base/DeferredFinalize.cpp | 28 + xpcom/base/DeferredFinalize.h | 32 + xpcom/base/moz.build | 7 +- xpcom/base/nsCycleCollector.cpp | 24 - xpcom/base/nsCycleCollector.h | 21 - 309 files changed, 11475 insertions(+), 8138 deletions(-) rename js/src/{jsreflect.cpp => builtin/ReflectParse.cpp} (98%) create mode 100644 js/src/jit-test/lib/class.js create mode 100644 js/src/jit-test/tests/asm.js/testZOOB.js rename js/src/jit-test/tests/basic/{semicolon-less-return.js => statement-after-return.js} (75%) delete mode 100644 js/src/jit-test/tests/closures/bug540136.js delete mode 100644 js/src/jit-test/tests/closures/bug540348.js create mode 100644 js/src/jit-test/tests/ctypes/AddressOfField.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-abi.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-array.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-cdata.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-ctypes.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-finalizer.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-function.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-int64.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-pointer.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-primitive.js create mode 100644 js/src/jit-test/tests/ctypes/argument-length-struct.js create mode 100644 js/src/jit-test/tests/ctypes/argument-type-array.js create mode 100644 js/src/jit-test/tests/ctypes/argument-type-ctypes.js create mode 100644 js/src/jit-test/tests/ctypes/argument-type-function.js create mode 100644 js/src/jit-test/tests/ctypes/argument-type-int64.js create mode 100644 js/src/jit-test/tests/ctypes/argument-type-pointer.js create mode 100644 js/src/jit-test/tests/ctypes/argument-type-struct.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-array.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-error.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-finalizer.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-function.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-int64.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-native-function.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-pointer.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-primitive.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-struct.js create mode 100644 js/src/jit-test/tests/ctypes/conversion-to-primitive.js delete mode 100644 js/src/jit-test/tests/debug/Memory-onGarbageCollection-01.js delete mode 100644 js/src/jit-test/tests/debug/Memory-onGarbageCollection-02.js delete mode 100644 js/src/jit-test/tests/debug/Memory-onGarbageCollection-03.js create mode 100644 js/src/jit-test/tests/debug/Script-lineCount.js create mode 100644 js/src/jit-test/tests/gc/bug-1155455.js create mode 100644 js/src/jit-test/tests/gc/incremental-abort.js create mode 100644 js/src/jit-test/tests/ion/bug1154971.js create mode 100644 js/src/jit-test/tests/ion/bug1159899.js delete mode 100644 js/src/jit-test/tests/jaeger/bug583684.js delete mode 100644 js/src/jit-test/tests/jaeger/bug719758.js delete mode 100644 js/src/jit/LiveRangeAllocator.cpp delete mode 100644 js/src/jit/LiveRangeAllocator.h create mode 100644 js/src/jit/MacroAssembler-inl.h rename js/src/jit/{BaselineHelpers.h => SharedICHelpers.h} (65%) rename js/src/jit/{BaselineRegisters.h => SharedICRegisters.h} (64%) rename js/src/jit/arm/{BaselineHelpers-arm.h => SharedICHelpers-arm.h} (98%) rename js/src/jit/arm/{BaselineRegisters-arm.h => SharedICRegisters-arm.h} (93%) rename js/src/jit/mips/{BaselineHelpers-mips.h => SharedICHelpers-mips.h} (98%) rename js/src/jit/mips/{BaselineRegisters-mips.h => SharedICRegisters-mips.h} (92%) rename js/src/jit/none/{BaselineHelpers-none.h => SharedICHelpers-none.h} (93%) rename js/src/jit/none/{BaselineRegisters-none.h => SharedICRegisters-none.h} (90%) rename js/src/jit/x64/{BaselineHelpers-x64.h => SharedICHelpers-x64.h} (98%) rename js/src/jit/x64/{BaselineRegisters-x64.h => SharedICRegisters-x64.h} (89%) rename js/src/jit/x86/{BaselineHelpers-x86.h => SharedICHelpers-x86.h} (98%) rename js/src/jit/x86/{BaselineRegisters-x86.h => SharedICRegisters-x86.h} (90%) delete mode 100644 js/src/jsreflect.h create mode 100644 js/src/tests/ecma_5/Date/UTC-convert-all-arguments.js create mode 100644 js/src/tests/ecma_5/Date/constructor-convert-all-arguments.js create mode 100644 js/src/vm/Printer.cpp create mode 100644 js/src/vm/Printer.h create mode 100644 js/src/vm/UnboxedObject-inl.h create mode 100644 js/xpconnect/tests/chrome/test_bug732665_meta.js create mode 100644 js/xpconnect/tests/chrome/test_onGarbageCollection.html create mode 100644 js/xpconnect/tests/unit/head_ongc.js create mode 100644 js/xpconnect/tests/unit/test_onGarbageCollection-01.js create mode 100644 js/xpconnect/tests/unit/test_onGarbageCollection-02.js create mode 100644 js/xpconnect/tests/unit/test_onGarbageCollection-03.js rename js/{src/jit-test/tests/debug/Memory-onGarbageCollection-04.js => xpconnect/tests/unit/test_onGarbageCollection-04.js} (52%) create mode 100644 toolkit/devtools/performance/test/browser_perf-details-06.js create mode 100644 xpcom/base/DebuggerOnGCRunnable.cpp create mode 100644 xpcom/base/DebuggerOnGCRunnable.h create mode 100644 xpcom/base/DeferredFinalize.cpp create mode 100644 xpcom/base/DeferredFinalize.h diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index bdff07e4db..7b8760de9d 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -220,7 +220,13 @@ static nsIScriptSecurityManager *sSecurityManager; // the appropriate pref is set. static bool sGCOnMemoryPressure; + +// nsJSEnvironmentObserver observes the user-interaction-inactive notifications +// and triggers a shrinking a garbage collection if the user is still inactive +// after NS_SHRINKING_GC_DELAY ms later, if the appropriate pref is set. + static bool sCompactOnUserInactive; +static bool sIsCompactingOnUserInactive = false; // In testing, we call RunNextCollectorTimer() to ensure that the collectors are run more // aggressively than they would be in regular browsing. sExpensiveCollectorPokes keeps @@ -300,6 +306,10 @@ nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic, } } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) { nsJSContext::KillShrinkingGCTimer(); + if (sIsCompactingOnUserInactive) { + JS::AbortIncrementalGC(sRuntime); + } + MOZ_ASSERT(!sIsCompactingOnUserInactive); } else if (!nsCRT::strcmp(aTopic, "quit-application") || !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { sShuttingDown = true; @@ -1820,6 +1830,7 @@ void ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure) { nsJSContext::KillShrinkingGCTimer(); + sIsCompactingOnUserInactive = true; nsJSContext::GarbageCollectNow(JS::gcreason::USER_INACTIVE, nsJSContext::IncrementalGC, nsJSContext::ShrinkingGC); @@ -2237,6 +2248,7 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::GCProgress aProgress, const JS::GCDescrip } sCCLockedOut = false; + sIsCompactingOnUserInactive = false; // May need to kill the inter-slice GC timer nsJSContext::KillInterSliceGCTimer(); diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index bd7ff01f83..94b70c43ae 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -14,6 +14,8 @@ #include "mozilla/Alignment.h" #include "mozilla/Array.h" #include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/DeferredFinalize.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/CallbackObject.h" #include "mozilla/dom/DOMJSClass.h" @@ -26,8 +28,6 @@ #include "mozilla/ErrorResult.h" #include "mozilla/Likely.h" #include "mozilla/MemoryReporting.h" -#include "mozilla/CycleCollectedJSRuntime.h" -#include "nsCycleCollector.h" #include "nsIGlobalObject.h" #include "nsIXPConnect.h" #include "nsJSUtils.h" diff --git a/dom/xbl/nsXBLBinding.cpp b/dom/xbl/nsXBLBinding.cpp index c84af30858..87c1bab8f5 100644 --- a/dom/xbl/nsXBLBinding.cpp +++ b/dom/xbl/nsXBLBinding.cpp @@ -50,12 +50,12 @@ #include "prprf.h" #include "nsNodeUtils.h" #include "nsJSUtils.h" -#include "nsCycleCollector.h" // Nasty hack. Maybe we could move some of the classinfo utility methods // (e.g. WrapNative) over to nsContentUtils? #include "nsDOMClassInfo.h" +#include "mozilla/DeferredFinalize.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ShadowRoot.h" diff --git a/js/public/Debug.h b/js/public/Debug.h index 84e294a917..b94662aa22 100644 --- a/js/public/Debug.h +++ b/js/public/Debug.h @@ -12,10 +12,11 @@ #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/MemoryReporting.h" -#include "mozilla/Move.h" +#include "mozilla/UniquePtr.h" #include "jspubtd.h" +#include "js/GCAPI.h" #include "js/RootingAPI.h" #include "js/TypeDecls.h" @@ -24,6 +25,9 @@ class Debugger; } // namespace js namespace JS { + +using mozilla::UniquePtr; + namespace dbg { // Helping embedding code build objects for Debugger @@ -261,6 +265,24 @@ class BuilderOrigin : public Builder { void SetDebuggerMallocSizeOf(JSRuntime* runtime, mozilla::MallocSizeOf mallocSizeOf); + +// Debugger and Garbage Collection Events +// -------------------------------------- +// +// The Debugger wants to report about its debuggees' GC cycles, however entering +// JS after a GC is troublesome since SpiderMonkey will often do something like +// force a GC and then rely on the nursery being empty. If we call into some +// Debugger's hook after the GC, then JS runs and the nursery won't be +// empty. Instead, we rely on embedders to call back into SpiderMonkey after a +// GC and notify Debuggers to call their onGarbageCollection hook. + + +// For each Debugger that observed a debuggee involved in the given GC event, +// call its `onGarbageCollection` hook. +JS_PUBLIC_API(bool) +FireOnGarbageCollectionHook(JSContext* cx, GarbageCollectionEvent::Ptr&& data); + + // Handlers for observing Promises // ------------------------------- diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h index 59fc410d54..8b840d5eb2 100644 --- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -7,12 +7,18 @@ #ifndef js_GCAPI_h #define js_GCAPI_h +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" + #include "js/HeapAPI.h" namespace js { namespace gc { class GCRuntime; } +namespace gcstats { +struct Statistics; +} } typedef enum JSGCMode { @@ -42,6 +48,8 @@ typedef enum JSGCInvocationKind { namespace JS { +using mozilla::UniquePtr; + #define GCREASONS(D) \ /* Reasons internal to the JS engine */ \ D(API) \ @@ -60,6 +68,7 @@ namespace JS { D(SHARED_MEMORY_LIMIT) \ D(PERIODIC_FULL_GC) \ D(INCREMENTAL_TOO_SLOW) \ + D(ABORT_GC) \ \ /* These are reserved for future use. */ \ D(RESERVED0) \ @@ -78,7 +87,6 @@ namespace JS { D(RESERVED13) \ D(RESERVED14) \ D(RESERVED15) \ - D(RESERVED16) \ \ /* Reasons from Firefox */ \ D(DOM_WINDOW_UTILS) \ @@ -237,6 +245,65 @@ IncrementalGCSlice(JSRuntime* rt, gcreason::Reason reason, int64_t millis = 0); extern JS_PUBLIC_API(void) FinishIncrementalGC(JSRuntime* rt, gcreason::Reason reason); +/* + * If IsIncrementalGCInProgress(rt), this call aborts the ongoing collection and + * performs whatever work needs to be done to return the collector to its idle + * state. This may take an arbitrarily long time. When this function returns, + * IsIncrementalGCInProgress(rt) will always be false. + */ +extern JS_PUBLIC_API(void) +AbortIncrementalGC(JSRuntime* rt); + +namespace dbg { + +// The `JS::dbg::GarbageCollectionEvent` class is essentially a view of the +// `js::gcstats::Statistics` data without the uber implementation-specific bits. +// It should generally be palatable for web developers. +class GarbageCollectionEvent +{ + // The major GC number of the GC cycle this data pertains to. + uint64_t majorGCNumber_; + + // Reference to a non-owned, statically allocated C string. This is a very + // short reason explaining why a GC was triggered. + const char* reason; + + // Reference to a nullable, non-owned, statically allocated C string. If the + // collection was forced to be non-incremental, this is a short reason of + // why the GC could not perform an incremental collection. + const char* nonincrementalReason; + + // Represents a single slice of a possibly multi-slice incremental garbage + // collection. + struct Collection { + int64_t startTimestamp; + int64_t endTimestamp; + }; + + // The set of garbage collection slices that made up this GC cycle. + mozilla::Vector collections; + + GarbageCollectionEvent(const GarbageCollectionEvent& rhs) = delete; + GarbageCollectionEvent& operator=(const GarbageCollectionEvent& rhs) = delete; + + public: + explicit GarbageCollectionEvent(uint64_t majorGCNum) + : majorGCNumber_(majorGCNum) + , reason(nullptr) + , nonincrementalReason(nullptr) + , collections() + { } + + using Ptr = UniquePtr>; + static Ptr Create(JSRuntime* rt, ::js::gcstats::Statistics& stats, uint64_t majorGCNumber); + + JSObject* toJSObject(JSContext* cx) const; + + uint64_t majorGCNumber() const { return majorGCNumber_; } +}; + +} // namespace dbg + enum GCProgress { /* * During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END @@ -263,6 +330,8 @@ struct JS_PUBLIC_API(GCDescription) { char16_t* formatMessage(JSRuntime* rt) const; char16_t* formatJSON(JSRuntime* rt, uint64_t timestamp) const; + + JS::dbg::GarbageCollectionEvent::Ptr toGCEvent(JSRuntime* rt) const; }; typedef void diff --git a/js/public/HeapAPI.h b/js/public/HeapAPI.h index df3d78afa3..94769a7424 100644 --- a/js/public/HeapAPI.h +++ b/js/public/HeapAPI.h @@ -222,6 +222,8 @@ class JS_FRIEND_API(GCCellPtr) return reinterpret_cast(asCell()); } + bool mayBeOwnedByOtherRuntime() const; + private: uintptr_t checkedCast(void* p, JSGCTraceKind traceKind) { js::gc::Cell* cell = static_cast(p); @@ -365,6 +367,8 @@ GCThingIsMarkedGray(GCCellPtr thing) { if (js::gc::IsInsideNursery(thing.asCell())) return false; + if (thing.mayBeOwnedByOtherRuntime()) + return false; return js::gc::detail::CellIsMarkedGray(thing.asCell()); } diff --git a/js/public/RootingAPI.h b/js/public/RootingAPI.h index 0563ddbf53..2834fc6d57 100644 --- a/js/public/RootingAPI.h +++ b/js/public/RootingAPI.h @@ -214,9 +214,13 @@ JS_FRIEND_API(void) HeapCellRelocate(js::gc::Cell** cellp); */ extern JS_FRIEND_API(void) AssertGCThingMustBeTenured(JSObject* obj); +extern JS_FRIEND_API(void) +AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell); #else inline void AssertGCThingMustBeTenured(JSObject* obj) {} +inline void +AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell) {} #endif /* @@ -639,7 +643,10 @@ struct GCMethods { static T* initial() { return nullptr; } static bool needsPostBarrier(T* v) { return false; } - static void postBarrier(T** vp) {} + static void postBarrier(T** vp) { + if (vp) + JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast(vp)); + } static void relocate(T** vp) {} }; diff --git a/js/public/SliceBudget.h b/js/public/SliceBudget.h index 22e260fd04..316bf9e62c 100644 --- a/js/public/SliceBudget.h +++ b/js/public/SliceBudget.h @@ -33,6 +33,12 @@ struct JS_PUBLIC_API(WorkBudget) */ struct JS_PUBLIC_API(SliceBudget) { + // Memory of the originally requested budget. If isUnlimited, neither of + // these are in use. If deadline==0, then workBudget is valid. Otherwise + // timeBudget is valid. + TimeBudget timeBudget; + WorkBudget workBudget; + int64_t deadline; /* in microseconds */ intptr_t counter; @@ -64,10 +70,12 @@ struct JS_PUBLIC_API(SliceBudget) return checkOverBudget(); } - bool isUnlimited() { + bool isUnlimited() const { return deadline == unlimitedDeadline; } + int describe(char* buffer, size_t maxlen) const; + private: bool checkOverBudget(); diff --git a/js/src/asmjs/AsmJSFrameIterator.cpp b/js/src/asmjs/AsmJSFrameIterator.cpp index a8915335b8..418aa4b835 100644 --- a/js/src/asmjs/AsmJSFrameIterator.cpp +++ b/js/src/asmjs/AsmJSFrameIterator.cpp @@ -22,6 +22,8 @@ #include "asmjs/AsmJSValidate.h" #include "jit/MacroAssembler.h" +#include "jit/MacroAssembler-inl.h" + using namespace js; using namespace js::jit; diff --git a/js/src/asmjs/AsmJSLink.cpp b/js/src/asmjs/AsmJSLink.cpp index 7f307588d4..3bcf31b794 100644 --- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -801,7 +801,7 @@ NewExportedFunction(JSContext* cx, const AsmJSModule::ExportedFunction& func, unsigned numArgs = func.isChangeHeap() ? 1 : func.numArgs(); JSFunction *fun = NewNativeConstructor(cx, CallAsmJS, numArgs, name, - JSFunction::ExtendedFinalizeKind, GenericObject, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject, JSFunction::ASMJS_CTOR); if (!fun) return nullptr; @@ -824,7 +824,7 @@ HandleDynamicLinkFailure(JSContext* cx, const CallArgs& args, AsmJSModule& modul return false; RootedFunction fun(cx, NewScriptedFunction(cx, 0, JSFunction::INTERPRETED, - name, JSFunction::FinalizeKind, + name, gc::AllocKind::FUNCTION, TenuredObject)); if (!fun) return false; @@ -1095,7 +1095,7 @@ js::NewAsmJSModuleFunction(ExclusiveContext* cx, JSFunction* origFun, HandleObje : JSFunction::ASMJS_CTOR; JSFunction *moduleFun = NewNativeConstructor(cx, LinkAsmJS, origFun->nargs(), name, - JSFunction::ExtendedFinalizeKind, TenuredObject, + gc::AllocKind::FUNCTION_EXTENDED, TenuredObject, flags); if (!moduleFun) return nullptr; diff --git a/js/src/asmjs/AsmJSModule.cpp b/js/src/asmjs/AsmJSModule.cpp index 722d128999..5529b3eb72 100644 --- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -44,6 +44,7 @@ #include "jsobjinlines.h" #include "frontend/ParseNode-inl.h" +#include "jit/MacroAssembler-inl.h" #include "vm/ArrayBufferObject-inl.h" #include "vm/Stack-inl.h" @@ -964,13 +965,13 @@ const Class AsmJSModuleObject::class_ = { AsmJSModuleObject_trace }; -AsmJSModuleObject * -AsmJSModuleObject::create(ExclusiveContext *cx, ScopedJSDeletePtr *module) +AsmJSModuleObject* +AsmJSModuleObject::create(ExclusiveContext* cx, ScopedJSDeletePtr* module) { - JSObject *obj = NewObjectWithGivenProto(cx, &AsmJSModuleObject::class_, NullPtr()); + JSObject* obj = NewObjectWithGivenProto(cx, &AsmJSModuleObject::class_, NullPtr()); if (!obj) return nullptr; - AsmJSModuleObject *nobj = &obj->as(); + AsmJSModuleObject* nobj = &obj->as(); nobj->setReservedSlot(MODULE_SLOT, PrivateValue(module->forget())); return nobj; diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp index 37ba1244e3..e506d151be 100644 --- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -49,6 +49,7 @@ #include "frontend/ParseNode-inl.h" #include "frontend/Parser-inl.h" +#include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::frontend; @@ -498,7 +499,7 @@ ParseVarOrConstStatement(AsmJSParser& parser, ParseNode** var) return true; } - *var = parser.statement(); + *var = parser.statement(YieldIsName); if (!*var) return false; @@ -538,9 +539,10 @@ class Type Type() : which_(Which(-1)) {} static Type Of(const AsmJSNumLit& lit) { MOZ_ASSERT(lit.hasType()); - MOZ_ASSERT(Type::Which(lit.which()) >= Fixnum && Type::Which(lit.which()) <= Float32x4); + Which which = Type::Which(lit.which()); + MOZ_ASSERT(which >= Fixnum && which <= Float32x4); Type t; - t.which_ = Type::Which(lit.which()); + t.which_ = which; return t; } MOZ_IMPLICIT Type(Which w) : which_(w) {} @@ -1120,7 +1122,7 @@ class MOZ_STACK_CLASS ModuleCompiler PropertyName* name() const { return name_; } bool defined() const { return defined_; } - void define(ModuleCompiler& m, ParseNode* fn) { + void define(ParseNode* fn) { MOZ_ASSERT(!defined_); defined_ = true; srcBegin_ = fn->pn_pos.begin; @@ -2597,10 +2599,6 @@ class FunctionCompiler return m_.lookupGlobal(name); } - bool supportsSimd() const { - return m_.supportsSimd(); - } - /*************************************************************************/ void enterHeapExpression() { @@ -3369,11 +3367,11 @@ class FunctionCompiler { if (!loopStack_.append(pn) || !breakableStack_.append(pn)) return false; - MOZ_ASSERT_IF(curBlock_, curBlock_->loopDepth() == loopStack_.length() - 1); if (inDeadCode()) { *loopEntry = nullptr; return true; } + MOZ_ASSERT(curBlock_->loopDepth() == loopStack_.length() - 1); *loopEntry = MBasicBlock::NewAsmJS(mirGraph(), info(), curBlock_, MBasicBlock::PENDING_LOOP_HEADER); if (!*loopEntry) @@ -5998,19 +5996,19 @@ CheckSimdOperationCall(FunctionCompiler& f, ParseNode* call, const ModuleCompile case AsmJSSimdOperation_load: return CheckSimdLoad(f, call, opType, 4, def, type); - case AsmJSSimdOperation_loadX: + case AsmJSSimdOperation_load1: return CheckSimdLoad(f, call, opType, 1, def, type); - case AsmJSSimdOperation_loadXY: + case AsmJSSimdOperation_load2: return CheckSimdLoad(f, call, opType, 2, def, type); - case AsmJSSimdOperation_loadXYZ: + case AsmJSSimdOperation_load3: return CheckSimdLoad(f, call, opType, 3, def, type); case AsmJSSimdOperation_store: return CheckSimdStore(f, call, opType, 4, def, type); - case AsmJSSimdOperation_storeX: + case AsmJSSimdOperation_store1: return CheckSimdStore(f, call, opType, 1, def, type); - case AsmJSSimdOperation_storeXY: + case AsmJSSimdOperation_store2: return CheckSimdStore(f, call, opType, 2, def, type); - case AsmJSSimdOperation_storeXYZ: + case AsmJSSimdOperation_store3: return CheckSimdStore(f, call, opType, 3, def, type); case AsmJSSimdOperation_bitselect: @@ -6935,150 +6933,6 @@ CheckLabel(FunctionCompiler& f, ParseNode* labeledStmt, LabelVector* maybeLabels return f.bindLabeledBreaks(&labels, labeledStmt); } -static bool -CheckLeafCondition(FunctionCompiler& f, ParseNode* cond, ParseNode* thenStmt, ParseNode* elseOrJoinStmt, - MBasicBlock** thenBlock, MBasicBlock** elseOrJoinBlock) -{ - MDefinition* condDef; - Type condType; - if (!CheckExpr(f, cond, &condDef, &condType)) - return false; - if (!condType.isInt()) - return f.failf(cond, "%s is not a subtype of int", condType.toChars()); - - if (!f.branchAndStartThen(condDef, thenBlock, elseOrJoinBlock, thenStmt, elseOrJoinStmt)) - return false; - return true; -} - -static bool -CheckIfCondition(FunctionCompiler& f, ParseNode* cond, ParseNode* thenStmt, ParseNode* elseOrJoinStmt, - MBasicBlock** thenBlock, MBasicBlock** elseOrJoinBlock); - -static bool -CheckIfConditional(FunctionCompiler& f, ParseNode* conditional, ParseNode* thenStmt, ParseNode* elseOrJoinStmt, - MBasicBlock** thenBlock, MBasicBlock** elseOrJoinBlock) -{ - MOZ_ASSERT(conditional->isKind(PNK_CONDITIONAL)); - - // a ? b : c <=> (a && b) || (!a && c) - // b is always referred to the AND condition, as we need A and B to reach this test, - // c is always referred as the OR condition, as we reach it if we don't have A. - ParseNode* cond = TernaryKid1(conditional); - ParseNode* lhs = TernaryKid2(conditional); - ParseNode* rhs = TernaryKid3(conditional); - - MBasicBlock* maybeAndTest = nullptr; - MBasicBlock* maybeOrTest = nullptr; - MBasicBlock** ifTrueBlock = &maybeAndTest; - MBasicBlock** ifFalseBlock = &maybeOrTest; - ParseNode* ifTrueBlockNode = lhs; - ParseNode* ifFalseBlockNode = rhs; - - // Try to spot opportunities for short-circuiting in the AND subpart - uint32_t andTestLiteral = 0; - bool skipAndTest = false; - - if (IsLiteralInt(f.m(), lhs, &andTestLiteral)) { - skipAndTest = true; - if (andTestLiteral == 0) { - // (a ? 0 : b) is equivalent to !a && b - // If a is true, jump to the elseBlock directly - ifTrueBlock = elseOrJoinBlock; - ifTrueBlockNode = elseOrJoinStmt; - } else { - // (a ? 1 : b) is equivalent to a || b - // If a is true, jump to the thenBlock directly - ifTrueBlock = thenBlock; - ifTrueBlockNode = thenStmt; - } - } - - // Try to spot opportunities for short-circuiting in the OR subpart - uint32_t orTestLiteral = 0; - bool skipOrTest = false; - - if (IsLiteralInt(f.m(), rhs, &orTestLiteral)) { - skipOrTest = true; - if (orTestLiteral == 0) { - // (a ? b : 0) is equivalent to a && b - // If a is false, jump to the elseBlock directly - ifFalseBlock = elseOrJoinBlock; - ifFalseBlockNode = elseOrJoinStmt; - } else { - // (a ? b : 1) is equivalent to !a || b - // If a is false, jump to the thenBlock directly - ifFalseBlock = thenBlock; - ifFalseBlockNode = thenStmt; - } - } - - // Pathological cases: a ? 0 : 0 (i.e. false) or a ? 1 : 1 (i.e. true) - // These cases can't be optimized properly at this point: one of the blocks might be - // created and won't ever be executed. Furthermore, it introduces inconsistencies in the - // MIR graph (even if we try to create a block by hand, it will have no predecessor, which - // breaks graph assumptions). The only way we could optimize it is to do it directly in - // CheckIf by removing the control flow entirely. - if (skipOrTest && skipAndTest && (!!orTestLiteral == !!andTestLiteral)) - return CheckLeafCondition(f, conditional, thenStmt, elseOrJoinStmt, thenBlock, elseOrJoinBlock); - - if (!CheckIfCondition(f, cond, ifTrueBlockNode, ifFalseBlockNode, ifTrueBlock, ifFalseBlock)) - return false; - f.assertCurrentBlockIs(*ifTrueBlock); - - // Add supplementary tests, if needed - if (!skipAndTest) { - if (!CheckIfCondition(f, lhs, thenStmt, elseOrJoinStmt, thenBlock, elseOrJoinBlock)) - return false; - f.assertCurrentBlockIs(*thenBlock); - } - - if (!skipOrTest) { - f.switchToElse(*ifFalseBlock); - if (!CheckIfCondition(f, rhs, thenStmt, elseOrJoinStmt, thenBlock, elseOrJoinBlock)) - return false; - f.assertCurrentBlockIs(*thenBlock); - } - - // We might not be on the thenBlock in one case - if (ifTrueBlock == elseOrJoinBlock) { - MOZ_ASSERT(skipAndTest && andTestLiteral == 0); - f.switchToElse(*thenBlock); - } - - // Check post-conditions - f.assertCurrentBlockIs(*thenBlock); - MOZ_ASSERT_IF(!f.inDeadCode(), *thenBlock && *elseOrJoinBlock); - return true; -} - -// Recursive function that checks for a complex condition (formed with ternary -// conditionals) and creates the associated short-circuiting control flow graph. -// -// After a call to CheckCondition, the followings are true: -// - if *thenBlock and *elseOrJoinBlock were non-null on entry, their value is -// not changed by this function. -// - *thenBlock and *elseOrJoinBlock are non-null on exit. -// - the current block on exit is the *thenBlock. -static bool -CheckIfCondition(FunctionCompiler& f, ParseNode* cond, ParseNode* thenStmt, - ParseNode* elseOrJoinStmt, MBasicBlock** thenBlock, MBasicBlock** elseOrJoinBlock) -{ - JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); - - if (cond->isKind(PNK_CONDITIONAL)) - return CheckIfConditional(f, cond, thenStmt, elseOrJoinStmt, thenBlock, elseOrJoinBlock); - - // We've reached a leaf, i.e. an atomic condition - if (!CheckLeafCondition(f, cond, thenStmt, elseOrJoinStmt, thenBlock, elseOrJoinBlock)) - return false; - - // Check post-conditions - f.assertCurrentBlockIs(*thenBlock); - MOZ_ASSERT_IF(!f.inDeadCode(), *thenBlock && *elseOrJoinBlock); - return true; -} - static bool CheckIf(FunctionCompiler& f, ParseNode* ifStmt) { @@ -7099,7 +6953,14 @@ CheckIf(FunctionCompiler& f, ParseNode* ifStmt) MBasicBlock* elseBlock = nullptr; ParseNode* elseOrJoinStmt = elseStmt ? elseStmt : nextStmt; - if (!CheckIfCondition(f, cond, thenStmt, elseOrJoinStmt, &thenBlock, &elseBlock)) + MDefinition* condDef; + Type condType; + if (!CheckExpr(f, cond, &condDef, &condType)) + return false; + if (!condType.isInt()) + return f.failf(cond, "%s is not a subtype of int", condType.toChars()); + + if (!f.branchAndStartThen(condDef, &thenBlock, &elseBlock, thenStmt, elseOrJoinStmt)) return false; if (!CheckStatement(f, thenStmt)) @@ -7599,7 +7460,7 @@ ParseFunction(ModuleCompiler& m, ParseNode** fnOut) // This flows into FunctionBox, so must be tenured. RootedFunction fun(m.cx(), NewScriptedFunction(m.cx(), 0, JSFunction::INTERPRETED, - name, JSFunction::FinalizeKind, + name, gc::AllocKind::FUNCTION, TenuredObject)); if (!fun) return false; @@ -7618,7 +7479,9 @@ ParseFunction(ModuleCompiler& m, ParseNode** fnOut) if (!funpc.init(tokenStream)) return false; - if (!m.parser().functionArgsAndBodyGeneric(fn, fun, Normal, Statement)) { + if (!m.parser().functionArgsAndBodyGeneric(InAllowed, YieldIsName, fn, fun, Normal, + Statement)) + { if (tokenStream.hadError() || directives == newDirectives) return false; @@ -7699,7 +7562,7 @@ CheckFunction(ModuleCompiler& m, LifoAlloc& lifo, MIRGenerator** mir, ModuleComp if (func->defined()) return m.failName(fn, "function '%s' already defined", FunctionName(fn)); - func->define(m, fn); + func->define(fn); func->accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC); m.parser().release(mark); @@ -8196,7 +8059,7 @@ CheckModuleReturn(ModuleCompiler& m) return m.fail(nullptr, "invalid asm.js statement"); } - ParseNode* returnStmt = m.parser().statement(); + ParseNode* returnStmt = m.parser().statement(YieldIsName); if (!returnStmt) return false; diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index c117932d41..8f73037d41 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -1089,7 +1089,7 @@ CreateObjectConstructor(JSContext* cx, JSProtoKey key) /* Create the Object function now that we have a [[Prototype]] for it. */ return NewNativeConstructor(cx, obj_construct, 1, HandlePropertyName(cx->names().Object), - JSFunction::FinalizeKind, SingletonObject); + gc::AllocKind::FUNCTION, SingletonObject); } static JSObject* diff --git a/js/src/jsreflect.cpp b/js/src/builtin/ReflectParse.cpp similarity index 98% rename from js/src/jsreflect.cpp rename to js/src/builtin/ReflectParse.cpp index c2b00db360..4f488213d2 100644 --- a/js/src/jsreflect.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -6,8 +6,6 @@ /* JS reflection package. */ -#include "jsreflect.h" - #include "mozilla/ArrayUtils.h" #include "mozilla/DebugOnly.h" @@ -34,7 +32,80 @@ using JS::AutoValueArray; using mozilla::ArrayLength; using mozilla::DebugOnly; -char const * const js::aopNames[] = { +enum ASTType { + AST_ERROR = -1, +#define ASTDEF(ast, str, method) ast, +#include "jsast.tbl" +#undef ASTDEF + AST_LIMIT +}; + +enum AssignmentOperator { + AOP_ERR = -1, + + /* assign */ + AOP_ASSIGN = 0, + /* operator-assign */ + AOP_PLUS, AOP_MINUS, AOP_STAR, AOP_DIV, AOP_MOD, AOP_POW, + /* shift-assign */ + AOP_LSH, AOP_RSH, AOP_URSH, + /* binary */ + AOP_BITOR, AOP_BITXOR, AOP_BITAND, + + AOP_LIMIT +}; + +enum BinaryOperator { + BINOP_ERR = -1, + + /* eq */ + BINOP_EQ = 0, BINOP_NE, BINOP_STRICTEQ, BINOP_STRICTNE, + /* rel */ + BINOP_LT, BINOP_LE, BINOP_GT, BINOP_GE, + /* shift */ + BINOP_LSH, BINOP_RSH, BINOP_URSH, + /* arithmetic */ + BINOP_ADD, BINOP_SUB, BINOP_STAR, BINOP_DIV, BINOP_MOD, BINOP_POW, + /* binary */ + BINOP_BITOR, BINOP_BITXOR, BINOP_BITAND, + /* misc */ + BINOP_IN, BINOP_INSTANCEOF, + + BINOP_LIMIT +}; + +enum UnaryOperator { + UNOP_ERR = -1, + + UNOP_DELETE = 0, + UNOP_NEG, + UNOP_POS, + UNOP_NOT, + UNOP_BITNOT, + UNOP_TYPEOF, + UNOP_VOID, + + UNOP_LIMIT +}; + +enum VarDeclKind { + VARDECL_ERR = -1, + VARDECL_VAR = 0, + VARDECL_CONST, + VARDECL_LET, + VARDECL_LIMIT +}; + +enum PropKind { + PROP_ERR = -1, + PROP_INIT = 0, + PROP_GETTER, + PROP_SETTER, + PROP_MUTATEPROTO, + PROP_LIMIT +}; + +static const char* const aopNames[] = { "=", /* AOP_ASSIGN */ "+=", /* AOP_PLUS */ "-=", /* AOP_MINUS */ @@ -50,7 +121,7 @@ char const * const js::aopNames[] = { "&=" /* AOP_BITAND */ }; -char const * const js::binopNames[] = { +static const char* const binopNames[] = { "==", /* BINOP_EQ */ "!=", /* BINOP_NE */ "===", /* BINOP_STRICTEQ */ @@ -75,7 +146,7 @@ char const * const js::binopNames[] = { "instanceof", /* BINOP_INSTANCEOF */ }; -char const * const js::unopNames[] = { +static const char* const unopNames[] = { "delete", /* UNOP_DELETE */ "-", /* UNOP_NEG */ "+", /* UNOP_POS */ @@ -85,14 +156,14 @@ char const * const js::unopNames[] = { "void" /* UNOP_VOID */ }; -char const * const js::nodeTypeNames[] = { +static const char* const nodeTypeNames[] = { #define ASTDEF(ast, str, method) str, #include "jsast.tbl" #undef ASTDEF nullptr }; -static char const * const callbackNames[] = { +static const char* const callbackNames[] = { #define ASTDEF(ast, str, method) method, #include "jsast.tbl" #undef ASTDEF diff --git a/js/src/builtin/SIMD.cpp b/js/src/builtin/SIMD.cpp index 38d9b4a7e9..93c67a7f97 100644 --- a/js/src/builtin/SIMD.cpp +++ b/js/src/builtin/SIMD.cpp @@ -17,6 +17,7 @@ #include "jsapi.h" #include "jsfriendapi.h" +#include "jsprf.h" #include "builtin/TypedObject.h" #include "js/Value.h" diff --git a/js/src/builtin/SIMD.h b/js/src/builtin/SIMD.h index ece9927981..8b0d449581 100644 --- a/js/src/builtin/SIMD.h +++ b/js/src/builtin/SIMD.h @@ -43,10 +43,10 @@ V(greaterThanOrEqual, (CompareFunc), 2) \ V(lessThan, (CompareFunc), 2) \ V(lessThanOrEqual, (CompareFunc), 2) \ - V(load, (Load), 2) \ - V(loadXYZ, (Load), 2) \ - V(loadXY, (Load), 2) \ - V(loadX, (Load), 2) \ + V(load, (Load), 2) \ + V(load3, (Load), 2) \ + V(load2, (Load), 2) \ + V(load1, (Load), 2) \ V(max, (BinaryFunc), 2) \ V(maxNum, (BinaryFunc), 2) \ V(min, (BinaryFunc), 2) \ @@ -54,10 +54,10 @@ V(mul, (BinaryFunc), 2) \ V(notEqual, (CompareFunc), 2) \ V(or, (CoercedBinaryFunc), 2) \ - V(store, (Store), 3) \ - V(storeXYZ, (Store), 3) \ - V(storeXY, (Store), 3) \ - V(storeX, (Store), 3) \ + V(store, (Store), 3) \ + V(store3, (Store), 3) \ + V(store2, (Store), 3) \ + V(store1, (Store), 3) \ V(sub, (BinaryFunc), 2) \ V(withX, (FuncWith), 2) \ V(withY, (FuncWith), 2) \ @@ -101,16 +101,16 @@ V(greaterThanOrEqual, (CompareFunc), 2) \ V(lessThan, (CompareFunc), 2) \ V(lessThanOrEqual, (CompareFunc), 2) \ - V(load, (Load), 2) \ - V(loadX, (Load), 2) \ + V(load, (Load), 2) \ + V(load1, (Load), 2) \ V(max, (BinaryFunc), 2) \ V(maxNum, (BinaryFunc), 2) \ V(min, (BinaryFunc), 2) \ V(minNum, (BinaryFunc), 2) \ V(mul, (BinaryFunc), 2) \ V(notEqual, (CompareFunc), 2) \ - V(store, (Store), 3) \ - V(storeX, (Store), 3) \ + V(store, (Store), 3) \ + V(store1, (Store), 3) \ V(sub, (BinaryFunc), 2) \ V(withX, (FuncWith), 2) \ V(withY, (FuncWith), 2) @@ -148,10 +148,10 @@ V(greaterThanOrEqual, (CompareFunc), 2) \ V(lessThan, (CompareFunc), 2) \ V(lessThanOrEqual, (CompareFunc), 2) \ - V(load, (Load), 2) \ - V(loadXYZ, (Load), 2) \ - V(loadXY, (Load), 2) \ - V(loadX, (Load), 2) \ + V(load, (Load), 2) \ + V(load3, (Load), 2) \ + V(load2, (Load), 2) \ + V(load1, (Load), 2) \ V(mul, (BinaryFunc), 2) \ V(notEqual, (CompareFunc), 2) \ V(or, (BinaryFunc), 2) \ @@ -159,10 +159,10 @@ V(shiftLeftByScalar, (Int32x4BinaryScalar), 2) \ V(shiftRightArithmeticByScalar, (Int32x4BinaryScalar), 2) \ V(shiftRightLogicalByScalar, (Int32x4BinaryScalar), 2) \ - V(store, (Store), 3) \ - V(storeXYZ, (Store), 3) \ - V(storeXY, (Store), 3) \ - V(storeX, (Store), 3) \ + V(store, (Store), 3) \ + V(store3, (Store), 3) \ + V(store2, (Store), 3) \ + V(store1, (Store), 3) \ V(withX, (FuncWith), 2) \ V(withY, (FuncWith), 2) \ V(withZ, (FuncWith), 2) \ @@ -244,13 +244,13 @@ _(swizzle) \ _(shuffle) \ _(load) \ - _(loadX) \ - _(loadXY) \ - _(loadXYZ) \ + _(load1) \ + _(load2) \ + _(load3) \ _(store) \ - _(storeX) \ - _(storeXY) \ - _(storeXYZ) \ + _(store1) \ + _(store2) \ + _(store3) \ _(check) #define ION_ONLY_INT32X4_SIMD_OP(_) \ _(bool) diff --git a/js/src/builtin/SymbolObject.cpp b/js/src/builtin/SymbolObject.cpp index 914827e557..761ce1f46c 100644 --- a/js/src/builtin/SymbolObject.cpp +++ b/js/src/builtin/SymbolObject.cpp @@ -161,7 +161,7 @@ SymbolObject::keyFor(JSContext* cx, unsigned argc, Value* vp) HandleValue arg = args.get(0); if (!arg.isSymbol()) { ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, - arg, js::NullPtr(), "not a symbol", nullptr); + arg, js::NullPtr(), "not a symbol", nullptr); return false; } diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 8e6730a486..bb8b8e6f36 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -708,6 +708,22 @@ GCSlice(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +AbortGC(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() != 0) { + RootedObject callee(cx, &args.callee()); + ReportUsageError(cx, callee, "Wrong number of arguments"); + return false; + } + + cx->runtime()->gc.abortGC(); + args.rval().setUndefined(); + return true; +} + static bool ValidateGC(JSContext* cx, unsigned argc, jsval* vp) { @@ -1132,7 +1148,7 @@ MakeFinalizeObserver(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); - JSObject *obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, JS::NullPtr()); + JSObject* obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, JS::NullPtr()); if (!obj) return false; @@ -2667,6 +2683,10 @@ gc::ZealModeHelpText), "gcslice([n])", " Start or continue an an incremental GC, running a slice that processes about n objects."), + JS_FN_HELP("abortgc", AbortGC, 1, 0, +"abortgc()", +" Abort the current incremental GC."), + JS_FN_HELP("validategc", ValidateGC, 1, 0, "validategc(true|false)", " If true, a separate validation step is performed after an incremental GC."), diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index d667bc0d16..0eaae61ed0 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -439,7 +439,7 @@ class ArrayTypeDescr : public ComplexTypeDescr class StructMetaTypeDescr : public NativeObject { private: - static JSObject *create(JSContext *cx, HandleObject structTypeGlobal, + static JSObject* create(JSContext* cx, HandleObject structTypeGlobal, HandleObject fields); public: @@ -455,7 +455,7 @@ class StructMetaTypeDescr : public NativeObject // This is the function that gets called when the user // does `new StructType(...)`. It produces a struct type object. - static bool construct(JSContext *cx, unsigned argc, Value *vp); + static bool construct(JSContext* cx, unsigned argc, Value* vp); }; class StructTypeDescr : public ComplexTypeDescr @@ -570,8 +570,8 @@ class TypedObject : public JSObject int32_t offset() const; int32_t length() const; - uint8_t *typedMem() const; - uint8_t *typedMemBase() const; + uint8_t* typedMem() const; + uint8_t* typedMemBase() const; bool isAttached() const; bool maybeForwardedIsAttached() const; @@ -579,7 +579,7 @@ class TypedObject : public JSObject return typeDescr().size(); } - uint8_t *typedMem(size_t offset) const { + uint8_t* typedMem(size_t offset) const { // It seems a bit surprising that one might request an offset // == size(), but it can happen when taking the "address of" a // 0-sized value. (In other words, we maintain the invariant @@ -594,18 +594,18 @@ class TypedObject : public JSObject // Creates a new typed object whose memory is freshly allocated and // initialized with zeroes (or, in the case of references, an appropriate // default value). - static TypedObject *createZeroed(JSContext *cx, HandleTypeDescr typeObj, int32_t length, + static TypedObject* createZeroed(JSContext* cx, HandleTypeDescr typeObj, int32_t length, gc::InitialHeap heap = gc::DefaultHeap); // User-accessible constructor (`new TypeDescriptor(...)`). Note that the // callee here is the type descriptor. - static bool construct(JSContext *cx, unsigned argc, Value *vp); + static bool construct(JSContext* cx, unsigned argc, Value* vp); /* Accessors for self hosted code. */ - static bool GetBuffer(JSContext *cx, unsigned argc, Value *vp); - static bool GetByteOffset(JSContext *cx, unsigned argc, Value *vp); + static bool GetBuffer(JSContext* cx, unsigned argc, Value* vp); + static bool GetByteOffset(JSContext* cx, unsigned argc, Value* vp); - Shape *shapeFromGC() { return shape_; } + Shape** addressOfShapeFromGC() { return shape_.unsafeGet(); } }; typedef Handle HandleTypedObject; @@ -715,8 +715,8 @@ class InlineTypedObject : public TypedObject return gc::GetGCObjectKindForBytes(nbytes + sizeof(TypedObject)); } - uint8_t *inlineTypedMem() const { - return (uint8_t *) &data_; + uint8_t* inlineTypedMem() const { + return (uint8_t*) &data_; } static void obj_trace(JSTracer* trace, JSObject* object); @@ -1021,8 +1021,8 @@ TypedObject::opaque() const return IsOpaqueTypedObjectClass(getClass()); } -JSObject * -InitTypedObjectModuleObject(JSContext *cx, JS::HandleObject obj); +JSObject* +InitTypedObjectModuleObject(JSContext* cx, JS::HandleObject obj); } // namespace js diff --git a/js/src/ctypes/CTypes.cpp b/js/src/ctypes/CTypes.cpp index 0587598dda..42aefc04dc 100644 --- a/js/src/ctypes/CTypes.cpp +++ b/js/src/ctypes/CTypes.cpp @@ -32,6 +32,7 @@ #endif #include "jscntxt.h" +#include "jsexn.h" #include "jsfun.h" #include "jsnum.h" #include "jsprf.h" @@ -40,6 +41,8 @@ #include "ctypes/Library.h" #include "gc/Zone.h" +#include "jsatominlines.h" + using namespace std; using mozilla::NumericLimits; @@ -876,21 +879,594 @@ GetErrorMessage(void* userRef, const unsigned errorNumber) return nullptr; } +static const char* +EncodeLatin1(JSContext* cx, AutoString& str, JSAutoByteString& bytes) +{ + return bytes.encodeLatin1(cx, NewUCString(cx, str)); +} + +static const char* +CTypesToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes) +{ + if (val.isObject() && + (CType::IsCType(&val.toObject()) || CData::IsCData(&val.toObject()))) { + RootedString str(cx, JS_ValueToSource(cx, val)); + return bytes.encodeLatin1(cx, str); + } + return ValueToSourceForError(cx, val, bytes); +} + +static void +BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj, + HandleString nameStr, unsigned ptrCount, + AutoString& source); + +static void +BuildCStyleTypeSource(JSContext* cx, JSObject* typeObj_, AutoString& source) +{ + RootedObject typeObj(cx, typeObj_); + + MOZ_ASSERT(CType::IsCType(typeObj)); + + switch (CType::GetTypeCode(typeObj)) { +#define BUILD_SOURCE(name, fromType, ffiType) \ + case TYPE_##name: \ + AppendString(source, #name); \ + break; + CTYPES_FOR_EACH_TYPE(BUILD_SOURCE) +#undef BUILD_SOURCE + case TYPE_void_t: + AppendString(source, "void"); + break; + case TYPE_pointer: { + unsigned ptrCount = 0; + TypeCode type; + RootedObject baseTypeObj(cx, typeObj); + do { + baseTypeObj = PointerType::GetBaseType(baseTypeObj); + ptrCount++; + type = CType::GetTypeCode(baseTypeObj); + } while (type == TYPE_pointer || type == TYPE_array); + if (type == TYPE_function) { + BuildCStyleFunctionTypeSource(cx, baseTypeObj, NullPtr(), ptrCount, + source); + break; + } + BuildCStyleTypeSource(cx, baseTypeObj, source); + AppendChars(source, '*', ptrCount); + break; + } + case TYPE_struct: { + RootedString name(cx, CType::GetName(cx, typeObj)); + AppendString(source, "struct "); + AppendString(source, name); + break; + } + case TYPE_function: + BuildCStyleFunctionTypeSource(cx, typeObj, NullPtr(), 0, source); + break; + case TYPE_array: + MOZ_CRASH("TYPE_array shouldn't appear in function type"); + } +} + +static void +BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj, + HandleString nameStr, unsigned ptrCount, + AutoString& source) +{ + MOZ_ASSERT(CType::IsCType(typeObj)); + + FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj); + BuildCStyleTypeSource(cx, fninfo->mReturnType, source); + AppendString(source, " "); + if (nameStr) { + MOZ_ASSERT(ptrCount == 0); + AppendString(source, nameStr); + } else if (ptrCount) { + AppendString(source, "("); + AppendChars(source, '*', ptrCount); + AppendString(source, ")"); + } + AppendString(source, "("); + if (fninfo->mArgTypes.length() > 0) { + for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) { + BuildCStyleTypeSource(cx, fninfo->mArgTypes[i], source); + if (i != fninfo->mArgTypes.length() - 1 || fninfo->mIsVariadic) { + AppendString(source, ", "); + } + } + if (fninfo->mIsVariadic) { + AppendString(source, "..."); + } + } + AppendString(source, ")"); +} + +static void +BuildFunctionTypeSource(JSContext* cx, HandleObject funObj, AutoString& source) +{ + MOZ_ASSERT(CData::IsCData(funObj) || CType::IsCType(funObj)); + + if (CData::IsCData(funObj)) { + jsval slot = JS_GetReservedSlot(funObj, SLOT_REFERENT); + if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) { + slot = JS_GetReservedSlot(funObj, SLOT_FUNNAME); + MOZ_ASSERT(!slot.isUndefined()); + RootedObject typeObj(cx, CData::GetCType(funObj)); + RootedObject baseTypeObj(cx, PointerType::GetBaseType(typeObj)); + RootedString nameStr(cx, slot.toString()); + BuildCStyleFunctionTypeSource(cx, baseTypeObj, nameStr, 0, source); + return; + } + } + + RootedValue funVal(cx, ObjectValue(*funObj)); + RootedString funcStr(cx, JS_ValueToSource(cx, funVal)); + if (!funcStr) { + JS_ClearPendingException(cx); + AppendString(source, "<>"); + return; + } + AppendString(source, funcStr); +} + +enum class ConversionType { + Argument = 0, + Construct, + Finalizer, + Return, + Setter +}; + +static void +BuildConversionPosition(JSContext* cx, ConversionType convType, + HandleObject funObj, unsigned argIndex, + AutoString& source) +{ + switch (convType) { + case ConversionType::Argument: { + MOZ_ASSERT(funObj); + + AppendString(source, " at argument "); + AppendUInt(source, argIndex + 1); + AppendString(source, " of "); + BuildFunctionTypeSource(cx, funObj, source); + break; + } + case ConversionType::Finalizer: + MOZ_ASSERT(funObj); + + AppendString(source, " at argument 1 of "); + BuildFunctionTypeSource(cx, funObj, source); + break; + case ConversionType::Return: + MOZ_ASSERT(funObj); + + AppendString(source, " at the return value of "); + BuildFunctionTypeSource(cx, funObj, source); + break; + default: + MOZ_ASSERT(!funObj); + break; + } +} + +static JSFlatString* +GetFieldName(HandleObject structObj, unsigned fieldIndex) +{ + const FieldInfoHash* fields = StructType::GetFieldInfo(structObj); + for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { + if (r.front().value().mIndex == fieldIndex) { + return (&r.front())->key(); + } + } + return nullptr; +} + +static void +BuildTypeSource(JSContext* cx, JSObject* typeObj_, bool makeShort, + AutoString& result); + +static bool +ConvError(JSContext* cx, const char* expectedStr, HandleValue actual, + ConversionType convType, + HandleObject funObj = NullPtr(), unsigned argIndex = 0, + HandleObject arrObj = NullPtr(), unsigned arrIndex = 0) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + if (arrObj) { + MOZ_ASSERT(CType::IsCType(arrObj)); + + switch (CType::GetTypeCode(arrObj)) { + case TYPE_array: { + MOZ_ASSERT(!funObj); + + char indexStr[16]; + JS_snprintf(indexStr, 16, "%u", arrIndex); + + AutoString arrSource; + JSAutoByteString arrBytes; + BuildTypeSource(cx, arrObj, true, arrSource); + const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes); + if (!arrStr) + return false; + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_ARRAY, + valStr, indexStr, arrStr); + break; + } + case TYPE_struct: { + JSFlatString* name = GetFieldName(arrObj, arrIndex); + MOZ_ASSERT(name); + JSAutoByteString nameBytes; + const char* nameStr = nameBytes.encodeLatin1(cx, name); + if (!nameStr) + return false; + + AutoString structSource; + JSAutoByteString structBytes; + BuildTypeSource(cx, arrObj, true, structSource); + const char* structStr = EncodeLatin1(cx, structSource, structBytes); + if (!structStr) + return false; + + JSAutoByteString posBytes; + const char* posStr; + if (funObj) { + AutoString posSource; + BuildConversionPosition(cx, convType, funObj, argIndex, posSource); + posStr = EncodeLatin1(cx, posSource, posBytes); + if (!posStr) + return false; + } else { + posStr = ""; + } + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_STRUCT, + valStr, nameStr, expectedStr, structStr, posStr); + break; + } + default: + MOZ_CRASH("invalid arrObj value"); + } + return false; + } + + switch (convType) { + case ConversionType::Argument: { + MOZ_ASSERT(funObj); + + char indexStr[16]; + JS_snprintf(indexStr, 16, "%u", argIndex + 1); + + AutoString funSource; + JSAutoByteString funBytes; + BuildFunctionTypeSource(cx, funObj, funSource); + const char* funStr = EncodeLatin1(cx, funSource, funBytes); + if (!funStr) + return false; + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_ARG, + valStr, indexStr, funStr); + break; + } + case ConversionType::Finalizer: { + MOZ_ASSERT(funObj); + + AutoString funSource; + JSAutoByteString funBytes; + BuildFunctionTypeSource(cx, funObj, funSource); + const char* funStr = EncodeLatin1(cx, funSource, funBytes); + if (!funStr) + return false; + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_FIN, valStr, funStr); + break; + } + case ConversionType::Return: { + MOZ_ASSERT(funObj); + + AutoString funSource; + JSAutoByteString funBytes; + BuildFunctionTypeSource(cx, funObj, funSource); + const char* funStr = EncodeLatin1(cx, funSource, funBytes); + if (!funStr) + return false; + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_RET, valStr, funStr); + break; + } + case ConversionType::Setter: + case ConversionType::Construct: + MOZ_ASSERT(!funObj); + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_SET, valStr, expectedStr); + break; + } + + return false; +} + +static bool +ConvError(JSContext* cx, HandleObject expectedType, HandleValue actual, + ConversionType convType, + HandleObject funObj = NullPtr(), unsigned argIndex = 0, + HandleObject arrObj = NullPtr(), unsigned arrIndex = 0) +{ + MOZ_ASSERT(CType::IsCType(expectedType)); + + AutoString expectedSource; + JSAutoByteString expectedBytes; + BuildTypeSource(cx, expectedType, true, expectedSource); + const char* expectedStr = EncodeLatin1(cx, expectedSource, expectedBytes); + if (!expectedStr) + return false; + + return ConvError(cx, expectedStr, actual, convType, funObj, argIndex, + arrObj, arrIndex); +} + +static bool +ArgumentConvError(JSContext* cx, HandleValue actual, const char* funStr, + unsigned argIndex) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + char indexStr[16]; + JS_snprintf(indexStr, 16, "%u", argIndex + 1); + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_CONV_ERROR_ARG, valStr, indexStr, funStr); + return false; +} + +static bool +ArgumentLengthError(JSContext* cx, const char* fun, const char* count, + const char* s) +{ + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_WRONG_ARG_LENGTH, fun, count, s); + return false; +} + +static bool +ArrayLengthMismatch(JSContext* cx, unsigned expectedLength, HandleObject arrObj, + unsigned actualLength, HandleValue actual, + ConversionType convType) +{ + MOZ_ASSERT(arrObj && CType::IsCType(arrObj)); + + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + char expectedLengthStr[16]; + JS_snprintf(expectedLengthStr, 16, "%u", expectedLength); + char actualLengthStr[16]; + JS_snprintf(actualLengthStr, 16, "%u", actualLength); + + AutoString arrSource; + JSAutoByteString arrBytes; + BuildTypeSource(cx, arrObj, true, arrSource); + const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes); + if (!arrStr) + return false; + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_ARRAY_MISMATCH, + valStr, arrStr, expectedLengthStr, actualLengthStr); + return false; +} + +static bool +ArrayLengthOverflow(JSContext* cx, unsigned expectedLength, HandleObject arrObj, + unsigned actualLength, HandleValue actual, + ConversionType convType) +{ + MOZ_ASSERT(arrObj && CType::IsCType(arrObj)); + + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + char expectedLengthStr[16]; + JS_snprintf(expectedLengthStr, 16, "%u", expectedLength); + char actualLengthStr[16]; + JS_snprintf(actualLengthStr, 16, "%u", actualLength); + + AutoString arrSource; + JSAutoByteString arrBytes; + BuildTypeSource(cx, arrObj, true, arrSource); + const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes); + if (!arrStr) + return false; + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_ARRAY_OVERFLOW, + valStr, arrStr, expectedLengthStr, actualLengthStr); + return false; +} + +static bool +ArgumentRangeMismatch(JSContext* cx, const char* arg, const char* func, + const char* range) +{ + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_ARG_RANGE_MISMATCH, arg, func, range); + return false; +} + +static bool +ArgumentTypeMismatch(JSContext* cx, const char* arg, const char* func, + const char* type) +{ + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_ARG_TYPE_MISMATCH, arg, func, type); + return false; +} + +static bool +EmptyFinalizerError(JSContext* cx, ConversionType convType, + HandleObject funObj = NullPtr(), unsigned argIndex = 0) +{ + JSAutoByteString posBytes; + const char* posStr; + if (funObj) { + AutoString posSource; + BuildConversionPosition(cx, convType, funObj, argIndex, posSource); + posStr = EncodeLatin1(cx, posSource, posBytes); + if (!posStr) + return false; + } else { + posStr = ""; + } + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_EMPTY_FIN, posStr); + return false; +} + +static bool +FieldCountMismatch(JSContext* cx, + unsigned expectedCount, HandleObject structObj, + unsigned actualCount, HandleValue actual, + ConversionType convType, + HandleObject funObj = NullPtr(), unsigned argIndex = 0) +{ + MOZ_ASSERT(structObj && CType::IsCType(structObj)); + + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + AutoString structSource; + JSAutoByteString structBytes; + BuildTypeSource(cx, structObj, true, structSource); + const char* structStr = EncodeLatin1(cx, structSource, structBytes); + if (!structStr) + return false; + + char expectedCountStr[16]; + JS_snprintf(expectedCountStr, 16, "%u", expectedCount); + char actualCountStr[16]; + JS_snprintf(actualCountStr, 16, "%u", actualCount); + + JSAutoByteString posBytes; + const char* posStr; + if (funObj) { + AutoString posSource; + BuildConversionPosition(cx, convType, funObj, argIndex, posSource); + posStr = EncodeLatin1(cx, posSource, posBytes); + if (!posStr) + return false; + } else { + posStr = ""; + } + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIELD_MISMATCH, + valStr, structStr, expectedCountStr, actualCountStr, + posStr); + return false; +} + +static bool +FinalizerSizeError(JSContext* cx, HandleObject funObj, HandleValue actual) +{ + MOZ_ASSERT(CType::IsCType(funObj)); + + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + AutoString funSource; + JSAutoByteString funBytes; + BuildFunctionTypeSource(cx, funObj, funSource); + const char* funStr = EncodeLatin1(cx, funSource, funBytes); + if (!funStr) + return false; + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_FIN_SIZE_ERROR, funStr, valStr); + return false; +} + +static bool +NonPrimitiveError(JSContext* cx, HandleObject typeObj) +{ + MOZ_ASSERT(CType::IsCType(typeObj)); + + AutoString typeSource; + JSAutoByteString typeBytes; + BuildTypeSource(cx, typeObj, true, typeSource); + const char* typeStr = EncodeLatin1(cx, typeSource, typeBytes); + if (!typeStr) + return false; + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_NON_PRIMITIVE, typeStr); + return false; +} + +static bool +PropNameNonStringError(JSContext* cx, HandleId id, HandleValue actual, + ConversionType convType, + HandleObject funObj = NullPtr(), unsigned argIndex = 0) +{ + JSAutoByteString valBytes; + const char* valStr = CTypesToSourceForError(cx, actual, valBytes); + if (!valStr) + return false; + + JSAutoByteString idBytes; + RootedValue idVal(cx, IdToValue(id)); + const char* propStr = CTypesToSourceForError(cx, idVal, idBytes); + if (!propStr) + return false; + + JSAutoByteString posBytes; + const char* posStr; + if (funObj) { + AutoString posSource; + BuildConversionPosition(cx, convType, funObj, argIndex, posSource); + posStr = EncodeLatin1(cx, posSource, posBytes); + if (!posStr) + return false; + } else { + posStr = ""; + } + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + CTYPESMSG_PROP_NONSTRING, propStr, valStr, posStr); + return false; +} + static bool TypeError(JSContext* cx, const char* expected, HandleValue actual) { - JSString* str = JS_ValueToSource(cx, actual); JSAutoByteString bytes; + const char* src = CTypesToSourceForError(cx, actual, bytes); + if (!src) + return false; - const char* src; - if (str) { - src = bytes.encodeLatin1(cx, str); - if (!src) - return false; - } else { - JS_ClearPendingException(cx); - src = "<>"; - } JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, CTYPESMSG_TYPE_ERROR, expected, src); return false; @@ -2210,8 +2786,7 @@ ConvertToJS(JSContext* cx, // We're about to create a new CData object to return. If the caller doesn't // want this, return early. if (wantPrimitive) { - JS_ReportError(cx, "cannot convert to primitive value"); - return false; + return NonPrimitiveError(cx, typeObj); } JSObject* obj = CData::Create(cx, typeObj, parentObj, data, ownResult); @@ -2276,18 +2851,20 @@ bool CanConvertTypedArrayItemTo(JSObject* baseType, JSObject* valObj, JSContext* // coercion between types. There are two cases in which this function is used: // 1) The target buffer is internal to a CData object; we simply write data // into it. -// 2) We are converting an argument for an ffi call, in which case 'isArgument' -// will be true. This allows us to handle a special case: if necessary, -// we can autoconvert a JS string primitive to a pointer-to-character type. -// In this case, ownership of the allocated string is handed off to the -// caller; 'freePointer' will be set to indicate this. +// 2) We are converting an argument for an ffi call, in which case 'convType' +// will be 'ConversionType::Argument'. This allows us to handle a special +// case: if necessary, we can autoconvert a JS string primitive to a +// pointer-to-character type. In this case, ownership of the allocated string +// is handed off to the caller; 'freePointer' will be set to indicate this. static bool ImplicitConvert(JSContext* cx, HandleValue val, JSObject* targetType_, void* buffer, - bool isArgument, - bool* freePointer) + ConversionType convType, + bool* freePointer, + HandleObject funObj = NullPtr(), unsigned argIndex = 0, + HandleObject arrObj = NullPtr(), unsigned arrIndex = 0) { RootedObject targetType(cx, targetType_); MOZ_ASSERT(CType::IsSizeDefined(targetType)); @@ -2319,8 +2896,7 @@ ImplicitConvert(JSContext* cx, if (!p) { // We have called |dispose| or |forget| already. - JS_ReportError(cx, "Attempting to convert an empty CDataFinalizer"); - return false; + return EmptyFinalizerError(cx, convType, funObj, argIndex); } // If the types are equal, copy the buffer contained within the CData. @@ -2339,7 +2915,8 @@ ImplicitConvert(JSContext* cx, // Programs can convert explicitly, if needed, using `Boolean(v)` or `!!v`. bool result; if (!jsvalToBool(cx, val, &result)) - return TypeError(cx, "boolean", val); + return ConvError(cx, "boolean", val, convType, funObj, argIndex, + arrObj, arrIndex); *static_cast(buffer) = result; break; } @@ -2351,13 +2928,15 @@ ImplicitConvert(JSContext* cx, if (val.isString()) { \ JSString* str = val.toString(); \ if (str->length() != 1) \ - return TypeError(cx, #name, val); \ + return ConvError(cx, #name, val, convType, funObj, argIndex, \ + arrObj, arrIndex); \ JSLinearString* linear = str->ensureLinear(cx); \ if (!linear) \ return false; \ result = linear->latin1OrTwoByteChar(0); \ } else if (!jsvalToInteger(cx, val, &result)) { \ - return TypeError(cx, #name, val); \ + return ConvError(cx, #name, val, convType, funObj, argIndex, \ + arrObj, arrIndex); \ } \ *static_cast(buffer) = result; \ break; \ @@ -2369,7 +2948,8 @@ ImplicitConvert(JSContext* cx, /* Do not implicitly lose bits. */ \ type result; \ if (!jsvalToInteger(cx, val, &result)) \ - return TypeError(cx, #name, val); \ + return ConvError(cx, #name, val, convType, funObj, argIndex, \ + arrObj, arrIndex); \ *static_cast(buffer) = result; \ break; \ } @@ -2385,7 +2965,8 @@ ImplicitConvert(JSContext* cx, case TYPE_##name: { \ type result; \ if (!jsvalToFloat(cx, val, &result)) \ - return TypeError(cx, #name, val); \ + return ConvError(cx, #name, val, convType, funObj, argIndex, \ + arrObj, arrIndex); \ *static_cast(buffer) = result; \ break; \ } @@ -2420,7 +3001,7 @@ ImplicitConvert(JSContext* cx, } } - } else if (isArgument && val.isString()) { + } else if (convType == ConversionType::Argument && val.isString()) { // Convert the string for the ffi call. This requires allocating space // which the caller assumes ownership of. // TODO: Extend this so we can safely convert strings at other times also. @@ -2474,7 +3055,8 @@ ImplicitConvert(JSContext* cx, break; } default: - return TypeError(cx, "string pointer", val); + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } break; } else if (val.isObject() && JS_IsArrayBufferObject(valObj)) { @@ -2482,8 +3064,9 @@ ImplicitConvert(JSContext* cx, // when converting an argument to a function call, as it is possible for // the pointer to be invalidated by anything that runs JS code. (It is // invalid to invoke JS code from a ctypes function call.) - if (!isArgument) { - return TypeError(cx, "arraybuffer pointer", val); + if (convType != ConversionType::Argument) { + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } void* ptr; { @@ -2491,7 +3074,8 @@ ImplicitConvert(JSContext* cx, ptr = JS_GetArrayBufferData(valObj, nogc); } if (!ptr) { - return TypeError(cx, "arraybuffer pointer", val); + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } *static_cast(buffer) = ptr; break; @@ -2499,10 +3083,12 @@ ImplicitConvert(JSContext* cx, // Same as ArrayBuffer, above, though note that this will take the // offset of the view into account. if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) { - return TypeError(cx, "typed array with the appropriate type", val); + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } - if (!isArgument) { - return TypeError(cx, "typed array pointer", val); + if (convType != ConversionType::Argument) { + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } void* ptr; { @@ -2510,14 +3096,18 @@ ImplicitConvert(JSContext* cx, ptr = JS_GetArrayBufferViewData(valObj, nogc); } if (!ptr) { - return TypeError(cx, "typed array pointer", val); + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } *static_cast(buffer) = ptr; break; } - return TypeError(cx, "pointer", val); + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } case TYPE_array: { + MOZ_ASSERT(!funObj); + RootedObject baseType(cx, ArrayType::GetBaseType(targetType)); size_t targetLength = ArrayType::GetLength(targetType); @@ -2539,8 +3129,9 @@ ImplicitConvert(JSContext* cx, return false; if (targetLength < nbytes) { - JS_ReportError(cx, "ArrayType has insufficient length"); - return false; + MOZ_ASSERT(!funObj); + return ArrayLengthOverflow(cx, targetLength, targetType, nbytes, val, + convType); } char* charBuffer = static_cast(buffer); @@ -2556,8 +3147,9 @@ ImplicitConvert(JSContext* cx, // Copy the string data, char16_t for char16_t, including the terminator // if there's space. if (targetLength < sourceLength) { - JS_ReportError(cx, "ArrayType has insufficient length"); - return false; + MOZ_ASSERT(!funObj); + return ArrayLengthOverflow(cx, targetLength, targetType, + sourceLength, val, convType); } char16_t* dest = static_cast(buffer); @@ -2575,7 +3167,8 @@ ImplicitConvert(JSContext* cx, break; } default: - return TypeError(cx, "array", val); + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } } else if (val.isObject() && JS_IsArrayObject(cx, valObj)) { @@ -2583,8 +3176,9 @@ ImplicitConvert(JSContext* cx, uint32_t sourceLength; if (!JS_GetArrayLength(cx, valObj, &sourceLength) || targetLength != size_t(sourceLength)) { - JS_ReportError(cx, "ArrayType length does not match source array length"); - return false; + MOZ_ASSERT(!funObj); + return ArrayLengthMismatch(cx, targetLength, targetType, + size_t(sourceLength), val, convType); } // Convert into an intermediate, in case of failure. @@ -2602,7 +3196,8 @@ ImplicitConvert(JSContext* cx, return false; char* data = intermediate.get() + elementSize * i; - if (!ImplicitConvert(cx, item, baseType, data, false, nullptr)) + if (!ImplicitConvert(cx, item, baseType, data, convType, nullptr, + funObj, argIndex, targetType, i)) return false; } @@ -2615,8 +3210,9 @@ ImplicitConvert(JSContext* cx, size_t elementSize = CType::GetSize(baseType); size_t arraySize = elementSize * targetLength; if (arraySize != size_t(sourceLength)) { - JS_ReportError(cx, "ArrayType length does not match source ArrayBuffer length"); - return false; + MOZ_ASSERT(!funObj); + return ArrayLengthMismatch(cx, arraySize, targetType, + size_t(sourceLength), val, convType); } JS::AutoCheckCannotGC nogc; memcpy(buffer, JS_GetArrayBufferData(valObj, nogc), sourceLength); @@ -2625,15 +3221,17 @@ ImplicitConvert(JSContext* cx, // Check that array is consistent with type, then // copy the array. if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) { - return TypeError(cx, "typed array with the appropriate type", val); + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj); size_t elementSize = CType::GetSize(baseType); size_t arraySize = elementSize * targetLength; if (arraySize != size_t(sourceLength)) { - JS_ReportError(cx, "typed array length does not match source TypedArray length"); - return false; + MOZ_ASSERT(!funObj); + return ArrayLengthMismatch(cx, arraySize, targetType, + size_t(sourceLength), val, convType); } JS::AutoCheckCannotGC nogc; memcpy(buffer, JS_GetArrayBufferViewData(valObj, nogc), sourceLength); @@ -2641,7 +3239,8 @@ ImplicitConvert(JSContext* cx, } else { // Don't implicitly convert to string. Users can implicitly convert // with `String(x)` or `""+x`. - return TypeError(cx, "array", val); + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } break; } @@ -2663,8 +3262,9 @@ ImplicitConvert(JSContext* cx, const FieldInfoHash* fields = StructType::GetFieldInfo(targetType); if (props.length() != fields->count()) { - JS_ReportError(cx, "missing fields"); - return false; + return FieldCountMismatch(cx, fields->count(), targetType, + props.length(), val, convType, + funObj, argIndex); } RootedId id(cx); @@ -2672,8 +3272,8 @@ ImplicitConvert(JSContext* cx, id = props[i]; if (!JSID_IS_STRING(id)) { - JS_ReportError(cx, "property name is not a string"); - return false; + return PropNameNonStringError(cx, id, val, convType, + funObj, argIndex); } JSFlatString* name = JSID_TO_FLAT_STRING(id); @@ -2687,7 +3287,8 @@ ImplicitConvert(JSContext* cx, // Convert the field via ImplicitConvert(). char* fieldData = intermediate.get() + field->mOffset; - if (!ImplicitConvert(cx, prop, field->mType, fieldData, false, nullptr)) + if (!ImplicitConvert(cx, prop, field->mType, fieldData, convType, + nullptr, funObj, argIndex, targetType, i)) return false; } @@ -2695,7 +3296,8 @@ ImplicitConvert(JSContext* cx, break; } - return TypeError(cx, "struct", val); + return ConvError(cx, targetType, val, convType, funObj, argIndex, + arrObj, arrIndex); } case TYPE_void_t: case TYPE_function: @@ -2709,10 +3311,11 @@ ImplicitConvert(JSContext* cx, // storing the result in 'buffer'. This function is more forceful than // ImplicitConvert. static bool -ExplicitConvert(JSContext* cx, HandleValue val, HandleObject targetType, void* buffer) +ExplicitConvert(JSContext* cx, HandleValue val, HandleObject targetType, + void* buffer, ConversionType convType) { // If ImplicitConvert succeeds, use that result. - if (ImplicitConvert(cx, val, targetType, buffer, false, nullptr)) + if (ImplicitConvert(cx, val, targetType, buffer, convType, nullptr)) return true; // If ImplicitConvert failed, and there is no pending exception, then assume @@ -2741,7 +3344,7 @@ ExplicitConvert(JSContext* cx, HandleValue val, HandleObject targetType, void* b if (!jsvalToIntegerExplicit(val, &result) && \ (!val.isString() || \ !StringToInteger(cx, val.toString(), &result))) \ - return TypeError(cx, #name, val); \ + return ConvError(cx, #name, val, convType); \ *static_cast(buffer) = result; \ break; \ } @@ -2754,7 +3357,7 @@ ExplicitConvert(JSContext* cx, HandleValue val, HandleObject targetType, void* b // Convert a number, Int64 object, or UInt64 object to a pointer. uintptr_t result; if (!jsvalToPtrExplicit(cx, val, &result)) - return TypeError(cx, "pointer", val); + return ConvError(cx, targetType, val, convType); *static_cast(buffer) = result; break; } @@ -3258,8 +3861,7 @@ CType::ConstructBasic(JSContext* cx, const CallArgs& args) { if (args.length() > 1) { - JS_ReportError(cx, "CType constructor takes zero or one argument"); - return false; + return ArgumentLengthError(cx, "CType constructor", "at most one", ""); } // construct a CData object @@ -3268,7 +3870,8 @@ CType::ConstructBasic(JSContext* cx, return false; if (args.length() == 1) { - if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result))) + if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result), + ConversionType::Construct)) return false; } @@ -3780,15 +4383,14 @@ CType::CreateArray(JSContext* cx, unsigned argc, jsval* vp) // Construct and return a new ArrayType object. if (args.length() > 1) { - JS_ReportError(cx, "array takes zero or one argument"); - return false; + return ArgumentLengthError(cx, "CType.prototype.array", "at most one", ""); } // Convert the length argument to a size_t. size_t length = 0; if (args.length() == 1 && !jsvalToSize(cx, args[0], false, &length)) { - JS_ReportError(cx, "argument must be a nonnegative integer"); - return false; + return ArgumentTypeMismatch(cx, "", "CType.prototype.array", + "a nonnegative integer"); } JSObject* result = ArrayType::CreateInternal(cx, baseType, length, args.length() == 1); @@ -3920,8 +4522,7 @@ ABI::ToSource(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { - JS_ReportError(cx, "toSource takes zero arguments"); - return false; + return ArgumentLengthError(cx, "ABI.prototype.toSource", "no", "s"); } JSObject* obj = JS_THIS_OBJECT(cx, vp); @@ -3965,15 +4566,13 @@ PointerType::Create(JSContext* cx, unsigned argc, jsval* vp) CallArgs args = CallArgsFromVp(argc, vp); // Construct and return a new PointerType object. if (args.length() != 1) { - JS_ReportError(cx, "PointerType takes one argument"); - return false; + return ArgumentLengthError(cx, "PointerType", "one", ""); } jsval arg = args[0]; RootedObject obj(cx); if (arg.isPrimitive() || !CType::IsCType(obj = &arg.toObject())) { - JS_ReportError(cx, "first argument must be a CType"); - return false; + return ArgumentTypeMismatch(cx, "", "PointerType", "a CType"); } JSObject* result = CreateInternal(cx, obj); @@ -4031,8 +4630,8 @@ PointerType::ConstructData(JSContext* cx, } if (args.length() > 3) { - JS_ReportError(cx, "constructor takes 0, 1, 2, or 3 arguments"); - return false; + return ArgumentLengthError(cx, "PointerType constructor", "0, 1, 2, or 3", + "s"); } RootedObject result(cx, CData::Create(cx, obj, NullPtr(), nullptr, true)); @@ -4066,10 +4665,10 @@ PointerType::ConstructData(JSContext* cx, // if (!looksLikeClosure) { if (args.length() != 1) { - JS_ReportError(cx, "first argument must be a function"); - return false; + return ArgumentLengthError(cx, "FunctionType constructor", "one", ""); } - return ExplicitConvert(cx, args[0], obj, CData::GetData(result)); + return ExplicitConvert(cx, args[0], obj, CData::GetData(result), + ConversionType::Construct); } // @@ -4253,7 +4852,8 @@ PointerType::ContentsSetter(JSContext* cx, const JS::CallArgs& args) } args.rval().setUndefined(); - return ImplicitConvert(cx, args.get(0), baseType, data, false, nullptr); + return ImplicitConvert(cx, args.get(0), baseType, data, + ConversionType::Setter, nullptr); } /******************************************************************************* @@ -4266,21 +4866,18 @@ ArrayType::Create(JSContext* cx, unsigned argc, jsval* vp) CallArgs args = CallArgsFromVp(argc, vp); // Construct and return a new ArrayType object. if (args.length() < 1 || args.length() > 2) { - JS_ReportError(cx, "ArrayType takes one or two arguments"); - return false; + return ArgumentLengthError(cx, "ArrayType", "one or two", "s"); } - if (args[0].isPrimitive() || - !CType::IsCType(&args[0].toObject())) { - JS_ReportError(cx, "first argument must be a CType"); - return false; + if (args[0].isPrimitive() || !CType::IsCType(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "first ", "ArrayType", "a CType"); } // Convert the length argument to a size_t. size_t length = 0; if (args.length() == 2 && !jsvalToSize(cx, args[1], false, &length)) { - JS_ReportError(cx, "second argument must be a nonnegative integer"); - return false; + return ArgumentTypeMismatch(cx, "second ", "ArrayType", + "a nonnegative integer"); } RootedObject baseType(cx, &args[0].toObject()); @@ -4367,14 +4964,14 @@ ArrayType::ConstructData(JSContext* cx, // with a length argument, or with an actual JS array. if (CType::IsSizeDefined(obj)) { if (args.length() > 1) { - JS_ReportError(cx, "constructor takes zero or one argument"); - return false; + return ArgumentLengthError(cx, "size defined ArrayType constructor", + "at most one", ""); } } else { if (args.length() != 1) { - JS_ReportError(cx, "constructor takes one argument"); - return false; + return ArgumentLengthError(cx, "size undefined ArrayType constructor", + "one", ""); } RootedObject baseType(cx, GetBaseType(obj)); @@ -4391,8 +4988,9 @@ ArrayType::ConstructData(JSContext* cx, RootedValue lengthVal(cx); if (!JS_GetProperty(cx, arg, "length", &lengthVal) || !jsvalToSize(cx, lengthVal, false, &length)) { - JS_ReportError(cx, "argument must be an array object or length"); - return false; + return ArgumentTypeMismatch(cx, "", + "size undefined ArrayType constructor", + "an array object or integer"); } } else if (args[0].isString()) { @@ -4420,12 +5018,13 @@ ArrayType::ConstructData(JSContext* cx, length = sourceLength + 1; break; default: - return TypeError(cx, "array", args[0]); + return ConvError(cx, obj, args[0], ConversionType::Construct); } } else { - JS_ReportError(cx, "argument must be an array object or length"); - return false; + return ArgumentTypeMismatch(cx, "", + "size undefined ArrayType constructor", + "an array object or integer"); } // Construct a new ArrayType of defined length, for the new CData object. @@ -4441,7 +5040,8 @@ ArrayType::ConstructData(JSContext* cx, args.rval().setObject(*result); if (convertObject) { - if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result))) + if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result), + ConversionType::Construct)) return false; } @@ -4611,7 +5211,8 @@ ArrayType::Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandle size_t length = GetLength(typeObj); bool ok = jsidToSize(cx, idval, true, &index); int32_t dummy; - if (!ok && JSID_IS_STRING(idval) && !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) { + if (!ok && JSID_IS_STRING(idval) && + !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) { // String either isn't a number, or doesn't fit in size_t. // Chances are it's a regular property lookup, so return. return true; @@ -4639,7 +5240,7 @@ ArrayType::Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandle // Bail early if we're not an ArrayType. (This setter is present for all // CData, regardless of CType.) - JSObject* typeObj = CData::GetCType(obj); + RootedObject typeObj(cx, CData::GetCType(obj)); if (CType::GetTypeCode(typeObj) != TYPE_array) return result.succeed(); @@ -4648,7 +5249,8 @@ ArrayType::Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandle size_t length = GetLength(typeObj); bool ok = jsidToSize(cx, idval, true, &index); int32_t dummy; - if (!ok && JSID_IS_STRING(idval) && !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) { + if (!ok && JSID_IS_STRING(idval) && + !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) { // String either isn't a number, or doesn't fit in size_t. // Chances are it's a regular property lookup, so return. return result.succeed(); @@ -4658,10 +5260,11 @@ ArrayType::Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandle return false; } - JSObject* baseType = GetBaseType(typeObj); + RootedObject baseType(cx, GetBaseType(typeObj)); size_t elementSize = CType::GetSize(baseType); char* data = static_cast(CData::GetData(obj)) + elementSize * index; - if (!ImplicitConvert(cx, vp, baseType, data, false, nullptr)) + if (!ImplicitConvert(cx, vp, baseType, data, ConversionType::Setter, + nullptr, NullPtr(), 0, typeObj, index)) return false; return result.succeed(); } @@ -4685,8 +5288,8 @@ ArrayType::AddressOfElement(JSContext* cx, unsigned argc, jsval* vp) } if (args.length() != 1) { - JS_ReportError(cx, "addressOfElement takes one argument"); - return false; + return ArgumentLengthError(cx, "ArrayType.prototype.addressOfElement", + "one", ""); } RootedObject baseType(cx, GetBaseType(typeObj)); @@ -4806,14 +5409,12 @@ StructType::Create(JSContext* cx, unsigned argc, jsval* vp) // Construct and return a new StructType object. if (args.length() < 1 || args.length() > 2) { - JS_ReportError(cx, "StructType takes one or two arguments"); - return false; + return ArgumentLengthError(cx, "StructType", "one or two", "s"); } jsval name = args[0]; if (!name.isString()) { - JS_ReportError(cx, "first argument must be a string"); - return false; + return ArgumentTypeMismatch(cx, "first ", "StructType", "a string"); } // Get ctypes.StructType.prototype from the ctypes.StructType constructor. @@ -4830,8 +5431,7 @@ StructType::Create(JSContext* cx, unsigned argc, jsval* vp) if (args.length() == 2) { RootedObject arr(cx, args[1].isPrimitive() ? nullptr : &args[1].toObject()); if (!arr || !JS_IsArrayObject(cx, arr)) { - JS_ReportError(cx, "second argument must be an array"); - return false; + return ArgumentTypeMismatch(cx, "second ", "StructType", "an array"); } // Define the struct fields. @@ -5095,19 +5695,18 @@ StructType::Define(JSContext* cx, unsigned argc, jsval* vp) } if (args.length() != 1) { - JS_ReportError(cx, "define takes one argument"); - return false; + return ArgumentLengthError(cx, "StructType.prototype.define", "one", ""); } jsval arg = args[0]; if (arg.isPrimitive()) { - JS_ReportError(cx, "argument must be an array"); - return false; + return ArgumentTypeMismatch(cx, "", "StructType.prototype.define", + "an array"); } RootedObject arr(cx, arg.toObjectOrNull()); if (!JS_IsArrayObject(cx, arr)) { - JS_ReportError(cx, "argument must be an array"); - return false; + return ArgumentTypeMismatch(cx, "", "StructType.prototype.define", + "an array"); } return DefineInternal(cx, obj, arr); @@ -5150,7 +5749,7 @@ StructType::ConstructData(JSContext* cx, // are mutually exclusive, so we can pick the right one. // Try option 1) first. - if (ExplicitConvert(cx, args[0], obj, buffer)) + if (ExplicitConvert(cx, args[0], obj, buffer, ConversionType::Construct)) return true; if (fields->count() != 1) @@ -5175,17 +5774,22 @@ StructType::ConstructData(JSContext* cx, const FieldInfo& field = r.front().value(); STATIC_ASSUME(field.mIndex < fields->count()); /* Quantified invariant */ if (!ImplicitConvert(cx, args[field.mIndex], field.mType, - buffer + field.mOffset, - false, nullptr)) + buffer + field.mOffset, ConversionType::Construct, + nullptr, NullPtr(), 0, obj, field.mIndex)) return false; } return true; } - JS_ReportError(cx, "constructor takes 0, 1, or %u arguments", - fields->count()); - return false; + size_t count = fields->count(); + if (count >= 2) { + char fieldLengthStr[32]; + JS_snprintf(fieldLengthStr, 32, "0, 1, or %u", count); + return ArgumentLengthError(cx, "StructType constructor", fieldLengthStr, + "s"); + } + return ArgumentLengthError(cx, "StructType constructor", "at most one", ""); } const FieldInfoHash* @@ -5340,7 +5944,7 @@ StructType::FieldSetter(JSContext* cx, unsigned argc, Value* vp) return false; } - JSObject* typeObj = CData::GetCType(obj); + RootedObject typeObj(cx, CData::GetCType(obj)); if (CType::GetTypeCode(typeObj) != TYPE_struct) { JS_ReportError(cx, "not a StructType"); return false; @@ -5358,7 +5962,8 @@ StructType::FieldSetter(JSContext* cx, unsigned argc, Value* vp) args.rval().setUndefined(); char* data = static_cast(CData::GetData(obj)) + field->mOffset; - return ImplicitConvert(cx, args.get(0), field->mType, data, false, nullptr); + return ImplicitConvert(cx, args.get(0), field->mType, data, ConversionType::Setter, nullptr, + NullPtr(), 0, typeObj, field->mIndex); } bool @@ -5380,8 +5985,13 @@ StructType::AddressOfField(JSContext* cx, unsigned argc, jsval* vp) } if (args.length() != 1) { - JS_ReportError(cx, "addressOfField takes one argument"); - return false; + return ArgumentLengthError(cx, "StructType.prototype.addressOfField", + "one", ""); + } + + if (!args[0].isString()) { + return ArgumentTypeMismatch(cx, "", "StructType.prototype.addressOfField", + "a string"); } JSFlatString* str = JS_FlattenString(cx, args[0].toString()); @@ -5720,8 +6330,7 @@ FunctionType::Create(JSContext* cx, unsigned argc, jsval* vp) // Construct and return a new FunctionType object. CallArgs args = CallArgsFromVp(argc, vp); if (args.length() < 2 || args.length() > 3) { - JS_ReportError(cx, "FunctionType takes two or three arguments"); - return false; + return ArgumentLengthError(cx, "FunctionType", "two or three", "s"); } AutoValueVector argTypes(cx); @@ -5732,8 +6341,7 @@ FunctionType::Create(JSContext* cx, unsigned argc, jsval* vp) if (args[2].isObject()) arrayObj = &args[2].toObject(); if (!arrayObj || !JS_IsArrayObject(cx, arrayObj)) { - JS_ReportError(cx, "third argument must be an array"); - return false; + return ArgumentTypeMismatch(cx, "third ", "FunctionType", "an array"); } uint32_t len; @@ -5839,6 +6447,8 @@ typedef Array AutoValueAutoArray; static bool ConvertArgument(JSContext* cx, + HandleObject funObj, + unsigned argIndex, HandleValue arg, JSObject* type, AutoValue* value, @@ -5850,7 +6460,9 @@ ConvertArgument(JSContext* cx, } bool freePointer = false; - if (!ImplicitConvert(cx, arg, type, value->mData, true, &freePointer)) + if (!ImplicitConvert(cx, arg, type, value->mData, + ConversionType::Argument, &freePointer, + funObj, argIndex)) return false; if (freePointer) { @@ -5919,7 +6531,8 @@ FunctionType::Call(JSContext* cx, } for (unsigned i = 0; i < argcFixed; ++i) - if (!ConvertArgument(cx, args[i], fninfo->mArgTypes[i], &values[i], &strings)) + if (!ConvertArgument(cx, obj, i, args[i], fninfo->mArgTypes[i], + &values[i], &strings)) return false; if (fninfo->mIsVariadic) { @@ -5944,7 +6557,7 @@ FunctionType::Call(JSContext* cx, !(type = PrepareType(cx, OBJECT_TO_JSVAL(type))) || // Relying on ImplicitConvert only for the limited purpose of // converting one CType to another (e.g., T[] to T*). - !ConvertArgument(cx, args[i], type, &values[i], &strings) || + !ConvertArgument(cx, obj, i, args[i], type, &values[i], &strings) || !(fninfo->mFFITypes[i] = CType::GetFFIType(cx, type))) { // These functions report their own errors. return false; @@ -6170,7 +6783,8 @@ CClosure::Create(JSContext* cx, return nullptr; // Do the value conversion. This might fail, in which case we throw. - if (!ImplicitConvert(cx, errVal, fninfo->mReturnType, errResult.get(), false, nullptr)) + if (!ImplicitConvert(cx, errVal, fninfo->mReturnType, errResult.get(), + ConversionType::Return, nullptr, typeObj)) return nullptr; } @@ -6317,14 +6931,14 @@ CClosure::ClosureStub(ffi_cif* cif, void* result, void** args, void* userData) RootedValue rval(cx); bool success = JS_CallFunctionValue(cx, thisObj, jsfnVal, argv, &rval); - // Convert the result. Note that we pass 'isArgument = false', such that + // Convert the result. Note that we pass 'ConversionType::Return', such that // ImplicitConvert will *not* autoconvert a JS string into a pointer-to-char // type, which would require an allocation that we can't track. The JS // function must perform this conversion itself and return a PointerType // CData; thusly, the burden of freeing the data is left to the user. if (success && cif->rtype != &ffi_type_void) - success = ImplicitConvert(cx, rval, fninfo->mReturnType, result, false, - nullptr); + success = ImplicitConvert(cx, rval, fninfo->mReturnType, result, + ConversionType::Return, nullptr, typeObj); if (!success) { // Something failed. The callee may have thrown, or it may not have @@ -6551,7 +7165,8 @@ CData::ValueSetter(JSContext* cx, const JS::CallArgs& args) { RootedObject obj(cx, &args.thisv().toObject()); args.rval().setUndefined(); - return ImplicitConvert(cx, args.get(0), GetCType(obj), GetData(obj), false, nullptr); + return ImplicitConvert(cx, args.get(0), GetCType(obj), GetData(obj), + ConversionType::Setter, nullptr); } bool @@ -6559,8 +7174,7 @@ CData::Address(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { - JS_ReportError(cx, "address takes zero arguments"); - return false; + return ArgumentLengthError(cx, "CData.prototype.address", "no", "s"); } RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); @@ -6594,20 +7208,17 @@ CData::Cast(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 2) { - JS_ReportError(cx, "cast takes two arguments"); - return false; + return ArgumentLengthError(cx, "ctypes.cast", "two", "s"); } if (args[0].isPrimitive() || !CData::IsCData(&args[0].toObject())) { - JS_ReportError(cx, "first argument must be a CData"); - return false; + return ArgumentTypeMismatch(cx, "first ", "ctypes.cast", "a CData"); } RootedObject sourceData(cx, &args[0].toObject()); JSObject* sourceType = CData::GetCType(sourceData); if (args[1].isPrimitive() || !CType::IsCType(&args[1].toObject())) { - JS_ReportError(cx, "second argument must be a CType"); - return false; + return ArgumentTypeMismatch(cx, "second ", "ctypes.cast", "a CType"); } RootedObject targetType(cx, &args[1].toObject()); @@ -6635,13 +7246,11 @@ CData::GetRuntime(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { - JS_ReportError(cx, "getRuntime takes one argument"); - return false; + return ArgumentLengthError(cx, "ctypes.getRuntime", "one", ""); } if (args[0].isPrimitive() || !CType::IsCType(&args[0].toObject())) { - JS_ReportError(cx, "first argument must be a CType"); - return false; + return ArgumentTypeMismatch(cx, "", "ctypes.getRuntime", "a CType"); } RootedObject targetType(cx, &args[0].toObject()); @@ -6668,8 +7277,11 @@ ReadStringCommon(JSContext* cx, InflateUTF8Method inflateUTF8, unsigned argc, js { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { - JS_ReportError(cx, "readString takes zero arguments"); - return false; + if (inflateUTF8 == JS::UTF8CharsToNewTwoByteCharsZ) { + return ArgumentLengthError(cx, "CData.prototype.readString", "no", "s"); + } + return ArgumentLengthError(cx, "CData.prototype.readStringReplaceMalformed", + "no", "s"); } JSObject* obj = CDataFinalizer::GetCData(cx, JS_THIS_OBJECT(cx, vp)); @@ -6784,8 +7396,7 @@ CData::ToSource(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { - JS_ReportError(cx, "toSource takes zero arguments"); - return false; + return ArgumentLengthError(cx, "CData.prototype.toSource", "no", "s"); } JSObject* obj = JS_THIS_OBJECT(cx, vp); @@ -7013,8 +7624,7 @@ CDataFinalizer::Construct(JSContext* cx, unsigned argc, jsval* vp) } if (args.length() != 2) { - JS_ReportError(cx, "CDataFinalizer takes 2 arguments"); - return false; + return ArgumentLengthError(cx, "CDataFinalizer constructor", "two", "s"); } JS::HandleValue valCodePtr = args[1]; @@ -7039,7 +7649,7 @@ CDataFinalizer::Construct(JSContext* cx, unsigned argc, jsval* vp) TypeCode typCodePtr = CType::GetTypeCode(objCodePtrType); if (typCodePtr != TYPE_pointer) { return TypeError(cx, "a CData object of a function _pointer_ type", - valCodePtrType); + valCodePtr); } JSObject* objCodeType = PointerType::GetBaseType(objCodePtrType); @@ -7048,12 +7658,12 @@ CDataFinalizer::Construct(JSContext* cx, unsigned argc, jsval* vp) TypeCode typCode = CType::GetTypeCode(objCodeType); if (typCode != TYPE_function) { return TypeError(cx, "a CData object of a _function_ pointer type", - valCodePtrType); + valCodePtr); } uintptr_t code = *reinterpret_cast(CData::GetData(objCodePtr)); if (!code) { return TypeError(cx, "a CData object of a _non-NULL_ function pointer type", - valCodePtrType); + valCodePtr); } FunctionInfo* funInfoFinalizer = @@ -7079,16 +7689,17 @@ CDataFinalizer::Construct(JSContext* cx, unsigned argc, jsval* vp) size_t sizeArg; RootedValue valData(cx, args[0]); if (!CType::GetSafeSize(objArgType, &sizeArg)) { - return TypeError(cx, "(an object with known size)", valData); + RootedValue valCodeType(cx, ObjectValue(*objCodeType)); + return TypeError(cx, "a function with one known size argument", + valCodeType); } ScopedJSFreePtr cargs(malloc(sizeArg)); if (!ImplicitConvert(cx, valData, objArgType, cargs.get(), - false, &freePointer)) { - RootedValue valArgType(cx, ObjectValue(*objArgType)); - return TypeError(cx, "(an object that can be converted to the following type)", - valArgType); + ConversionType::Finalizer, &freePointer, + objCodePtrType, 0)) { + return false; } if (freePointer) { // Note: We could handle that case, if necessary. @@ -7124,7 +7735,7 @@ CDataFinalizer::Construct(JSContext* cx, unsigned argc, jsval* vp) MOZ_CRASH("object with unknown size"); } if (sizeBestArg != sizeArg) { - return TypeError(cx, "(an object with the same size as that expected by the C finalization function)", valData); + return FinalizerSizeError(cx, objCodePtrType, valData); } } } @@ -7227,16 +7838,16 @@ CDataFinalizer::Methods::Forget(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { - JS_ReportError(cx, "CDataFinalizer.prototype.forget takes no arguments"); - return false; + return ArgumentLengthError(cx, "CDataFinalizer.prototype.forget", "no", + "s"); } JS::Rooted obj(cx, args.thisv().toObjectOrNull()); if (!obj) return false; if (!CDataFinalizer::IsCDataFinalizer(obj)) { - RootedValue val(cx, ObjectValue(*obj)); - return TypeError(cx, "a CDataFinalizer", val); + JS_ReportError(cx, "not a CDataFinalizer"); + return false; } CDataFinalizer::Private* p = (CDataFinalizer::Private*) @@ -7275,16 +7886,16 @@ CDataFinalizer::Methods::Dispose(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 0) { - JS_ReportError(cx, "CDataFinalizer.prototype.dispose takes no arguments"); - return false; + return ArgumentLengthError(cx, "CDataFinalizer.prototype.dispose", "no", + "s"); } RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); if (!obj) return false; if (!CDataFinalizer::IsCDataFinalizer(obj)) { - RootedValue val(cx, ObjectValue(*obj)); - return TypeError(cx, "a CDataFinalizer", val); + JS_ReportError(cx, "not a CDataFinalizer"); + return false; } CDataFinalizer::Private* p = (CDataFinalizer::Private*) @@ -7449,8 +8060,12 @@ Int64Base::ToString(JSContext* cx, bool isUnsigned) { if (args.length() > 1) { - JS_ReportError(cx, "toString takes zero or one argument"); - return false; + if (isUnsigned) { + return ArgumentLengthError(cx, "UInt64.prototype.toString", + "at most one", ""); + } + return ArgumentLengthError(cx, "Int64.prototype.toString", + "at most one", ""); } int radix = 10; @@ -7459,8 +8074,10 @@ Int64Base::ToString(JSContext* cx, if (arg.isInt32()) radix = arg.toInt32(); if (!arg.isInt32() || radix < 2 || radix > 36) { - JS_ReportError(cx, "radix argument must be an integer between 2 and 36"); - return false; + if (isUnsigned) { + return ArgumentRangeMismatch(cx, "", "UInt64.prototype.toString", "an integer at least 2 and no greater than 36"); + } + return ArgumentRangeMismatch(cx, "", "Int64.prototype.toString", "an integer at least 2 and no greater than 36"); } } @@ -7486,8 +8103,10 @@ Int64Base::ToSource(JSContext* cx, bool isUnsigned) { if (args.length() != 0) { - JS_ReportError(cx, "toSource takes zero arguments"); - return false; + if (isUnsigned) { + return ArgumentLengthError(cx, "UInt64.prototype.toSource", "no", "s"); + } + return ArgumentLengthError(cx, "Int64.prototype.toSource", "no", "s"); } // Return a decimal string suitable for constructing the number. @@ -7518,13 +8137,13 @@ Int64::Construct(JSContext* cx, // Construct and return a new Int64 object. if (args.length() != 1) { - JS_ReportError(cx, "Int64 takes one argument"); - return false; + return ArgumentLengthError(cx, "Int64 constructor", "one", ""); } int64_t i = 0; - if (!jsvalToBigInteger(cx, args[0], true, &i)) - return TypeError(cx, "int64", args[0]); + if (!jsvalToBigInteger(cx, args[0], true, &i)) { + return ArgumentConvError(cx, args[0], "Int64", 0); + } // Get ctypes.Int64.prototype from the 'prototype' property of the ctor. RootedValue slot(cx); @@ -7581,13 +8200,14 @@ bool Int64::Compare(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() != 2 || - args[0].isPrimitive() || - args[1].isPrimitive() || - !Int64::IsInt64(&args[0].toObject()) || - !Int64::IsInt64(&args[1].toObject())) { - JS_ReportError(cx, "compare takes two Int64 arguments"); - return false; + if (args.length() != 2) { + return ArgumentLengthError(cx, "Int64.compare", "two", "s"); + } + if (args[0].isPrimitive() || !Int64::IsInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "first ", "Int64.compare", "a Int64"); + } + if (args[1].isPrimitive() ||!Int64::IsInt64(&args[1].toObject())) { + return ArgumentTypeMismatch(cx, "second ", "Int64.compare", "a Int64"); } JSObject* obj1 = &args[0].toObject(); @@ -7614,10 +8234,11 @@ bool Int64::Lo(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() != 1 || args[0].isPrimitive() || - !Int64::IsInt64(&args[0].toObject())) { - JS_ReportError(cx, "lo takes one Int64 argument"); - return false; + if (args.length() != 1) { + return ArgumentLengthError(cx, "Int64.lo", "one", ""); + } + if (args[0].isPrimitive() || !Int64::IsInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "", "Int64.lo", "a Int64"); } JSObject* obj = &args[0].toObject(); @@ -7632,10 +8253,11 @@ bool Int64::Hi(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() != 1 || args[0].isPrimitive() || - !Int64::IsInt64(&args[0].toObject())) { - JS_ReportError(cx, "hi takes one Int64 argument"); - return false; + if (args.length() != 1) { + return ArgumentLengthError(cx, "Int64.hi", "one", ""); + } + if (args[0].isPrimitive() || !Int64::IsInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "", "Int64.hi", "a Int64"); } JSObject* obj = &args[0].toObject(); @@ -7651,16 +8273,15 @@ Int64::Join(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 2) { - JS_ReportError(cx, "join takes two arguments"); - return false; + return ArgumentLengthError(cx, "Int64.join", "two", "s"); } int32_t hi; uint32_t lo; if (!jsvalToInteger(cx, args[0], &hi)) - return TypeError(cx, "int32", args[0]); + return ArgumentConvError(cx, args[0], "Int64.join", 0); if (!jsvalToInteger(cx, args[1], &lo)) - return TypeError(cx, "uint32", args[1]); + return ArgumentConvError(cx, args[1], "Int64.join", 1); int64_t i = (int64_t(hi) << 32) + int64_t(lo); @@ -7688,13 +8309,13 @@ UInt64::Construct(JSContext* cx, // Construct and return a new UInt64 object. if (args.length() != 1) { - JS_ReportError(cx, "UInt64 takes one argument"); - return false; + return ArgumentLengthError(cx, "UInt64 constructor", "one", ""); } uint64_t u = 0; - if (!jsvalToBigInteger(cx, args[0], true, &u)) - return TypeError(cx, "uint64", args[0]); + if (!jsvalToBigInteger(cx, args[0], true, &u)) { + return ArgumentConvError(cx, args[0], "UInt64", 0); + } // Get ctypes.UInt64.prototype from the 'prototype' property of the ctor. RootedValue slot(cx); @@ -7751,13 +8372,14 @@ bool UInt64::Compare(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() != 2 || - args[0].isPrimitive() || - args[1].isPrimitive() || - !UInt64::IsUInt64(&args[0].toObject()) || - !UInt64::IsUInt64(&args[1].toObject())) { - JS_ReportError(cx, "compare takes two UInt64 arguments"); - return false; + if (args.length() != 2) { + return ArgumentLengthError(cx, "UInt64.compare", "two", "s"); + } + if (args[0].isPrimitive() || !UInt64::IsUInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "first ", "UInt64.compare", "a UInt64"); + } + if (args[1].isPrimitive() || !UInt64::IsUInt64(&args[1].toObject())) { + return ArgumentTypeMismatch(cx, "second ", "UInt64.compare", "a UInt64"); } JSObject* obj1 = &args[0].toObject(); @@ -7780,10 +8402,11 @@ bool UInt64::Lo(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() != 1 || args[0].isPrimitive() || - !UInt64::IsUInt64(&args[0].toObject())) { - JS_ReportError(cx, "lo takes one UInt64 argument"); - return false; + if (args.length() != 1) { + return ArgumentLengthError(cx, "UInt64.lo", "one", ""); + } + if (args[0].isPrimitive() || !UInt64::IsUInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "", "UInt64.lo", "a UInt64"); } JSObject* obj = &args[0].toObject(); @@ -7798,10 +8421,11 @@ bool UInt64::Hi(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() != 1 || args[0].isPrimitive() || - !UInt64::IsUInt64(&args[0].toObject())) { - JS_ReportError(cx, "hi takes one UInt64 argument"); - return false; + if (args.length() != 1) { + return ArgumentLengthError(cx, "UInt64.hi", "one", ""); + } + if (args[0].isPrimitive() || !UInt64::IsUInt64(&args[0].toObject())) { + return ArgumentTypeMismatch(cx, "", "UInt64.hi", "a UInt64"); } JSObject* obj = &args[0].toObject(); @@ -7817,16 +8441,15 @@ UInt64::Join(JSContext* cx, unsigned argc, jsval* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 2) { - JS_ReportError(cx, "join takes two arguments"); - return false; + return ArgumentLengthError(cx, "UInt64.join", "two", "s"); } uint32_t hi; uint32_t lo; if (!jsvalToInteger(cx, args[0], &hi)) - return TypeError(cx, "uint32_t", args[0]); + return ArgumentConvError(cx, args[0], "UInt64.join", 0); if (!jsvalToInteger(cx, args[1], &lo)) - return TypeError(cx, "uint32_t", args[1]); + return ArgumentConvError(cx, args[1], "UInt64.join", 1); uint64_t u = (uint64_t(hi) << 32) + uint64_t(lo); diff --git a/js/src/ctypes/CTypes.h b/js/src/ctypes/CTypes.h index f3ac140d3a..76de720cc5 100644 --- a/js/src/ctypes/CTypes.h +++ b/js/src/ctypes/CTypes.h @@ -10,6 +10,7 @@ #include "ffi.h" #include "jsalloc.h" +#include "jsprf.h" #include "prlink.h" #include "ctypes/typedefs.h" @@ -53,6 +54,32 @@ AppendString(Vector& v, const char (&array)[ArrayLength]) v[i + vlen] = array[i]; } +template +void +AppendChars(Vector& v, const char c, size_t count) +{ + size_t vlen = v.length(); + if (!v.resize(vlen + count)) + return; + + for (size_t i = 0; i < count; ++i) + v[i + vlen] = c; +} + +template +void +AppendUInt(Vector& v, unsigned n) +{ + char array[16]; + size_t alen = JS_snprintf(array, 16, "%u", n); + size_t vlen = v.length(); + if (!v.resize(vlen + alen)) + return; + + for (size_t i = 0; i < alen; ++i) + v[i + vlen] = array[i]; +} + template void AppendString(Vector& v, Vector& w) @@ -375,6 +402,7 @@ enum CDataSlot { SLOT_REFERENT = 1, // JSObject this object must keep alive, if any SLOT_DATA = 2, // pointer to a buffer containing the binary data SLOT_OWNS = 3, // JSVAL_TRUE if this CData owns its own buffer + SLOT_FUNNAME = 4, // JSString representing the function name CDATA_SLOTS }; diff --git a/js/src/ctypes/Library.cpp b/js/src/ctypes/Library.cpp index 2523f8583d..dbbd146cf4 100644 --- a/js/src/ctypes/Library.cpp +++ b/js/src/ctypes/Library.cpp @@ -321,7 +321,7 @@ Library::Declare(JSContext* cx, unsigned argc, jsval* vp) void* data; PRFuncPtr fnptr; - JSString* nameStr = args[0].toString(); + RootedString nameStr(cx, args[0].toString()); AutoCString symbol; if (isFunction) { // Build the symbol, with mangling if necessary. @@ -352,6 +352,9 @@ Library::Declare(JSContext* cx, unsigned argc, jsval* vp) if (!result) return false; + if (isFunction) + JS_SetReservedSlot(result, SLOT_FUNNAME, StringValue(nameStr)); + args.rval().setObject(*result); // Seal the CData object, to prevent modification of the function pointer. diff --git a/js/src/ctypes/ctypes.msg b/js/src/ctypes/ctypes.msg index 7178fcb26b..49bf3f0daa 100644 --- a/js/src/ctypes/ctypes.msg +++ b/js/src/ctypes/ctypes.msg @@ -10,5 +10,30 @@ */ MSG_DEF(CTYPESMSG_PLACEHOLDER_0, 0, JSEXN_NONE, NULL) -MSG_DEF(CTYPESMSG_TYPE_ERROR, 2, JSEXN_TYPEERR, "expected type {0}, got {1}") +/* type conversion */ +MSG_DEF(CTYPESMSG_CONV_ERROR_ARG,3, JSEXN_TYPEERR, "can't pass {0} to argument {1} of {2}") +MSG_DEF(CTYPESMSG_CONV_ERROR_ARRAY,3, JSEXN_TYPEERR, "can't convert {0} to element {1} of the type {2}") +MSG_DEF(CTYPESMSG_CONV_ERROR_FIN,2, JSEXN_TYPEERR, "can't convert {0} to the type of argument 1 of {1}") +MSG_DEF(CTYPESMSG_CONV_ERROR_RET,2, JSEXN_TYPEERR, "can't convert {0} to the return type of {1}") +MSG_DEF(CTYPESMSG_CONV_ERROR_SET,2, JSEXN_TYPEERR, "can't convert {0} to the type {1}") +MSG_DEF(CTYPESMSG_CONV_ERROR_STRUCT,5, JSEXN_TYPEERR, "can't convert {0} to the '{1}' field ({2}) of {3}{4}") +MSG_DEF(CTYPESMSG_NON_PRIMITIVE, 1, JSEXN_TYPEERR, ".value only works on character and numeric types, not `{0}`") +MSG_DEF(CTYPESMSG_TYPE_ERROR, 2, JSEXN_TYPEERR, "expected {0}, got {1}") + +/* array */ +MSG_DEF(CTYPESMSG_ARRAY_MISMATCH,4, JSEXN_TYPEERR, "length of {0} does not match to the length of the type {1} (expected {2}, got {3})") +MSG_DEF(CTYPESMSG_ARRAY_OVERFLOW,4, JSEXN_TYPEERR, "length of {0} does not fit to the length of the type {1} (expected {2} or lower, got {3})") + +/* struct */ +MSG_DEF(CTYPESMSG_FIELD_MISMATCH,5, JSEXN_TYPEERR, "property count of {0} does not match to field count of the type {1} (expected {2}, got {3}){4}") +MSG_DEF(CTYPESMSG_PROP_NONSTRING,3, JSEXN_TYPEERR, "property name {0} of {1} is not a string{2}") + +/* data finalizer */ +MSG_DEF(CTYPESMSG_EMPTY_FIN, 1, JSEXN_TYPEERR, "attempting to convert an empty CDataFinalizer{0}") +MSG_DEF(CTYPESMSG_FIN_SIZE_ERROR,2, JSEXN_TYPEERR, "expected an object with the same size as argument 1 of {0}, got {1}") + +/* native function */ +MSG_DEF(CTYPESMSG_ARG_RANGE_MISMATCH,3, JSEXN_RANGEERR, "{0}argument of {1} must be {2}") +MSG_DEF(CTYPESMSG_ARG_TYPE_MISMATCH,3, JSEXN_TYPEERR, "{0}argument of {1} must be {2}") +MSG_DEF(CTYPESMSG_WRONG_ARG_LENGTH,3, JSEXN_TYPEERR, "{0} takes {1} argument{2}") diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index c4fbdc3c4d..eef9fd238d 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -21,6 +21,7 @@ #include "jsscriptinlines.h" #include "frontend/Parser-inl.h" +#include "vm/ScopeObject-inl.h" using namespace js; using namespace js::frontend; @@ -328,7 +329,7 @@ frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject sco TokenStream::Position pos(parser.keepAtoms); parser.tokenStream.tell(&pos); - ParseNode* pn = parser.statement(canHaveDirectives); + ParseNode* pn = parser.statement(YieldIsName, canHaveDirectives); if (!pn) { if (parser.hadAbortedSyntaxParse()) { // Parsing inner functions lazily may lead the parser into an @@ -352,7 +353,7 @@ frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject sco return nullptr; MOZ_ASSERT(parser.pc == pc.ptr()); - pn = parser.statement(); + pn = parser.statement(YieldIsName); } if (!pn) { MOZ_ASSERT(!parser.hadAbortedSyntaxParse()); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 285d794baf..4047a4100c 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -625,6 +625,15 @@ class FullParseHandler return false; } + bool isReturnStatement(ParseNode* node) { + return node->isKind(PNK_RETURN); + } + + bool isStatementPermittedAfterReturnStatement(ParseNode *node) { + ParseNodeKind kind = node->getKind(); + return kind == PNK_FUNCTION || kind == PNK_VAR || kind == PNK_BREAK || kind == PNK_THROW; + } + inline bool finishInitializerAssignment(ParseNode* pn, ParseNode* init, JSOp op); void setBeginPosition(ParseNode* pn, ParseNode* oth) { @@ -651,11 +660,23 @@ class FullParseHandler } ParseNode* newList(ParseNodeKind kind, JSOp op = JSOP_NOP) { + MOZ_ASSERT(kind != PNK_VAR); + return new_(kind, op, pos()); + } + ParseNode* newDeclarationList(ParseNodeKind kind, JSOp op = JSOP_NOP) { + MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || + kind == PNK_GLOBALCONST); return new_(kind, op, pos()); } /* New list with one initial child node. kid must be non-null. */ ParseNode* newList(ParseNodeKind kind, ParseNode* kid, JSOp op = JSOP_NOP) { + MOZ_ASSERT(kind != PNK_VAR); + return new_(kind, op, kid); + } + ParseNode* newDeclarationList(ParseNodeKind kind, ParseNode* kid, JSOp op = JSOP_NOP) { + MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || + kind == PNK_GLOBALCONST); return new_(kind, op, kid); } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 7414bd38fc..9975d793e4 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -564,8 +564,8 @@ Parser::~Parser() } template -ObjectBox * -Parser::newObjectBox(JSObject *obj) +ObjectBox* +Parser::newObjectBox(JSObject* obj) { MOZ_ASSERT(obj); @@ -710,7 +710,7 @@ Parser::parse(JSObject* chain) if (!globalpc.init(tokenStream)) return null(); - Node pn = statements(); + Node pn = statements(YieldIsName); if (pn) { TokenKind tt; if (!tokenStream.getToken(&tt)) @@ -830,7 +830,8 @@ Parser::standaloneFunctionBody(HandleFunction fun, const AutoN return null(); } - ParseNode* pn = functionBody(Statement, StatementListBody); + YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; + ParseNode* pn = functionBody(InAllowed, yieldHandling, Statement, StatementListBody); if (!pn) return null(); @@ -971,7 +972,8 @@ Parser::checkFunctionArguments() template typename ParseHandler::Node -Parser::functionBody(FunctionSyntaxKind kind, FunctionBodyType type) +Parser::functionBody(InHandling inHandling, YieldHandling yieldHandling, + FunctionSyntaxKind kind, FunctionBodyType type) { MOZ_ASSERT(pc->sc->isFunctionBox()); MOZ_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid); @@ -982,13 +984,13 @@ Parser::functionBody(FunctionSyntaxKind kind, FunctionBodyType typ Node pn; if (type == StatementListBody) { - pn = statements(); + pn = statements(yieldHandling); if (!pn) return null(); } else { MOZ_ASSERT(type == ExpressionBody); - Node kid = assignExpr(); + Node kid = assignExpr(inHandling, yieldHandling); if (!kid) return null(); @@ -1003,7 +1005,7 @@ Parser::functionBody(FunctionSyntaxKind kind, FunctionBodyType typ break; case LegacyGenerator: - // FIXME: Catch these errors eagerly, in yieldExpression(). + // FIXME: Catch these errors eagerly, in Parser::yieldExpression. MOZ_ASSERT(pc->lastYieldOffset != startYieldOffset); if (kind == Arrow) { reportWithOffset(ParseError, false, pc->lastYieldOffset, @@ -1202,10 +1204,10 @@ Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind, Hand flags = JSFunction::INTERPRETED; break; } - - gc::AllocKind allocKind = JSFunction::FinalizeKind; + + gc::AllocKind allocKind = gc::AllocKind::FUNCTION; if (kind == Arrow || kind == Method) - allocKind = JSFunction::ExtendedFinalizeKind; + allocKind = gc::AllocKind::FUNCTION_EXTENDED; fun = NewFunctionWithProto(context, nullptr, 0, flags, NullPtr(), atom, proto, allocKind, TenuredObject); if (!fun) @@ -1527,8 +1529,8 @@ Parser::bindDestructuringArg(BindData* data, template bool -Parser::functionArguments(FunctionSyntaxKind kind, FunctionType type, Node* listp, - Node funcpn, bool* hasRest) +Parser::functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind, + FunctionType type, Node* listp, Node funcpn, bool* hasRest) { FunctionBox* funbox = pc->sc->asFunctionBox(); @@ -1621,7 +1623,8 @@ Parser::functionArguments(FunctionSyntaxKind kind, FunctionType ty data.pn = ParseHandler::null(); data.op = JSOP_DEFVAR; data.binder = bindDestructuringArg; - Node lhs = destructuringExprWithoutYield(&data, tt, JSMSG_YIELD_IN_DEFAULT); + Node lhs = destructuringExprWithoutYield(yieldHandling, &data, tt, + JSMSG_YIELD_IN_DEFAULT); if (!lhs) return false; @@ -1644,7 +1647,7 @@ Parser::functionArguments(FunctionSyntaxKind kind, FunctionType ty if (list) { handler.addList(list, item); } else { - list = handler.newList(PNK_VAR, item); + list = handler.newDeclarationList(PNK_VAR, item); if (!list) return false; *listp = list; @@ -1655,6 +1658,7 @@ Parser::functionArguments(FunctionSyntaxKind kind, FunctionType ty case TOK_YIELD: if (!checkYieldNameValidity()) return false; + MOZ_ASSERT(yieldHandling == YieldIsName); goto TOK_NAME; case TOK_TRIPLEDOT: @@ -1667,6 +1671,12 @@ Parser::functionArguments(FunctionSyntaxKind kind, FunctionType ty *hasRest = true; if (!tokenStream.getToken(&tt)) return false; + // FIXME: This fails to handle a rest parameter named |yield| + // correctly outside of generators: that is, + // |var f = (...yield) => 42;| should be valid code! + // When this is fixed, make sure to consult both + // |yieldHandling| and |checkYieldNameValidity| for + // correctness until legacy generator syntax is removed. if (tt != TOK_NAME) { report(ParseError, false, null(), JSMSG_NO_REST_NAME); return false; @@ -1716,7 +1726,7 @@ Parser::functionArguments(FunctionSyntaxKind kind, FunctionType ty // before the first default argument. funbox->length = pc->numArgs() - 1; } - Node def_expr = assignExprWithoutYield(JSMSG_YIELD_IN_DEFAULT); + Node def_expr = assignExprWithoutYield(yieldHandling, JSMSG_YIELD_IN_DEFAULT); if (!def_expr) return false; handler.setLastFunctionArgumentDefault(funcpn, def_expr); @@ -2048,9 +2058,10 @@ Parser::checkFunctionDefinition(HandlePropertyName funName, template bool -Parser::addExprAndGetNextTemplStrToken(Node nodeList, TokenKind* ttp) +Parser::addExprAndGetNextTemplStrToken(YieldHandling yieldHandling, Node nodeList, + TokenKind* ttp) { - Node pn = expr(); + Node pn = expr(InAllowed, yieldHandling); if (!pn) return false; handler.addList(nodeList, pn); @@ -2068,7 +2079,7 @@ Parser::addExprAndGetNextTemplStrToken(Node nodeList, TokenKind* t template bool -Parser::taggedTemplate(Node nodeList, TokenKind tt) +Parser::taggedTemplate(YieldHandling yieldHandling, Node nodeList, TokenKind tt) { Node callSiteObjNode = handler.newCallSiteObject(pos().begin, pc->blockidGen); if (!callSiteObjNode) @@ -2081,7 +2092,7 @@ Parser::taggedTemplate(Node nodeList, TokenKind tt) if (tt != TOK_TEMPLATE_HEAD) break; - if (!addExprAndGetNextTemplStrToken(nodeList, &tt)) + if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt)) return false; } handler.setEndPosition(nodeList, callSiteObjNode); @@ -2090,7 +2101,7 @@ Parser::taggedTemplate(Node nodeList, TokenKind tt) template typename ParseHandler::Node -Parser::templateLiteral() +Parser::templateLiteral(YieldHandling yieldHandling) { Node pn = noSubstitutionTemplate(); if (!pn) @@ -2099,7 +2110,7 @@ Parser::templateLiteral() TokenKind tt; do { - if (!addExprAndGetNextTemplStrToken(nodeList, &tt)) + if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt)) return null(); pn = noSubstitutionTemplate(); @@ -2113,9 +2124,10 @@ Parser::templateLiteral() template typename ParseHandler::Node -Parser::functionDef(HandlePropertyName funName, - FunctionType type, FunctionSyntaxKind kind, - GeneratorKind generatorKind, InvokedPrediction invoked) +Parser::functionDef(InHandling inHandling, YieldHandling yieldHandling, + HandlePropertyName funName, FunctionType type, + FunctionSyntaxKind kind, GeneratorKind generatorKind, + InvokedPrediction invoked) { MOZ_ASSERT_IF(kind == Statement, funName); @@ -2159,8 +2171,11 @@ Parser::functionDef(HandlePropertyName funName, tokenStream.tell(&start); while (true) { - if (functionArgsAndBody(pn, fun, type, kind, generatorKind, directives, &newDirectives)) + if (functionArgsAndBody(inHandling, pn, fun, type, kind, generatorKind, directives, + &newDirectives)) + { break; + } if (tokenStream.hadError() || directives == newDirectives) return null(); @@ -2263,9 +2278,9 @@ Parser::finishFunctionDefinition(Node pn, FunctionBox* funbo template <> bool -Parser::functionArgsAndBody(ParseNode* pn, HandleFunction fun, - FunctionType type, FunctionSyntaxKind kind, - GeneratorKind generatorKind, +Parser::functionArgsAndBody(InHandling inHandling, ParseNode* pn, + HandleFunction fun, FunctionType type, + FunctionSyntaxKind kind, GeneratorKind generatorKind, Directives inheritedDirectives, Directives* newDirectives) { @@ -2276,6 +2291,8 @@ Parser::functionArgsAndBody(ParseNode* pn, HandleFunction fun, if (!funbox) return false; + YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; + // Try a syntax parse for this inner function. do { // If we're assuming this function is an IIFE, always perform a full @@ -2302,8 +2319,9 @@ Parser::functionArgsAndBody(ParseNode* pn, HandleFunction fun, if (!funpc.init(tokenStream)) return false; - if (!parser->functionArgsAndBodyGeneric(SyntaxParseHandler::NodeGeneric, - fun, type, kind)) + if (!parser->functionArgsAndBodyGeneric(inHandling, yieldHandling, + SyntaxParseHandler::NodeGeneric, fun, type, + kind)) { if (parser->hadAbortedSyntaxParse()) { // Try again with a full parse. @@ -2341,7 +2359,7 @@ Parser::functionArgsAndBody(ParseNode* pn, HandleFunction fun, if (!funpc.init(tokenStream)) return false; - if (!functionArgsAndBodyGeneric(pn, fun, type, kind)) + if (!functionArgsAndBodyGeneric(inHandling, yieldHandling, pn, fun, type, kind)) return false; if (!leaveFunction(pn, outerpc, kind)) @@ -2361,7 +2379,7 @@ Parser::functionArgsAndBody(ParseNode* pn, HandleFunction fun, template <> bool -Parser::functionArgsAndBody(Node pn, HandleFunction fun, +Parser::functionArgsAndBody(InHandling inHandling, Node pn, HandleFunction fun, FunctionType type, FunctionSyntaxKind kind, GeneratorKind generatorKind, Directives inheritedDirectives, @@ -2381,7 +2399,8 @@ Parser::functionArgsAndBody(Node pn, HandleFunction fun, if (!funpc.init(tokenStream)) return false; - if (!functionArgsAndBodyGeneric(pn, fun, type, kind)) + YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; + if (!functionArgsAndBodyGeneric(inHandling, yieldHandling, pn, fun, type, kind)) return false; if (!leaveFunction(pn, outerpc, kind)) @@ -2442,8 +2461,9 @@ Parser::standaloneLazyFunction(HandleFunction fun, unsigned st if (!funpc.init(tokenStream)) return null(); + YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; FunctionSyntaxKind syntaxKind = fun->isMethod() ? Method : Statement; - if (!functionArgsAndBodyGeneric(pn, fun, Normal, syntaxKind)) { + if (!functionArgsAndBodyGeneric(InAllowed, yieldHandling, pn, fun, Normal, syntaxKind)) { MOZ_ASSERT(directives == newDirectives); return null(); } @@ -2469,7 +2489,9 @@ Parser::standaloneLazyFunction(HandleFunction fun, unsigned st template bool -Parser::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, FunctionType type, +Parser::functionArgsAndBodyGeneric(InHandling inHandling, + YieldHandling yieldHandling, Node pn, + HandleFunction fun, FunctionType type, FunctionSyntaxKind kind) { // Given a properly initialized parse context, try to parse an actual @@ -2478,7 +2500,7 @@ Parser::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu Node prelude = null(); bool hasRest; - if (!functionArguments(kind, type, &prelude, pn, &hasRest)) + if (!functionArguments(yieldHandling, kind, type, &prelude, pn, &hasRest)) return false; FunctionBox* funbox = pc->sc->asFunctionBox(); @@ -2523,7 +2545,7 @@ Parser::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu #endif } - Node body = functionBody(kind, bodyType); + Node body = functionBody(inHandling, yieldHandling, kind, bodyType); if (!body) return false; @@ -2568,7 +2590,7 @@ Parser::checkYieldNameValidity() template typename ParseHandler::Node -Parser::functionStmt() +Parser::functionStmt(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); @@ -2601,7 +2623,7 @@ Parser::functionStmt() !report(ParseStrictError, pc->sc->strict(), null(), JSMSG_STRICT_FUNCTION_STATEMENT)) return null(); - return functionDef(name, Normal, Statement, generatorKind); + return functionDef(InAllowed, yieldHandling, name, Normal, Statement, generatorKind); } template @@ -2632,7 +2654,8 @@ Parser::functionExpr(InvokedPrediction invoked) tokenStream.ungetToken(); } - return functionDef(name, Normal, Expression, generatorKind, invoked); + YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName; + return functionDef(InAllowed, yieldHandling, name, Normal, Expression, generatorKind, invoked); } /* @@ -2783,7 +2806,7 @@ Parser::maybeParseDirective(Node list, Node pn, bool* cont) */ template typename ParseHandler::Node -Parser::statements() +Parser::statements(YieldHandling yieldHandling) { JS_CHECK_RECURSION(context, return null()); @@ -2795,6 +2818,9 @@ Parser::statements() pc->blockNode = pn; bool canHaveDirectives = pc->atBodyLevel(); + bool afterReturn = false; + bool warnedAboutStatementsAfterReturn = false; + uint32_t statementBegin; for (;;) { TokenKind tt; if (!tokenStream.peekToken(&tt, TokenStream::Operand)) { @@ -2804,12 +2830,32 @@ Parser::statements() } if (tt == TOK_EOF || tt == TOK_RC) break; - Node next = statement(canHaveDirectives); + if (afterReturn) { + TokenPos pos(0, 0); + if (!tokenStream.peekTokenPos(&pos, TokenStream::Operand)) + return null(); + statementBegin = pos.begin; + } + Node next = statement(yieldHandling, canHaveDirectives); if (!next) { if (tokenStream.isEOF()) isUnexpectedEOF_ = true; return null(); } + if (!warnedAboutStatementsAfterReturn) { + if (afterReturn) { + if (!handler.isStatementPermittedAfterReturnStatement(next)) { + if (!reportWithOffset(ParseWarning, false, statementBegin, + JSMSG_STMT_AFTER_RETURN)) + { + return null(); + } + warnedAboutStatementsAfterReturn = true; + } + } else if (handler.isReturnStatement(next)) { + afterReturn = true; + } + } if (canHaveDirectives) { if (!maybeParseDirective(pn, next, &canHaveDirectives)) @@ -2832,10 +2878,10 @@ Parser::statements() template typename ParseHandler::Node -Parser::condition() +Parser::condition(InHandling inHandling, YieldHandling yieldHandling) { MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); - Node pn = exprInParens(); + Node pn = exprInParens(inHandling, yieldHandling); if (!pn) return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); @@ -2850,15 +2896,21 @@ Parser::condition() template bool -Parser::matchLabel(MutableHandle label) +Parser::matchLabel(YieldHandling yieldHandling, MutableHandle label) { TokenKind tt; if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand)) return false; + if (tt == TOK_NAME) { tokenStream.consumeKnownToken(TOK_NAME); + MOZ_ASSERT_IF(tokenStream.currentName() == context->names().yield, + yieldHandling == YieldIsName); label.set(tokenStream.currentName()); } else if (tt == TOK_YIELD) { + // We might still consider |yield| to be valid here, contrary to ES6. + // Fix bug 1104014, then stop shipping legacy generators in chrome + // code, then remove this check! tokenStream.consumeKnownToken(TOK_YIELD); if (!checkYieldNameValidity()) return false; @@ -3294,7 +3346,7 @@ Parser::noteNameUse(HandlePropertyName name, Node pn) template <> bool -Parser::bindInitialized(BindData *data, ParseNode *pn) +Parser::bindInitialized(BindData* data, ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_NAME)); @@ -3473,12 +3525,13 @@ Parser::checkDestructuring(BindData* dat template typename ParseHandler::Node -Parser::destructuringExpr(BindData* data, TokenKind tt) +Parser::destructuringExpr(YieldHandling yieldHandling, BindData* data, + TokenKind tt) { MOZ_ASSERT(tokenStream.isCurrentTokenType(tt)); pc->inDeclDestructuring = true; - Node pn = primaryExpr(tt); + Node pn = primaryExpr(yieldHandling, tt); pc->inDeclDestructuring = false; if (!pn) return null(); @@ -3489,11 +3542,12 @@ Parser::destructuringExpr(BindData* data, TokenKind template typename ParseHandler::Node -Parser::destructuringExprWithoutYield(BindData* data, TokenKind tt, +Parser::destructuringExprWithoutYield(YieldHandling yieldHandling, + BindData* data, TokenKind tt, unsigned msg) { uint32_t startYieldOffset = pc->lastYieldOffset; - Node res = destructuringExpr(data, tt); + Node res = destructuringExpr(yieldHandling, data, tt); if (res && pc->lastYieldOffset != startYieldOffset) { reportWithOffset(ParseError, false, pc->lastYieldOffset, msg, js_yield_str); @@ -3591,7 +3645,7 @@ PushBlocklikeStatement(TokenStream& ts, StmtInfoPC* stmt, StmtType type, template typename ParseHandler::Node -Parser::blockStatement() +Parser::blockStatement(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); @@ -3599,7 +3653,7 @@ Parser::blockStatement() if (!PushBlocklikeStatement(tokenStream, &stmtInfo, STMT_BLOCK, pc)) return null(); - Node list = statements(); + Node list = statements(yieldHandling); if (!list) return null(); @@ -3645,7 +3699,8 @@ Parser::newBindingNode(PropertyName* name, bool functionScope, Var */ template typename ParseHandler::Node -Parser::variables(ParseNodeKind kind, bool* psimple, +Parser::variables(YieldHandling yieldHandling, + ParseNodeKind kind, bool* psimple, StaticBlockObject* blockObj, VarContext varContext) { /* @@ -3669,7 +3724,7 @@ Parser::variables(ParseNodeKind kind, bool* psimple, else if (kind == PNK_GLOBALCONST) op = JSOP_DEFCONST; - Node pn = handler.newList(kind, op); + Node pn = handler.newDeclarationList(kind, op); if (!pn) return null(); @@ -3702,7 +3757,7 @@ Parser::variables(ParseNodeKind kind, bool* psimple, *psimple = false; pc->inDeclDestructuring = true; - pn2 = primaryExpr(tt); + pn2 = primaryExpr(yieldHandling, tt); pc->inDeclDestructuring = false; if (!pn2) return null(); @@ -3730,7 +3785,7 @@ Parser::variables(ParseNodeKind kind, bool* psimple, MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL); - Node init = assignExpr(); + Node init = assignExpr(InAllowed, yieldHandling); if (!init) return null(); @@ -3783,7 +3838,7 @@ Parser::variables(ParseNodeKind kind, bool* psimple, if (bindBeforeInitializer && !data.binder(&data, name, this)) return null(); - Node init = assignExpr(); + Node init = assignExpr(InAllowed, yieldHandling); if (!init) return null(); @@ -3801,6 +3856,8 @@ Parser::variables(ParseNodeKind kind, bool* psimple, if (!data.binder(&data, name, this)) return null(); } + + handler.setEndPosition(pn, pn2); } while (false); bool matched; @@ -3828,7 +3885,7 @@ Parser::checkAndPrepareLexical(bool isConst, const TokenPos &e * enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then * we also need to set pc->blockNode to be our PNK_LEXICALSCOPE. */ - StmtInfoPC *stmt = pc->topStmt; + StmtInfoPC* stmt = pc->topStmt; if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) { reportWithOffset(ParseError, false, errorPos.begin, JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, isConst ? "const" : "lexical"); @@ -3873,11 +3930,11 @@ Parser::checkAndPrepareLexical(bool isConst, const TokenPos &e MOZ_ASSERT(!stmt->downScope); /* Convert the block statement into a scope statement. */ - StaticBlockObject *blockObj = StaticBlockObject::create(context); + StaticBlockObject* blockObj = StaticBlockObject::create(context); if (!blockObj) return false; - ObjectBox *blockbox = newObjectBox(blockObj); + ObjectBox* blockbox = newObjectBox(blockObj); if (!blockbox) return false; @@ -3896,12 +3953,12 @@ Parser::checkAndPrepareLexical(bool isConst, const TokenPos &e stmt->staticScope = blockObj; #ifdef DEBUG - ParseNode *tmp = pc->blockNode; + ParseNode* tmp = pc->blockNode; MOZ_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE)); #endif /* Create a new lexical scope node for these statements. */ - ParseNode *pn1 = handler.new_(blockbox, pc->blockNode); + ParseNode* pn1 = handler.new_(blockbox, pc->blockNode); if (!pn1) return false;; pc->blockNode = pn1; @@ -3909,15 +3966,15 @@ Parser::checkAndPrepareLexical(bool isConst, const TokenPos &e return true; } -static StaticBlockObject * -CurrentLexicalStaticBlock(ParseContext *pc) +static StaticBlockObject* +CurrentLexicalStaticBlock(ParseContext* pc) { return pc->atBodyLevel() ? nullptr : &pc->staticScope->as(); } template <> -ParseNode * +ParseNode* Parser::makeInitializedLexicalBinding(HandlePropertyName name, bool isConst, const TokenPos &pos) { @@ -3930,7 +3987,7 @@ Parser::makeInitializedLexicalBinding(HandlePropertyName name, return null(); data.initLexical(HoistVars, CurrentLexicalStaticBlock(pc), JSMSG_TOO_MANY_LOCALS, isConst); } - ParseNode *dn = newBindingNode(name, pc->atGlobalLevel()); + ParseNode* dn = newBindingNode(name, pc->atGlobalLevel()); if (!dn) return null(); handler.setPosition(dn, pos); @@ -3942,8 +3999,8 @@ Parser::makeInitializedLexicalBinding(HandlePropertyName name, } template <> -ParseNode * -Parser::lexicalDeclaration(bool isConst) +ParseNode* +Parser::lexicalDeclaration(YieldHandling yieldHandling, bool isConst) { handler.disableSyntaxParser(); @@ -3970,7 +4027,7 @@ Parser::lexicalDeclaration(bool isConst) else if (isConst) kind = PNK_CONST; - ParseNode *pn = variables(kind, nullptr, + ParseNode* pn = variables(yieldHandling, kind, nullptr, CurrentLexicalStaticBlock(pc), HoistVars); if (!pn) @@ -3981,7 +4038,7 @@ Parser::lexicalDeclaration(bool isConst) template <> SyntaxParseHandler::Node -Parser::lexicalDeclaration(bool) +Parser::lexicalDeclaration(YieldHandling, bool) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); return SyntaxParseHandler::NodeFailure; @@ -4225,13 +4282,12 @@ Parser::exportDeclaration() break; case TOK_FUNCTION: - kid = functionStmt(); + kid = functionStmt(YieldIsKeyword); if (!kid) return null(); break; - case TOK_VAR: - kid = variables(PNK_VAR); + case TOK_VAR: kid = variables(YieldIsName, PNK_VAR); if (!kid) return null(); kid->pn_xflags = PNX_POPVAR; @@ -4245,10 +4301,12 @@ Parser::exportDeclaration() // Handle the form |export a| in the same way as |export let a|, by // acting as if we've just seen the let keyword. Simply unget the token // and fall through. + // + // XXX This |export foo = 5| syntax is *not* in ES6! Remove it! tokenStream.ungetToken(); case TOK_LET: case TOK_CONST: - kid = lexicalDeclaration(tt == TOK_CONST); + kid = lexicalDeclaration(YieldIsName, tt == TOK_CONST); if (!kid) return null(); break; @@ -4271,10 +4329,10 @@ Parser::exportDeclaration() template typename ParseHandler::Node -Parser::expressionStatement(InvokedPrediction invoked) +Parser::expressionStatement(YieldHandling yieldHandling, InvokedPrediction invoked) { tokenStream.ungetToken(); - Node pnexpr = expr(invoked); + Node pnexpr = expr(InAllowed, yieldHandling, invoked); if (!pnexpr) return null(); if (!MatchOrInsertSemicolon(tokenStream)) @@ -4284,12 +4342,12 @@ Parser::expressionStatement(InvokedPrediction invoked) template typename ParseHandler::Node -Parser::ifStatement() +Parser::ifStatement(YieldHandling yieldHandling) { uint32_t begin = pos().begin; /* An IF node has three kids: condition, then, and optional else. */ - Node cond = condition(); + Node cond = condition(InAllowed, yieldHandling); if (!cond) return null(); @@ -4303,7 +4361,7 @@ Parser::ifStatement() StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_IF); - Node thenBranch = statement(); + Node thenBranch = statement(yieldHandling); if (!thenBranch) return null(); @@ -4313,7 +4371,7 @@ Parser::ifStatement() return null(); if (matched) { stmtInfo.type = STMT_ELSE; - elseBranch = statement(); + elseBranch = statement(yieldHandling); if (!elseBranch) return null(); } else { @@ -4326,16 +4384,16 @@ Parser::ifStatement() template typename ParseHandler::Node -Parser::doWhileStatement() +Parser::doWhileStatement(YieldHandling yieldHandling) { uint32_t begin = pos().begin; StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_DO_LOOP); - Node body = statement(); + Node body = statement(yieldHandling); if (!body) return null(); MUST_MATCH_TOKEN(TOK_WHILE, JSMSG_WHILE_AFTER_DO); - Node cond = condition(); + Node cond = condition(InAllowed, yieldHandling); if (!cond) return null(); PopStatementPC(tokenStream, pc); @@ -4353,15 +4411,15 @@ Parser::doWhileStatement() template typename ParseHandler::Node -Parser::whileStatement() +Parser::whileStatement(YieldHandling yieldHandling) { uint32_t begin = pos().begin; StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_WHILE_LOOP); - Node cond = condition(); + Node cond = condition(InAllowed, yieldHandling); if (!cond) return null(); - Node body = statement(); + Node body = statement(yieldHandling); if (!body) return null(); PopStatementPC(tokenStream, pc); @@ -4425,7 +4483,7 @@ Parser::checkForHeadConstInitializers(ParseNode* pn1) template <> ParseNode* -Parser::forStatement() +Parser::forStatement(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); uint32_t begin = pos().begin; @@ -4487,7 +4545,7 @@ Parser::forStatement() if (tt == TOK_VAR) { isForDecl = true; tokenStream.consumeKnownToken(tt); - pn1 = variables(PNK_VAR); + pn1 = variables(yieldHandling, PNK_VAR); } else if (tt == TOK_LET || tt == TOK_CONST) { handler.disableSyntaxParser(); bool constDecl = tt == TOK_CONST; @@ -4496,10 +4554,11 @@ Parser::forStatement() blockObj = StaticBlockObject::create(context); if (!blockObj) return null(); - pn1 = variables(constDecl ? PNK_CONST : PNK_LET, nullptr, blockObj, + pn1 = variables(yieldHandling, + constDecl ? PNK_CONST : PNK_LET, nullptr, blockObj, DontHoistVars); } else { - pn1 = expr(); + pn1 = expr(InProhibited, yieldHandling); } pc->parsingForInit = false; if (!pn1) @@ -4592,7 +4651,6 @@ Parser::forStatement() * rhs of 'in'. */ if (headKind == PNK_FOROF) { - forStmt.type = STMT_FOR_OF_LOOP; forStmt.type = (headKind == PNK_FOROF) ? STMT_FOR_OF_LOOP : STMT_FOR_IN_LOOP; if (isForEach) { report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP); @@ -4618,39 +4676,11 @@ Parser::forStatement() if (isForDecl) { pn2 = pn1->pn_head; if ((pn2->isKind(PNK_NAME) && pn2->maybeExpr()) || pn2->isKind(PNK_ASSIGN)) { - /* - * Declaration with initializer. - * - * Rewrite 'for ( x = i in o)' where is 'var' or - * 'const' to hoist the initializer or the entire decl out of - * the loop head. - */ - if (headKind == PNK_FOROF) { - report(ParseError, false, pn2, JSMSG_INVALID_FOR_OF_INIT); - return null(); - } - if (blockObj) { - report(ParseError, false, pn2, JSMSG_INVALID_FOR_IN_INIT); - return null(); - } - - hoistedVar = pn1; - - /* - * All of 'var x = i' is hoisted above 'for (x in o)'. - * - * Request JSOP_POP here since the var is for a simple - * name (it is not a destructuring binding's left-hand - * side) and it has an initializer. - */ - pn1->pn_xflags |= PNX_POPVAR; - pn1 = nullptr; - - if (pn2->isKind(PNK_ASSIGN)) { - pn2 = pn2->pn_left; - MOZ_ASSERT(pn2->isKind(PNK_ARRAY) || pn2->isKind(PNK_OBJECT) || - pn2->isKind(PNK_NAME)); - } + // We have a bizarre |for (var/const/let x = ... in/of ...)| + // loop erroneously permitted by ES1-5 but removed in ES6. + report(ParseError, false, pn2, JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT, + headKind == PNK_FOROF ? "of" : "in"); + return null(); } } else { /* Not a declaration. */ @@ -4662,7 +4692,9 @@ Parser::forStatement() return null(); } - pn3 = (headKind == PNK_FOROF) ? assignExpr() : expr(); + pn3 = (headKind == PNK_FOROF) + ? assignExpr(InAllowed, yieldHandling) + : expr(InAllowed, yieldHandling); if (!pn3) return null(); @@ -4757,7 +4789,7 @@ Parser::forStatement() if (tt == TOK_SEMI) { pn2 = nullptr; } else { - pn2 = expr(); + pn2 = expr(InAllowed, yieldHandling); if (!pn2) return null(); } @@ -4769,7 +4801,7 @@ Parser::forStatement() if (tt == TOK_RP) { pn3 = nullptr; } else { - pn3 = expr(); + pn3 = expr(InAllowed, yieldHandling); if (!pn3) return null(); } @@ -4783,7 +4815,7 @@ Parser::forStatement() return null(); /* Parse the loop body. */ - ParseNode* body = statement(); + ParseNode* body = statement(yieldHandling); if (!body) return null(); @@ -4813,7 +4845,7 @@ Parser::forStatement() template <> SyntaxParseHandler::Node -Parser::forStatement() +Parser::forStatement(YieldHandling yieldHandling) { /* * 'for' statement parsing is fantastically complicated and requires being @@ -4860,14 +4892,14 @@ Parser::forStatement() if (tt == TOK_VAR) { isForDecl = true; tokenStream.consumeKnownToken(tt); - lhsNode = variables(PNK_VAR, &simpleForDecl); + lhsNode = variables(yieldHandling, PNK_VAR, &simpleForDecl); } else if (tt == TOK_CONST || tt == TOK_LET) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); return null(); } else { - lhsNode = expr(); + lhsNode = expr(InProhibited, yieldHandling); } if (!lhsNode) return null(); @@ -4908,7 +4940,7 @@ Parser::forStatement() if (!isForDecl && !checkAndMarkAsAssignmentLhs(lhsNode, PlainAssignment)) return null(); - if (!expr()) + if (!(isForIn ? expr(InAllowed, yieldHandling) : assignExpr(InAllowed, yieldHandling))) return null(); } else { /* Parse the loop condition or null. */ @@ -4917,7 +4949,7 @@ Parser::forStatement() if (!tokenStream.peekToken(&tt, TokenStream::Operand)) return null(); if (tt != TOK_SEMI) { - if (!expr()) + if (!expr(InAllowed, yieldHandling)) return null(); } @@ -4926,7 +4958,7 @@ Parser::forStatement() if (!tokenStream.peekToken(&tt, TokenStream::Operand)) return null(); if (tt != TOK_RP) { - if (!expr()) + if (!expr(InAllowed, yieldHandling)) return null(); } } @@ -4934,7 +4966,7 @@ Parser::forStatement() MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); /* Parse the loop body. */ - if (!statement()) + if (!statement(yieldHandling)) return null(); PopStatementPC(tokenStream, pc); @@ -4943,14 +4975,14 @@ Parser::forStatement() template typename ParseHandler::Node -Parser::switchStatement() +Parser::switchStatement(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_SWITCH)); uint32_t begin = pos().begin; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH); - Node discriminant = exprInParens(); + Node discriminant = exprInParens(InAllowed, yieldHandling); if (!discriminant) return null(); @@ -4991,7 +5023,7 @@ Parser::switchStatement() break; case TOK_CASE: - caseExpr = expr(); + caseExpr = expr(InAllowed, yieldHandling); if (!caseExpr) return null(); break; @@ -5007,14 +5039,37 @@ Parser::switchStatement() if (!body) return null(); + bool afterReturn = false; + bool warnedAboutStatementsAfterReturn = false; + uint32_t statementBegin; while (true) { if (!tokenStream.peekToken(&tt, TokenStream::Operand)) return null(); if (tt == TOK_RC || tt == TOK_CASE || tt == TOK_DEFAULT) break; - Node stmt = statement(); + if (afterReturn) { + TokenPos pos(0, 0); + if (!tokenStream.peekTokenPos(&pos, TokenStream::Operand)) + return null(); + statementBegin = pos.begin; + } + Node stmt = statement(yieldHandling); if (!stmt) return null(); + if (!warnedAboutStatementsAfterReturn) { + if (afterReturn) { + if (!handler.isStatementPermittedAfterReturnStatement(stmt)) { + if (!reportWithOffset(ParseWarning, false, statementBegin, + JSMSG_STMT_AFTER_RETURN)) + { + return null(); + } + warnedAboutStatementsAfterReturn = true; + } + } else if (handler.isReturnStatement(stmt)) { + afterReturn = true; + } + } handler.addList(body, stmt); } @@ -5056,13 +5111,13 @@ Parser::switchStatement() template typename ParseHandler::Node -Parser::continueStatement() +Parser::continueStatement(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CONTINUE)); uint32_t begin = pos().begin; RootedPropertyName label(context); - if (!matchLabel(&label)) + if (!matchLabel(yieldHandling, &label)) return null(); StmtInfoPC* stmt = pc->topStmt; @@ -5103,13 +5158,13 @@ Parser::continueStatement() template typename ParseHandler::Node -Parser::breakStatement() +Parser::breakStatement(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_BREAK)); uint32_t begin = pos().begin; RootedPropertyName label(context); - if (!matchLabel(&label)) + if (!matchLabel(yieldHandling, &label)) return null(); StmtInfoPC* stmt = pc->topStmt; if (label) { @@ -5140,15 +5195,12 @@ Parser::breakStatement() template typename ParseHandler::Node -Parser::returnStatement() +Parser::returnStatement(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_RETURN)); uint32_t begin = pos().begin; - if (!pc->sc->isFunctionBox()) { - report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_return_str); - return null(); - } + MOZ_ASSERT(pc->sc->isFunctionBox()); // Parse an optional operand. // @@ -5158,19 +5210,7 @@ Parser::returnStatement() if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand)) return null(); switch (tt) { - case TOK_EOL: { - bool startsExpr; - if (!tokenStream.nextTokenStartsExpr(&startsExpr, TokenStream::Operand)) - return null(); - if (startsExpr) { - TokenPos pos; - if (!tokenStream.peekTokenPos(&pos, TokenStream::Operand)) - return null(); - if (!reportWithOffset(ParseWarning, false, pos.begin, JSMSG_STMT_AFTER_SEMI_LESS)) - return null(); - } - // Fall through. - } + case TOK_EOL: case TOK_EOF: case TOK_SEMI: case TOK_RC: @@ -5178,7 +5218,7 @@ Parser::returnStatement() pc->funHasReturnVoid = true; break; default: { - exprNode = expr(); + exprNode = expr(InAllowed, yieldHandling); if (!exprNode) return null(); pc->funHasReturnExpr = true; @@ -5230,7 +5270,7 @@ Parser::newYieldExpression(uint32_t begin, typename ParseHandler:: template typename ParseHandler::Node -Parser::yieldExpression() +Parser::yieldExpression(InHandling inHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_YIELD)); uint32_t begin = pos().begin; @@ -5270,7 +5310,7 @@ Parser::yieldExpression() tokenStream.consumeKnownToken(TOK_MUL); // Fall through. default: - exprNode = assignExpr(); + exprNode = assignExpr(inHandling, YieldIsKeyword); if (!exprNode) return null(); } @@ -5327,7 +5367,7 @@ Parser::yieldExpression() exprNode = null(); break; default: - exprNode = assignExpr(); + exprNode = assignExpr(inHandling, YieldIsKeyword); if (!exprNode) return null(); } @@ -5341,7 +5381,7 @@ Parser::yieldExpression() template <> ParseNode* -Parser::withStatement() +Parser::withStatement(YieldHandling yieldHandling) { // test262/ch12/12.10/12.10-0-1.js fails if we try to parse with-statements // in syntax-parse mode. See bug 892583. @@ -5364,7 +5404,7 @@ Parser::withStatement() return null(); MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); - Node objectExpr = exprInParens(); + Node objectExpr = exprInParens(InAllowed, yieldHandling); if (!objectExpr) return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH); @@ -5380,7 +5420,7 @@ Parser::withStatement() staticWith->initEnclosingNestedScopeFromParser(pc->staticScope); FinishPushNestedScope(pc, &stmtInfo, *staticWith); - Node innerBlock = statement(); + Node innerBlock = statement(yieldHandling); if (!innerBlock) return null(); @@ -5408,7 +5448,7 @@ Parser::withStatement() template <> SyntaxParseHandler::Node -Parser::withStatement() +Parser::withStatement(YieldHandling yieldHandling) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); return null(); @@ -5416,7 +5456,7 @@ Parser::withStatement() template typename ParseHandler::Node -Parser::labeledStatement() +Parser::labeledStatement(YieldHandling yieldHandling) { uint32_t begin = pos().begin; RootedPropertyName label(context, tokenStream.currentName()); @@ -5433,7 +5473,7 @@ Parser::labeledStatement() StmtInfoPC stmtInfo(context); PushStatementPC(pc, &stmtInfo, STMT_LABEL); stmtInfo.label = label; - Node pn = statement(); + Node pn = statement(yieldHandling); if (!pn) return null(); @@ -5445,7 +5485,7 @@ Parser::labeledStatement() template typename ParseHandler::Node -Parser::throwStatement() +Parser::throwStatement(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_THROW)); uint32_t begin = pos().begin; @@ -5463,7 +5503,7 @@ Parser::throwStatement() return null(); } - Node throwExpr = expr(); + Node throwExpr = expr(InAllowed, yieldHandling); if (!throwExpr) return null(); @@ -5475,7 +5515,7 @@ Parser::throwStatement() template typename ParseHandler::Node -Parser::tryStatement() +Parser::tryStatement(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_TRY)); uint32_t begin = pos().begin; @@ -5502,7 +5542,7 @@ Parser::tryStatement() StmtInfoPC stmtInfo(context); if (!PushBlocklikeStatement(tokenStream, &stmtInfo, STMT_TRY, pc)) return null(); - Node innerBlock = statements(); + Node innerBlock = statements(yieldHandling); if (!innerBlock) return null(); MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_TRY); @@ -5561,12 +5601,19 @@ Parser::tryStatement() switch (tt) { case TOK_LB: case TOK_LC: - catchName = destructuringExpr(&data, tt); + catchName = destructuringExpr(yieldHandling, &data, tt); if (!catchName) return null(); break; case TOK_YIELD: + if (yieldHandling == YieldIsKeyword) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); + return null(); + } + + // Even if yield is *not* necessarily a keyword, we still must + // check its validity for legacy generators. if (!checkYieldNameValidity()) return null(); // Fall through. @@ -5598,7 +5645,7 @@ Parser::tryStatement() if (!tokenStream.matchToken(&matched, TOK_IF)) return null(); if (matched) { - catchGuard = expr(); + catchGuard = expr(InAllowed, yieldHandling); if (!catchGuard) return null(); } @@ -5606,7 +5653,7 @@ Parser::tryStatement() MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH); MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH); - Node catchBody = statements(); + Node catchBody = statements(yieldHandling); if (!catchBody) return null(); MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_CATCH); @@ -5631,7 +5678,7 @@ Parser::tryStatement() MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY); if (!PushBlocklikeStatement(tokenStream, &stmtInfo, STMT_FINALLY, pc)) return null(); - finallyBlock = statements(); + finallyBlock = statements(yieldHandling); if (!finallyBlock) return null(); MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_FINALLY); @@ -5664,8 +5711,9 @@ Parser::debuggerStatement() } template <> -ParseNode * -Parser::classDefinition(ClassContext classContext) +ParseNode* +Parser::classDefinition(YieldHandling yieldHandling, + ClassContext classContext) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS)); @@ -5681,6 +5729,7 @@ Parser::classDefinition(ClassContext classContext) } else if (tt == TOK_YIELD) { if (!checkYieldNameValidity()) return null(); + MOZ_ASSERT(yieldHandling != YieldIsKeyword); name = tokenStream.currentName(); } else if (classContext == ClassStatement) { // Class statements must have a bound name @@ -5696,7 +5745,7 @@ Parser::classDefinition(ClassContext classContext) return null(); } - ParseNode *classBlock = null(); + ParseNode* classBlock = null(); StmtInfoPC classStmt(context); if (name) { classBlock = pushLexicalScope(&classStmt); @@ -5709,28 +5758,28 @@ Parser::classDefinition(ClassContext classContext) // in order to provide it for the nodes created later. TokenPos namePos = pos(); - ParseNode *classHeritage = null(); + ParseNode* classHeritage = null(); bool hasHeritage; if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS)) return null(); if (hasHeritage) { if (!tokenStream.getToken(&tt)) return null(); - classHeritage = memberExpr(tt, true); + classHeritage = memberExpr(yieldHandling, tt, true); if (!classHeritage) return null(); } MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS); - ParseNode *classMethods = propertyList(ClassBody); + ParseNode* classMethods = propertyList(yieldHandling, ClassBody); if (!classMethods) return null(); - ParseNode *nameNode = null(); - ParseNode *methodsOrBlock = classMethods; + ParseNode* nameNode = null(); + ParseNode* methodsOrBlock = classMethods; if (name) { - ParseNode *innerBinding = makeInitializedLexicalBinding(name, true, namePos); + ParseNode* innerBinding = makeInitializedLexicalBinding(name, true, namePos); if (!innerBinding) return null(); @@ -5740,7 +5789,7 @@ Parser::classDefinition(ClassContext classContext) PopStatementPC(tokenStream, pc); - ParseNode *outerBinding = null(); + ParseNode* outerBinding = null(); if (classContext == ClassStatement) { outerBinding = makeInitializedLexicalBinding(name, false, namePos); if (!outerBinding) @@ -5759,7 +5808,7 @@ Parser::classDefinition(ClassContext classContext) template <> SyntaxParseHandler::Node -Parser::classDefinition(ClassContext classContext) +Parser::classDefinition(YieldHandling yieldHandling, ClassContext classContext) { MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); return SyntaxParseHandler::NodeFailure; @@ -5767,7 +5816,7 @@ Parser::classDefinition(ClassContext classContext) template typename ParseHandler::Node -Parser::statement(bool canHaveDirectives) +Parser::statement(YieldHandling yieldHandling, bool canHaveDirectives) { MOZ_ASSERT(checkOptionsCalled); @@ -5776,22 +5825,15 @@ Parser::statement(bool canHaveDirectives) TokenKind tt; if (!tokenStream.getToken(&tt, TokenStream::Operand)) return null(); + switch (tt) { + // BlockStatement[?Yield, ?Return] case TOK_LC: - return blockStatement(); - - case TOK_LET: - if (!abortIfSyntaxParser()) - return null(); - return lexicalDeclaration(/* isConst = */ false); - - case TOK_CONST: - if (!abortIfSyntaxParser()) - return null(); - return lexicalDeclaration(/* isConst = */ true); + return blockStatement(yieldHandling); + // VariableStatement[?Yield] case TOK_VAR: { - Node pn = variables(PNK_VAR); + Node pn = variables(yieldHandling, PNK_VAR); if (!pn) return null(); @@ -5803,52 +5845,14 @@ Parser::statement(bool canHaveDirectives) return pn; } - case TOK_IMPORT: - return importDeclaration(); - case TOK_EXPORT: - return exportDeclaration(); + // EmptyStatement case TOK_SEMI: return handler.newEmptyStatement(pos()); - case TOK_IF: - return ifStatement(); - case TOK_DO: - return doWhileStatement(); - case TOK_WHILE: - return whileStatement(); - case TOK_FOR: - return forStatement(); - case TOK_SWITCH: - return switchStatement(); - case TOK_CONTINUE: - return continueStatement(); - case TOK_BREAK: - return breakStatement(); - case TOK_RETURN: - return returnStatement(); - case TOK_WITH: - return withStatement(); - case TOK_THROW: - return throwStatement(); - case TOK_TRY: - return tryStatement(); - case TOK_FUNCTION: - return functionStmt(); - case TOK_DEBUGGER: - return debuggerStatement(); - case TOK_CLASS: - if (!abortIfSyntaxParser()) - return null(); - return classDefinition(ClassStatement); - - /* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */ - case TOK_CATCH: - report(ParseError, false, null(), JSMSG_CATCH_WITHOUT_TRY); - return null(); - - case TOK_FINALLY: - report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY); - return null(); + // ExpressionStatement[?Yield]. + // + // These should probably be handled by a single ExpressionStatement + // function in a default, not split up this way. case TOK_STRING: if (!canHaveDirectives && tokenStream.currentToken().atom() == context->names().useAsm) { if (!abortIfSyntaxParser()) @@ -5856,7 +5860,7 @@ Parser::statement(bool canHaveDirectives) if (!report(ParseWarning, false, null(), JSMSG_USE_ASM_DIRECTIVE_FAIL)) return null(); } - return expressionStatement(); + return expressionStatement(yieldHandling); case TOK_YIELD: { TokenKind next; @@ -5868,9 +5872,9 @@ Parser::statement(bool canHaveDirectives) if (next == TOK_COLON) { if (!checkYieldNameValidity()) return null(); - return labeledStatement(); + return labeledStatement(yieldHandling); } - return expressionStatement(); + return expressionStatement(yieldHandling); } case TOK_NAME: { @@ -5878,23 +5882,123 @@ Parser::statement(bool canHaveDirectives) if (!tokenStream.peekToken(&next)) return null(); if (next == TOK_COLON) - return labeledStatement(); - return expressionStatement(); + return labeledStatement(yieldHandling); + return expressionStatement(yieldHandling); } case TOK_NEW: - return expressionStatement(PredictInvoked); + return expressionStatement(yieldHandling, PredictInvoked); default: - return expressionStatement(); + return expressionStatement(yieldHandling); + + // IfStatement[?Yield, ?Return] + case TOK_IF: + return ifStatement(yieldHandling); + + // BreakableStatement[?Yield, ?Return] + // + // BreakableStatement[Yield, Return]: + // IterationStatement[?Yield, ?Return] + // SwitchStatement[?Yield, ?Return] + case TOK_DO: + return doWhileStatement(yieldHandling); + + case TOK_WHILE: + return whileStatement(yieldHandling); + + case TOK_FOR: + return forStatement(yieldHandling); + + case TOK_SWITCH: + return switchStatement(yieldHandling); + + // ContinueStatement[?Yield] + case TOK_CONTINUE: + return continueStatement(yieldHandling); + + // BreakStatement[?Yield] + case TOK_BREAK: + return breakStatement(yieldHandling); + + // [+Return] ReturnStatement[?Yield] + case TOK_RETURN: + // The Return parameter is only used here, and the effect is easily + // detected this way, so don't bother passing around an extra parameter + // everywhere. + if (!pc->sc->isFunctionBox()) { + report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_return_str); + return null(); + } + return returnStatement(yieldHandling); + + // WithStatement[?Yield, ?Return] + case TOK_WITH: + return withStatement(yieldHandling); + + // LabelledStatement[?Yield, ?Return] + // This is really handled by TOK_NAME and TOK_YIELD cases above. + + // ThrowStatement[?Yield] + case TOK_THROW: + return throwStatement(yieldHandling); + + // TryStatement[?Yield, ?Return] + case TOK_TRY: + return tryStatement(yieldHandling); + + // DebuggerStatement + case TOK_DEBUGGER: + return debuggerStatement(); + + // HoistableDeclaration[?Yield] + case TOK_FUNCTION: + return functionStmt(yieldHandling); + + // ClassDeclaration[?Yield] + case TOK_CLASS: + if (!abortIfSyntaxParser()) + return null(); + return classDefinition(yieldHandling, ClassStatement); + + // LexicalDeclaration[In, ?Yield] + case TOK_LET: + if (!abortIfSyntaxParser()) + return null(); + return lexicalDeclaration(yieldHandling, /* isConst = */ false); + case TOK_CONST: + if (!abortIfSyntaxParser()) + return null(); + return lexicalDeclaration(yieldHandling, /* isConst = */ true); + + // ImportDeclaration (only inside modules) + case TOK_IMPORT: + return importDeclaration(); + + // ExportDeclaration (only inside modules) + case TOK_EXPORT: + return exportDeclaration(); + + // Miscellaneous error cases arguably better caught here than elsewhere. + + case TOK_CATCH: + report(ParseError, false, null(), JSMSG_CATCH_WITHOUT_TRY); + return null(); + + case TOK_FINALLY: + report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY); + return null(); + + // NOTE: default case handled in the ExpressionStatement section. } } template typename ParseHandler::Node -Parser::expr(InvokedPrediction invoked) +Parser::expr(InHandling inHandling, YieldHandling yieldHandling, + InvokedPrediction invoked) { - Node pn = assignExpr(invoked); + Node pn = assignExpr(inHandling, yieldHandling, invoked); if (!pn) return null(); @@ -5911,7 +6015,7 @@ Parser::expr(InvokedPrediction invoked) return null(); } - pn = assignExpr(); + pn = assignExpr(inHandling, yieldHandling); if (!pn) return null(); handler.addList(seq, pn); @@ -6018,7 +6122,8 @@ Precedence(ParseNodeKind pnk) { template MOZ_ALWAYS_INLINE typename ParseHandler::Node -Parser::orExpr1(InvokedPrediction invoked) +Parser::orExpr1(InHandling inHandling, YieldHandling yieldHandling, + InvokedPrediction invoked) { // Shift-reduce parser for the binary operator part of the JS expression // syntax. @@ -6034,7 +6139,7 @@ Parser::orExpr1(InvokedPrediction invoked) Node pn; for (;;) { - pn = unaryExpr(invoked); + pn = unaryExpr(yieldHandling, invoked); if (!pn) return pn; @@ -6043,6 +6148,9 @@ Parser::orExpr1(InvokedPrediction invoked) TokenKind tok; if (!tokenStream.getToken(&tok)) return null(); + + // FIXME: Change this to use |inHandling == InAllowed|, not + // |pc->parsingForInit|. ParseNodeKind pnk; if (IsBinaryOpToken(tok, oldParsingForInit)) { pnk = BinaryOpTokenKindToParseNodeKind(tok); @@ -6083,9 +6191,10 @@ Parser::orExpr1(InvokedPrediction invoked) template MOZ_ALWAYS_INLINE typename ParseHandler::Node -Parser::condExpr1(InvokedPrediction invoked) +Parser::condExpr1(InHandling inHandling, YieldHandling yieldHandling, + InvokedPrediction invoked) { - Node condition = orExpr1(invoked); + Node condition = orExpr1(inHandling, yieldHandling, invoked); if (!condition || !tokenStream.isCurrentTokenType(TOK_HOOK)) return condition; @@ -6096,14 +6205,14 @@ Parser::condExpr1(InvokedPrediction invoked) */ bool oldParsingForInit = pc->parsingForInit; pc->parsingForInit = false; - Node thenExpr = assignExpr(); + Node thenExpr = assignExpr(InAllowed, yieldHandling); pc->parsingForInit = oldParsingForInit; if (!thenExpr) return null(); MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND); - Node elseExpr = assignExpr(); + Node elseExpr = assignExpr(inHandling, yieldHandling); if (!elseExpr) return null(); @@ -6186,7 +6295,8 @@ Parser::checkAndMarkAsAssignmentLhs(Node pn, AssignmentFlavo template typename ParseHandler::Node -Parser::assignExpr(InvokedPrediction invoked) +Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandling, + InvokedPrediction invoked) { JS_CHECK_RECURSION(context, return null()); @@ -6211,7 +6321,7 @@ Parser::assignExpr(InvokedPrediction invoked) if (!tokenStream.nextTokenEndsExpr(&endsExpr)) return null(); if (endsExpr) - return identifierName(); + return identifierName(yieldHandling); } if (tt == TOK_NUMBER) { @@ -6229,7 +6339,7 @@ Parser::assignExpr(InvokedPrediction invoked) } if (tt == TOK_YIELD && yieldExpressionsSupported()) - return yieldExpression(); + return yieldExpression(inHandling); tokenStream.ungetToken(); @@ -6238,7 +6348,7 @@ Parser::assignExpr(InvokedPrediction invoked) TokenStream::Position start(keepAtoms); tokenStream.tell(&start); - Node lhs = condExpr1(invoked); + Node lhs = condExpr1(inHandling, yieldHandling, invoked); if (!lhs) return null(); @@ -6277,7 +6387,7 @@ Parser::assignExpr(InvokedPrediction invoked) if (!tokenStream.peekToken(&ignored)) return null(); - return functionDef(NullPtr(), Normal, Arrow, NotGenerator); + return functionDef(inHandling, yieldHandling, NullPtr(), Normal, Arrow, NotGenerator); } default: @@ -6292,7 +6402,7 @@ Parser::assignExpr(InvokedPrediction invoked) bool saved = pc->inDeclDestructuring; pc->inDeclDestructuring = false; - Node rhs = assignExpr(); + Node rhs = assignExpr(inHandling, yieldHandling); pc->inDeclDestructuring = saved; if (!rhs) return null(); @@ -6349,9 +6459,10 @@ Parser::checkAndMarkAsIncOperand(Node kid, TokenKind tt, boo template typename ParseHandler::Node -Parser::unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin) +Parser::unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kind, JSOp op, + uint32_t begin) { - Node kid = unaryExpr(); + Node kid = unaryExpr(yieldHandling); if (!kid) return null(); return handler.newUnary(kind, op, begin, kid); @@ -6359,7 +6470,7 @@ Parser::unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin) template typename ParseHandler::Node -Parser::unaryExpr(InvokedPrediction invoked) +Parser::unaryExpr(YieldHandling yieldHandling, InvokedPrediction invoked) { Node pn, pn2; @@ -6371,17 +6482,17 @@ Parser::unaryExpr(InvokedPrediction invoked) uint32_t begin = pos().begin; switch (tt) { case TOK_TYPEOF: - return unaryOpExpr(PNK_TYPEOF, JSOP_TYPEOF, begin); + return unaryOpExpr(yieldHandling, PNK_TYPEOF, JSOP_TYPEOF, begin); case TOK_VOID: - return unaryOpExpr(PNK_VOID, JSOP_VOID, begin); + return unaryOpExpr(yieldHandling, PNK_VOID, JSOP_VOID, begin); case TOK_NOT: - return unaryOpExpr(PNK_NOT, JSOP_NOT, begin); + return unaryOpExpr(yieldHandling, PNK_NOT, JSOP_NOT, begin); case TOK_BITNOT: - return unaryOpExpr(PNK_BITNOT, JSOP_BITNOT, begin); + return unaryOpExpr(yieldHandling, PNK_BITNOT, JSOP_BITNOT, begin); case TOK_ADD: - return unaryOpExpr(PNK_POS, JSOP_POS, begin); + return unaryOpExpr(yieldHandling, PNK_POS, JSOP_POS, begin); case TOK_SUB: - return unaryOpExpr(PNK_NEG, JSOP_NEG, begin); + return unaryOpExpr(yieldHandling, PNK_NEG, JSOP_NEG, begin); case TOK_INC: case TOK_DEC: @@ -6389,7 +6500,7 @@ Parser::unaryExpr(InvokedPrediction invoked) TokenKind tt2; if (!tokenStream.getToken(&tt2, TokenStream::Operand)) return null(); - pn2 = memberExpr(tt2, true); + pn2 = memberExpr(yieldHandling, tt2, true); if (!pn2) return null(); if (!checkAndMarkAsIncOperand(pn2, tt, true)) @@ -6401,7 +6512,7 @@ Parser::unaryExpr(InvokedPrediction invoked) } case TOK_DELETE: { - Node expr = unaryExpr(); + Node expr = unaryExpr(yieldHandling); if (!expr) return null(); @@ -6417,7 +6528,7 @@ Parser::unaryExpr(InvokedPrediction invoked) } default: - pn = memberExpr(tt, /* allowCallSyntax = */ true, invoked); + pn = memberExpr(yieldHandling, tt, /* allowCallSyntax = */ true, invoked); if (!pn) return null(); @@ -6831,7 +6942,7 @@ Parser::legacyComprehensionTail(ParseNode* bodyExpr, unsigned case TOK_LB: case TOK_LC: pc->inDeclDestructuring = true; - pn3 = primaryExpr(tt); + pn3 = primaryExpr(YieldIsKeyword, tt); pc->inDeclDestructuring = false; if (!pn3) return null(); @@ -6875,7 +6986,7 @@ Parser::legacyComprehensionTail(ParseNode* bodyExpr, unsigned headKind = PNK_FOROF; } - ParseNode* pn4 = expr(); + ParseNode* pn4 = expr(InAllowed, YieldIsKeyword); if (!pn4) return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); @@ -6936,7 +7047,7 @@ Parser::legacyComprehensionTail(ParseNode* bodyExpr, unsigned if (!tokenStream.matchToken(&matched, TOK_IF)) return null(); if (matched) { - ParseNode* cond = condition(); + ParseNode* cond = condition(InAllowed, YieldIsKeyword); if (!cond) return null(); ParseNode* ifNode = handler.new_(PNK_IF, JSOP_NOP, cond, nullptr, nullptr, @@ -7214,7 +7325,7 @@ Parser::comprehensionFor(GeneratorKind comprehensionKind) return null(); } - Node rhs = assignExpr(); + Node rhs = assignExpr(InAllowed, YieldIsKeyword); if (!rhs) return null(); @@ -7265,7 +7376,7 @@ Parser::comprehensionIf(GeneratorKind comprehensionKind) uint32_t begin = pos().begin; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); - Node cond = assignExpr(); + Node cond = assignExpr(InAllowed, YieldIsKeyword); if (!cond) return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); @@ -7302,7 +7413,7 @@ Parser::comprehensionTail(GeneratorKind comprehensionKind) uint32_t begin = pos().begin; - Node bodyExpr = assignExpr(); + Node bodyExpr = assignExpr(InAllowed, YieldIsKeyword); if (!bodyExpr) return null(); @@ -7389,10 +7500,10 @@ Parser::generatorComprehension(uint32_t begin) template typename ParseHandler::Node -Parser::assignExprWithoutYield(unsigned msg) +Parser::assignExprWithoutYield(YieldHandling yieldHandling, unsigned msg) { uint32_t startYieldOffset = pc->lastYieldOffset; - Node res = assignExpr(); + Node res = assignExpr(InAllowed, yieldHandling); if (res && pc->lastYieldOffset != startYieldOffset) { reportWithOffset(ParseError, false, pc->lastYieldOffset, msg, js_yield_str); @@ -7403,7 +7514,7 @@ Parser::assignExprWithoutYield(unsigned msg) template bool -Parser::argumentList(Node listNode, bool* isSpread) +Parser::argumentList(YieldHandling yieldHandling, Node listNode, bool* isSpread) { bool matched; if (!tokenStream.matchToken(&matched, TOK_RP, TokenStream::Operand)) @@ -7427,7 +7538,7 @@ Parser::argumentList(Node listNode, bool* isSpread) *isSpread = true; } - Node argNode = assignExpr(); + Node argNode = assignExpr(InAllowed, yieldHandling); if (!argNode) return false; if (spread) { @@ -7514,7 +7625,8 @@ Parser::checkAndMarkSuperScope() template typename ParseHandler::Node -Parser::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPrediction invoked) +Parser::memberExpr(YieldHandling yieldHandling, TokenKind tt, bool allowCallSyntax, + InvokedPrediction invoked) { MOZ_ASSERT(tokenStream.isCurrentTokenType(tt)); @@ -7533,7 +7645,7 @@ Parser::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred if (!tokenStream.getToken(&tt, TokenStream::Operand)) return null(); - Node ctorExpr = memberExpr(tt, false, PredictInvoked); + Node ctorExpr = memberExpr(yieldHandling, tt, false, PredictInvoked); if (!ctorExpr) return null(); @@ -7544,7 +7656,7 @@ Parser::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred return null(); if (matched) { bool isSpread = false; - if (!argumentList(lhs, &isSpread)) + if (!argumentList(yieldHandling, lhs, &isSpread)) return null(); if (isSpread) handler.setOp(lhs, JSOP_SPREADNEW); @@ -7553,7 +7665,7 @@ Parser::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred lhs = null(); isSuper = true; } else { - lhs = primaryExpr(tt, invoked); + lhs = primaryExpr(yieldHandling, tt, invoked); if (!lhs) return null(); } @@ -7587,7 +7699,7 @@ Parser::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred return null(); } } else if (tt == TOK_LB) { - Node propExpr = expr(); + Node propExpr = expr(InAllowed, yieldHandling); if (!propExpr) return null(); @@ -7656,7 +7768,7 @@ Parser::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred if (tt == TOK_LP) { bool isSpread = false; - if (!argumentList(nextMember, &isSpread)) + if (!argumentList(yieldHandling, nextMember, &isSpread)) return null(); if (isSpread) { if (op == JSOP_EVAL) @@ -7667,7 +7779,7 @@ Parser::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred op = JSOP_SPREADCALL; } } else { - if (!taggedTemplate(nextMember, tt)) + if (!taggedTemplate(yieldHandling, nextMember, tt)) return null(); } handler.setOp(nextMember, op); @@ -7700,9 +7812,17 @@ Parser::newName(PropertyName* name) template typename ParseHandler::Node -Parser::identifierName() +Parser::identifierName(YieldHandling yieldHandling) { RootedPropertyName name(context, tokenStream.currentName()); + if (yieldHandling == YieldIsKeyword && name == context->names().yield) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); + return null(); + } + + // If we're inside a function that later becomes a legacy generator, then + // a |yield| identifier name here will be detected by a subsequent + // |checkYieldNameValidity| call. Node pn = newName(name); if (!pn) return null(); @@ -7764,7 +7884,7 @@ Parser::newRegExp() template typename ParseHandler::Node -Parser::arrayInitializer() +Parser::arrayInitializer(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LB)); @@ -7812,13 +7932,13 @@ Parser::arrayInitializer() spread = true; tokenStream.consumeKnownToken(TOK_TRIPLEDOT); uint32_t begin = pos().begin; - Node inner = assignExpr(); + Node inner = assignExpr(InAllowed, yieldHandling); if (!inner) return null(); if (!handler.addSpreadElement(literal, begin, inner)) return null(); } else { - Node element = assignExpr(); + Node element = assignExpr(InAllowed, yieldHandling); if (!element) return null(); if (foldConstants && !FoldConstants(context, &element, this)) @@ -7909,7 +8029,7 @@ DoubleToAtom(ExclusiveContext* cx, double value) template typename ParseHandler::Node -Parser::computedPropertyName(Node literal) +Parser::computedPropertyName(YieldHandling yieldHandling, Node literal) { uint32_t begin = pos().begin; @@ -7919,7 +8039,7 @@ Parser::computedPropertyName(Node literal) // Parser<>::checkDestructuring() for details. bool saved = pc->inDeclDestructuring; pc->inDeclDestructuring = false; - Node assignNode = assignExpr(); + Node assignNode = assignExpr(InAllowed, yieldHandling); pc->inDeclDestructuring = saved; if (!assignNode) return null(); @@ -7945,7 +8065,7 @@ Parser::newPropertyListNode(PropListType type) template typename ParseHandler::Node -Parser::propertyList(PropListType type) +Parser::propertyList(YieldHandling yieldHandling, PropListType type) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); @@ -7999,7 +8119,7 @@ Parser::propertyList(PropListType type) break; case TOK_LB: { - propname = computedPropertyName(propList); + propname = computedPropertyName(yieldHandling, propList); if (!propname) return null(); break; @@ -8055,7 +8175,7 @@ Parser::propertyList(PropListType type) if (!propname) return null(); } else if (tt == TOK_LB) { - propname = computedPropertyName(propList); + propname = computedPropertyName(yieldHandling, propList); if (!propname) return null(); } else { @@ -8135,7 +8255,7 @@ Parser::propertyList(PropListType type) return null(); } - Node propexpr = assignExpr(); + Node propexpr = assignExpr(InAllowed, yieldHandling); if (!propexpr) return null(); @@ -8181,7 +8301,7 @@ Parser::propertyList(PropListType type) if (!tokenStream.checkForKeyword(atom, nullptr)) return null(); - Node nameExpr = identifierName(); + Node nameExpr = identifierName(yieldHandling); if (!nameExpr) return null(); @@ -8189,8 +8309,9 @@ Parser::propertyList(PropListType type) return null(); } else if (tt == TOK_LP) { tokenStream.ungetToken(); - if (!methodDefinition(type, propList, propname, Normal, - isGenerator ? StarGenerator : NotGenerator, isStatic, op)) { + if (!methodDefinition(yieldHandling, type, propList, propname, Normal, + isGenerator ? StarGenerator : NotGenerator, isStatic, op)) + { return null(); } } else { @@ -8198,8 +8319,10 @@ Parser::propertyList(PropListType type) return null(); } } else { - if (!methodDefinition(type, propList, propname, op == JSOP_INITPROP_GETTER ? Getter : Setter, - NotGenerator, isStatic, op)) { + if (!methodDefinition(yieldHandling, type, propList, propname, + op == JSOP_INITPROP_GETTER ? Getter : Setter, NotGenerator, + isStatic, op)) + { return null(); } } @@ -8229,9 +8352,9 @@ Parser::propertyList(PropListType type) template bool -Parser::methodDefinition(PropListType listType, Node propList, Node propname, - FunctionType type, GeneratorKind generatorKind, - bool isStatic, JSOp op) +Parser::methodDefinition(YieldHandling yieldHandling, PropListType listType, + Node propList, Node propname, FunctionType type, + GeneratorKind generatorKind, bool isStatic, JSOp op) { /* NB: Getter function in { get x(){} } is unnamed. */ RootedPropertyName funName(context); @@ -8240,7 +8363,7 @@ Parser::methodDefinition(PropListType listType, Node propList, Nod else funName = nullptr; - Node fn = functionDef(funName, type, Method, generatorKind); + Node fn = functionDef(InAllowed, yieldHandling, funName, type, Method, generatorKind); if (!fn) return false; @@ -8253,7 +8376,8 @@ Parser::methodDefinition(PropListType listType, Node propList, Nod template typename ParseHandler::Node -Parser::primaryExpr(TokenKind tt, InvokedPrediction invoked) +Parser::primaryExpr(YieldHandling yieldHandling, TokenKind tt, + InvokedPrediction invoked) { MOZ_ASSERT(tokenStream.isCurrentTokenType(tt)); JS_CHECK_RECURSION(context, return null()); @@ -8263,20 +8387,20 @@ Parser::primaryExpr(TokenKind tt, InvokedPrediction invoked) return functionExpr(invoked); case TOK_CLASS: - return classDefinition(ClassExpression); + return classDefinition(yieldHandling, ClassExpression); case TOK_LB: - return arrayInitializer(); + return arrayInitializer(yieldHandling); case TOK_LC: - return propertyList(ObjectLiteral); + return propertyList(yieldHandling, ObjectLiteral); case TOK_LP: { TokenKind next; if (!tokenStream.peekToken(&next, TokenStream::Operand)) return null(); if (next != TOK_RP) - return parenExprOrGeneratorComprehension(); + return parenExprOrGeneratorComprehension(yieldHandling); // Not valid expression syntax, but this is valid in an arrow function // with no params: `() => body`. @@ -8297,7 +8421,7 @@ Parser::primaryExpr(TokenKind tt, InvokedPrediction invoked) } case TOK_TEMPLATE_HEAD: - return templateLiteral(); + return templateLiteral(yieldHandling); case TOK_NO_SUBS_TEMPLATE: return noSubstitutionTemplate(); @@ -8310,7 +8434,7 @@ Parser::primaryExpr(TokenKind tt, InvokedPrediction invoked) return null(); // Fall through. case TOK_NAME: - return identifierName(); + return identifierName(yieldHandling); case TOK_REGEXP: return newRegExp(); @@ -8338,6 +8462,11 @@ Parser::primaryExpr(TokenKind tt, InvokedPrediction invoked) // are present. if (!tokenStream.getToken(&next)) return null(); + // FIXME: This fails to handle a rest parameter named |yield| correctly + // outside of generators: |var f = (...yield) => 42;| should be + // valid code! When this is fixed, make sure to consult both + // |yieldHandling| and |checkYieldNameValidity| for correctness + // until legacy generator syntax is removed. if (next != TOK_NAME) { report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN, "rest argument name", TokenKindToDesc(next)); @@ -8375,7 +8504,7 @@ Parser::primaryExpr(TokenKind tt, InvokedPrediction invoked) template typename ParseHandler::Node -Parser::parenExprOrGeneratorComprehension() +Parser::parenExprOrGeneratorComprehension(YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LP)); uint32_t begin = pos().begin; @@ -8394,7 +8523,7 @@ Parser::parenExprOrGeneratorComprehension() */ bool oldParsingForInit = pc->parsingForInit; pc->parsingForInit = false; - Node pn = expr(PredictInvoked); + Node pn = expr(InAllowed, yieldHandling, PredictInvoked); pc->parsingForInit = oldParsingForInit; if (!pn) @@ -8458,7 +8587,7 @@ Parser::parenExprOrGeneratorComprehension() template typename ParseHandler::Node -Parser::exprInParens() +Parser::exprInParens(InHandling inHandling, YieldHandling yieldHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LP)); uint32_t begin = pos().begin; @@ -8471,7 +8600,7 @@ Parser::exprInParens() */ bool oldParsingForInit = pc->parsingForInit; pc->parsingForInit = false; - Node pn = expr(PredictInvoked); + Node pn = expr(inHandling, yieldHandling, PredictInvoked); pc->parsingForInit = oldParsingForInit; if (!pn) diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 2c3954ba21..3d71bc9c01 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -330,6 +330,13 @@ enum VarContext { HoistVars, DontHoistVars }; enum FunctionType { Getter, Setter, Normal }; enum PropListType { ObjectLiteral, ClassBody }; +// Specify a value for an ES6 grammar parametrization. We have no enum for +// [Return] because its behavior is exactly equivalent to checking whether +// we're in a function box -- easier and simpler than passing an extra +// parameter everywhere. +enum YieldHandling { YieldIsName, YieldIsKeyword }; +enum InHandling { InAllowed, InProhibited }; + template class Parser : private JS::AutoGCRooter, public StrictModeGetter { @@ -459,10 +466,11 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node stringLiteral(); Node noSubstitutionTemplate(); - Node templateLiteral(); - bool taggedTemplate(Node nodeList, TokenKind tt); + Node templateLiteral(YieldHandling yieldHandling); + bool taggedTemplate(YieldHandling yieldHandling, Node nodeList, TokenKind tt); bool appendToCallSiteObj(Node callSiteObj); - bool addExprAndGetNextTemplStrToken(Node nodeList, TokenKind* ttp); + bool addExprAndGetNextTemplStrToken(YieldHandling yieldHandling, Node nodeList, + TokenKind* ttp); inline Node newName(PropertyName* name); inline Node newYieldExpression(uint32_t begin, Node expr, bool isYieldStar = false); @@ -470,9 +478,9 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter inline bool abortIfSyntaxParser(); public: - /* Public entry points for parsing. */ - Node statement(bool canHaveDirectives = false); + Node statement(YieldHandling yieldHandling, bool canHaveDirectives = false); + bool maybeParseDirective(Node list, Node pn, bool* cont); // Parse a function, given only its body. Used for the Function and @@ -491,9 +499,11 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter * statements; pass ExpressionBody if the body is a single expression. */ enum FunctionBodyType { StatementListBody, ExpressionBody }; - Node functionBody(FunctionSyntaxKind kind, FunctionBodyType type); + Node functionBody(InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, + FunctionBodyType type); - bool functionArgsAndBodyGeneric(Node pn, HandleFunction fun, FunctionType type, + bool functionArgsAndBodyGeneric(InHandling inHandling, YieldHandling yieldHandling, Node pn, + HandleFunction fun, FunctionType type, FunctionSyntaxKind kind); // Determine whether |yield| is a valid name in the current context, or @@ -534,66 +544,73 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter * Some parsers have two versions: an always-inlined version (with an 'i' * suffix) and a never-inlined version (with an 'n' suffix). */ - Node functionStmt(); + Node functionStmt(YieldHandling yieldHandling); Node functionExpr(InvokedPrediction invoked = PredictUninvoked); - Node statements(); + Node statements(YieldHandling yieldHandling); - Node blockStatement(); - Node ifStatement(); - Node doWhileStatement(); - Node whileStatement(); - Node forStatement(); - Node switchStatement(); - Node continueStatement(); - Node breakStatement(); - Node returnStatement(); - Node withStatement(); - Node labeledStatement(); - Node throwStatement(); - Node tryStatement(); + Node blockStatement(YieldHandling yieldHandling); + Node ifStatement(YieldHandling yieldHandling); + Node doWhileStatement(YieldHandling yieldHandling); + Node whileStatement(YieldHandling yieldHandling); + Node forStatement(YieldHandling yieldHandling); + Node switchStatement(YieldHandling yieldHandling); + Node continueStatement(YieldHandling yieldHandling); + Node breakStatement(YieldHandling yieldHandling); + Node returnStatement(YieldHandling yieldHandling); + Node withStatement(YieldHandling yieldHandling); + Node labeledStatement(YieldHandling yieldHandling); + Node throwStatement(YieldHandling yieldHandling); + Node tryStatement(YieldHandling yieldHandling); Node debuggerStatement(); - Node lexicalDeclaration(bool isConst); + Node lexicalDeclaration(YieldHandling yieldHandling, bool isConst); Node importDeclaration(); Node exportDeclaration(); - Node expressionStatement(InvokedPrediction invoked = PredictUninvoked); - Node variables(ParseNodeKind kind, bool* psimple = nullptr, - StaticBlockObject* blockObj = nullptr, - VarContext varContext = HoistVars); - Node expr(InvokedPrediction invoked = PredictUninvoked); - Node assignExpr(InvokedPrediction invoked = PredictUninvoked); - Node assignExprWithoutYield(unsigned err); - Node yieldExpression(); - Node condExpr1(InvokedPrediction invoked = PredictUninvoked); - Node orExpr1(InvokedPrediction invoked = PredictUninvoked); - Node unaryExpr(InvokedPrediction invoked = PredictUninvoked); - Node memberExpr(TokenKind tt, bool allowCallSyntax, + Node expressionStatement(YieldHandling yieldHandling, + InvokedPrediction invoked = PredictUninvoked); + Node variables(YieldHandling yieldHandling, + ParseNodeKind kind, bool* psimple = nullptr, + StaticBlockObject* blockObj = nullptr, VarContext varContext = HoistVars); + Node expr(InHandling inHandling, YieldHandling yieldHandling, + InvokedPrediction invoked = PredictUninvoked); + Node assignExpr(InHandling inHandling, YieldHandling yieldHandling, InvokedPrediction invoked = PredictUninvoked); - Node primaryExpr(TokenKind tt, InvokedPrediction invoked = PredictUninvoked); - Node parenExprOrGeneratorComprehension(); - Node exprInParens(); + Node assignExprWithoutYield(YieldHandling yieldHandling, unsigned err); + Node yieldExpression(InHandling inHandling); + Node condExpr1(InHandling inHandling, YieldHandling yieldHandling, + InvokedPrediction invoked = PredictUninvoked); + Node orExpr1(InHandling inHandling, YieldHandling yieldHandling, + InvokedPrediction invoked = PredictUninvoked); + Node unaryExpr(YieldHandling yieldHandling, InvokedPrediction invoked = PredictUninvoked); + Node memberExpr(YieldHandling yieldHandling, TokenKind tt, bool allowCallSyntax, + InvokedPrediction invoked = PredictUninvoked); + Node primaryExpr(YieldHandling yieldHandling, TokenKind tt, + InvokedPrediction invoked = PredictUninvoked); + Node parenExprOrGeneratorComprehension(YieldHandling yieldHandling); + Node exprInParens(InHandling inHandling, YieldHandling yieldHandling); bool checkAndMarkSuperScope(); - bool methodDefinition(PropListType listType, Node propList, Node propname, FunctionType type, - GeneratorKind generatorKind, bool isStatic, JSOp Op); + bool methodDefinition(YieldHandling yieldHandling, PropListType listType, Node propList, + Node propname, FunctionType type, GeneratorKind generatorKind, + bool isStatic, JSOp Op); /* * Additional JS parsers. */ - bool functionArguments(FunctionSyntaxKind kind, FunctionType type, Node* list, Node funcpn, - bool* hasRest); + bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind, FunctionType type, + Node* list, Node funcpn, bool* hasRest); - Node functionDef(HandlePropertyName name, FunctionType type, FunctionSyntaxKind kind, - GeneratorKind generatorKind, InvokedPrediction invoked = PredictUninvoked); - bool functionArgsAndBody(Node pn, HandleFunction fun, - FunctionType type, FunctionSyntaxKind kind, - GeneratorKind generatorKind, + Node functionDef(InHandling inHandling, YieldHandling uieldHandling, HandlePropertyName name, + FunctionType type, FunctionSyntaxKind kind, GeneratorKind generatorKind, + InvokedPrediction invoked = PredictUninvoked); + bool functionArgsAndBody(InHandling inHandling, Node pn, HandleFunction fun, FunctionType type, + FunctionSyntaxKind kind, GeneratorKind generatorKind, Directives inheritedDirectives, Directives* newDirectives); - Node unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin); + Node unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kind, JSOp op, uint32_t begin); - Node condition(); + Node condition(InHandling inHandling, YieldHandling yieldHandling); Node generatorComprehensionLambda(GeneratorKind comprehensionKind, unsigned begin, Node innerStmt); @@ -611,16 +628,18 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node arrayComprehension(uint32_t begin); Node generatorComprehension(uint32_t begin); - bool argumentList(Node listNode, bool* isSpread); - Node destructuringExpr(BindData* data, TokenKind tt); - Node destructuringExprWithoutYield(BindData* data, TokenKind tt, unsigned msg); + bool argumentList(YieldHandling yieldHandling, Node listNode, bool* isSpread); + Node destructuringExpr(YieldHandling yieldHandling, BindData* data, + TokenKind tt); + Node destructuringExprWithoutYield(YieldHandling yieldHandling, BindData* data, + TokenKind tt, unsigned msg); enum ClassContext { ClassStatement, ClassExpression }; - Node classDefinition(ClassContext classContext); + Node classDefinition(YieldHandling yieldHandling, ClassContext classContext); - Node identifierName(); + Node identifierName(YieldHandling yieldHandling); - bool matchLabel(MutableHandle label); + bool matchLabel(YieldHandling yieldHandling, MutableHandle label); bool allowsForEachIn() { #if !JS_HAS_FOR_EACH_IN @@ -659,11 +678,11 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node pushLexicalScope(Handle blockObj, StmtInfoPC* stmt); Node pushLetScope(Handle blockObj, StmtInfoPC* stmt); bool noteNameUse(HandlePropertyName name, Node pn); - Node computedPropertyName(Node literal); - Node arrayInitializer(); + Node computedPropertyName(YieldHandling yieldHandling, Node literal); + Node arrayInitializer(YieldHandling yieldHandling); Node newRegExp(); - Node propertyList(PropListType type); + Node propertyList(YieldHandling yieldHandling, PropListType type); Node newPropertyListNode(PropListType type); bool checkAndPrepareLexical(bool isConst, const TokenPos &errorPos); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 7be11fda6c..31418c8121 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -43,6 +43,10 @@ class SyntaxParseHandler NodeGetProp, NodeStringExprStatement, NodeLValue, + NodeReturn, + NodeHoistableDeclaration, + NodeBreak, + NodeThrow, // In rare cases a parenthesized |node| doesn't have the same semantics // as |node|. Each such node has a special Node value, and we use a @@ -214,14 +218,14 @@ class SyntaxParseHandler Node newSwitchStatement(uint32_t begin, Node discriminant, Node caseList) { return NodeGeneric; } Node newCaseOrDefault(uint32_t begin, Node expr, Node body) { return NodeGeneric; } Node newContinueStatement(PropertyName* label, const TokenPos& pos) { return NodeGeneric; } - Node newBreakStatement(PropertyName* label, const TokenPos& pos) { return NodeGeneric; } - Node newReturnStatement(Node expr, Node genrval, const TokenPos& pos) { return NodeGeneric; } + Node newBreakStatement(PropertyName* label, const TokenPos& pos) { return NodeBreak; } + Node newReturnStatement(Node expr, Node genrval, const TokenPos& pos) { return NodeReturn; } Node newLabeledStatement(PropertyName* label, Node stmt, uint32_t begin) { return NodeGeneric; } - Node newThrowStatement(Node expr, const TokenPos& pos) { return NodeGeneric; } + Node newThrowStatement(Node expr, const TokenPos& pos) { return NodeThrow; } Node newTryStatement(uint32_t begin, Node body, Node catchList, Node finallyBlock) { return NodeGeneric; } @@ -238,7 +242,7 @@ class SyntaxParseHandler Node catchName, Node catchGuard, Node catchBody) { return true; } void setLastFunctionArgumentDefault(Node funcpn, Node pn) {} - Node newFunctionDefinition() { return NodeGeneric; } + Node newFunctionDefinition() { return NodeHoistableDeclaration; } void setFunctionBody(Node pn, Node kid) {} void setFunctionBox(Node pn, FunctionBox* funbox) {} void addFunctionArgument(Node pn, Node argpn) {} @@ -273,11 +277,23 @@ class SyntaxParseHandler } Node newList(ParseNodeKind kind, JSOp op = JSOP_NOP) { + MOZ_ASSERT(kind != PNK_VAR); return NodeGeneric; } + Node newDeclarationList(ParseNodeKind kind, JSOp op = JSOP_NOP) { + MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || + kind == PNK_GLOBALCONST); + return kind == PNK_VAR ? NodeHoistableDeclaration : NodeGeneric; + } Node newList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) { + MOZ_ASSERT(kind != PNK_VAR); return NodeGeneric; } + Node newDeclarationList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) { + MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || + kind == PNK_GLOBALCONST); + return kind == PNK_VAR ? NodeHoistableDeclaration : NodeGeneric; + } Node newCatchList() { return newList(PNK_CATCHLIST, JSOP_NOP); @@ -288,7 +304,8 @@ class SyntaxParseHandler } void addList(Node list, Node kid) { - MOZ_ASSERT(list == NodeGeneric || list == NodeUnparenthesizedCommaExpr); + MOZ_ASSERT(list == NodeGeneric || list == NodeUnparenthesizedCommaExpr || + list == NodeHoistableDeclaration); } Node newAssignment(ParseNodeKind kind, Node lhs, Node rhs, @@ -311,6 +328,14 @@ class SyntaxParseHandler return node == NodeUnparenthesizedAssignment; } + bool isReturnStatement(Node node) { + return node == NodeReturn; + } + + bool isStatementPermittedAfterReturnStatement(Node pn) { + return pn == NodeHoistableDeclaration || pn == NodeBreak || pn == NodeThrow; + } + void setOp(Node pn, JSOp op) {} void setBlockId(Node pn, unsigned blockid) {} void setFlag(Node pn, unsigned flag) {} diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 54517da973..18c6bf3235 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -322,33 +322,6 @@ TokenStream::TokenStream(ExclusiveContext* cx, const ReadOnlyCompileOptions& opt isExprEnding[TOK_RP] = 1; isExprEnding[TOK_RB] = 1; isExprEnding[TOK_RC] = 1; - - memset(isExprStarting, 0, sizeof(isExprStarting)); - isExprStarting[TOK_INC] = 1; - isExprStarting[TOK_DEC] = 1; - isExprStarting[TOK_LB] = 1; - isExprStarting[TOK_LC] = 1; - isExprStarting[TOK_LP] = 1; - isExprStarting[TOK_NAME] = 1; - isExprStarting[TOK_NUMBER] = 1; - isExprStarting[TOK_STRING] = 1; - isExprStarting[TOK_TEMPLATE_HEAD] = 1; - isExprStarting[TOK_NO_SUBS_TEMPLATE] = 1; - isExprStarting[TOK_REGEXP] = 1; - isExprStarting[TOK_TRUE] = 1; - isExprStarting[TOK_FALSE] = 1; - isExprStarting[TOK_NULL] = 1; - isExprStarting[TOK_THIS] = 1; - isExprStarting[TOK_NEW] = 1; - isExprStarting[TOK_DELETE] = 1; - isExprStarting[TOK_YIELD] = 1; - isExprStarting[TOK_CLASS] = 1; - isExprStarting[TOK_ADD] = 1; - isExprStarting[TOK_SUB] = 1; - isExprStarting[TOK_TYPEOF] = 1; - isExprStarting[TOK_VOID] = 1; - isExprStarting[TOK_NOT] = 1; - isExprStarting[TOK_BITNOT] = 1; } #ifdef _MSC_VER diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 8cee0d0a9f..d2f917d7fb 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -500,14 +500,6 @@ class MOZ_STACK_CLASS TokenStream return true; } - bool nextTokenStartsExpr(bool* startsExpr, Modifier modifier = None) { - TokenKind tt; - if (!peekToken(&tt, modifier)) - return false; - *startsExpr = isExprStarting[tt]; - return true; - } - bool nextTokenEndsExpr(bool* endsExpr) { TokenKind tt; if (!peekToken(&tt)) @@ -844,7 +836,6 @@ class MOZ_STACK_CLASS TokenStream mozilla::UniquePtr displayURL_; // the user's requested source URL or null mozilla::UniquePtr sourceMapURL_; // source map's filename or null CharBuffer tokenbuf; // current token string buffer - uint8_t isExprStarting[TOK_LIMIT];// which tokens can start exprs? uint8_t isExprEnding[TOK_LIMIT];// which tokens definitely terminate exprs? ExclusiveContext* const cx; bool mutedErrors; diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index f9699feea2..f11386f6e4 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -72,7 +72,10 @@ GCRuntime::checkAllocatorState(JSContext* cx, AllocKind kind) // For testing out of memory conditions if (js::oom::ShouldFailWithOOM()) { - ReportOutOfMemory(cx); + // If we are doing a fallible allocation, percolate up the OOM + // instead of reporting it. + if (allowGC) + ReportOutOfMemory(cx); return false; } diff --git a/js/src/gc/Barrier.h b/js/src/gc/Barrier.h index ec0575ec1a..de21ad17e8 100644 --- a/js/src/gc/Barrier.h +++ b/js/src/gc/Barrier.h @@ -733,6 +733,7 @@ typedef ReadBarriered ReadBarrieredDebugScopeObject; typedef ReadBarriered ReadBarrieredGlobalObject; typedef ReadBarriered ReadBarrieredFunction; typedef ReadBarriered ReadBarrieredObject; +typedef ReadBarriered ReadBarrieredScript; typedef ReadBarriered ReadBarrieredScriptSourceObject; typedef ReadBarriered ReadBarrieredShape; typedef ReadBarriered ReadBarrieredUnownedBaseShape; diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index 025b60c4c3..53b395ecb1 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -619,6 +619,7 @@ class GCRuntime void startGC(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0); void gcSlice(JS::gcreason::Reason reason, int64_t millis = 0); void finishGC(JS::gcreason::Reason reason); + void abortGC(); void startDebugGC(JSGCInvocationKind gckind, SliceBudget& budget); void debugGCSlice(SliceBudget& budget); diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 27491a70f0..2e061d886e 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -77,7 +77,10 @@ enum InitialHeap { /* The GC allocation kinds. */ enum class AllocKind : uint8_t { FIRST, - OBJECT0 = FIRST, + OBJECT_FIRST = FIRST, + FUNCTION = FIRST, + FUNCTION_EXTENDED, + OBJECT0, OBJECT0_BACKGROUND, OBJECT2, OBJECT2_BACKGROUND, @@ -108,13 +111,13 @@ enum class AllocKind : uint8_t { static_assert(int(AllocKind::FIRST) == 0, "Various places depend on AllocKind starting at 0, " "please audit them carefully!"); -static_assert(int(AllocKind::OBJECT0) == 0, "Various places depend on AllocKind::OBJECT0 being 0, " - "please audit them carefully!"); +static_assert(int(AllocKind::OBJECT_FIRST) == 0, "Various places depend on AllocKind::OBJECT_FIRST " + "being 0, please audit them carefully!"); inline bool IsObjectAllocKind(AllocKind kind) { - return kind >= AllocKind::OBJECT0 && kind <= AllocKind::OBJECT_LAST; + return kind >= AllocKind::OBJECT_FIRST && kind <= AllocKind::OBJECT_LAST; } inline bool @@ -138,10 +141,10 @@ AllAllocKinds() // Returns a sequence for use in a range-based for loop, // to iterate over all object alloc kinds. -inline decltype(mozilla::MakeEnumeratedRange(AllocKind::OBJECT0, AllocKind::OBJECT_LIMIT)) +inline decltype(mozilla::MakeEnumeratedRange(AllocKind::OBJECT_FIRST, AllocKind::OBJECT_LIMIT)) ObjectAllocKinds() { - return mozilla::MakeEnumeratedRange(AllocKind::OBJECT0, AllocKind::OBJECT_LIMIT); + return mozilla::MakeEnumeratedRange(AllocKind::OBJECT_FIRST, AllocKind::OBJECT_LIMIT); } // Returns a sequence for use in a range-based for loop, @@ -168,6 +171,8 @@ static inline JSGCTraceKind MapAllocToTraceKind(AllocKind kind) { static const JSGCTraceKind map[] = { + JSTRACE_OBJECT, /* AllocKind::FUNCTION */ + JSTRACE_OBJECT, /* AllocKind::FUNCTION_EXTENDED */ JSTRACE_OBJECT, /* AllocKind::OBJECT0 */ JSTRACE_OBJECT, /* AllocKind::OBJECT0_BACKGROUND */ JSTRACE_OBJECT, /* AllocKind::OBJECT2 */ @@ -1022,19 +1027,20 @@ struct Chunk ArenaHeader* allocateArena(JSRuntime* rt, JS::Zone* zone, AllocKind kind, const AutoLockGC& lock); - enum ArenaDecommitState { IsCommitted = false, IsDecommitted = true }; - void releaseArena(JSRuntime* rt, ArenaHeader* aheader, const AutoLockGC& lock, - ArenaDecommitState state = IsCommitted); + void releaseArena(JSRuntime* rt, ArenaHeader* aheader, const AutoLockGC& lock); void recycleArena(ArenaHeader* aheader, SortedArenaList& dest, AllocKind thingKind, size_t thingsPerArena); - static Chunk* allocate(JSRuntime* rt); + bool decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock); + void decommitAllArenasWithoutUnlocking(const AutoLockGC& lock); - void decommitAllArenas(JSRuntime* rt); + static Chunk* allocate(JSRuntime* rt); private: inline void init(JSRuntime* rt); + void decommitAllArenas(JSRuntime* rt); + /* Search for a decommitted arena to allocate. */ unsigned findDecommittedArenaOffset(); ArenaHeader* fetchNextDecommittedArena(); @@ -1042,6 +1048,9 @@ struct Chunk void addArenaToFreeList(JSRuntime* rt, ArenaHeader* aheader); void addArenaToDecommittedList(JSRuntime* rt, const ArenaHeader* aheader); + void updateChunkListAfterAlloc(JSRuntime* rt, const AutoLockGC& lock); + void updateChunkListAfterFree(JSRuntime* rt, const AutoLockGC& lock); + public: /* Unlink and return the freeArenasHead. */ inline ArenaHeader* fetchNextFreeArena(JSRuntime* rt); @@ -1112,6 +1121,7 @@ inline uintptr_t ArenaHeader::address() const { uintptr_t addr = reinterpret_cast(this); + MOZ_ASSERT(addr); MOZ_ASSERT(!(addr & ArenaMask)); MOZ_ASSERT(Chunk::withinArenasRange(addr)); return addr; @@ -1175,7 +1185,8 @@ ArenaHeader::setNextDelayedMarking(ArenaHeader* aheader) MOZ_ASSERT(!(uintptr_t(aheader) & ArenaMask)); MOZ_ASSERT(!auxNextLink && !hasDelayedMarking); hasDelayedMarking = 1; - auxNextLink = aheader->arenaAddress() >> ArenaShift; + if (aheader) + auxNextLink = aheader->arenaAddress() >> ArenaShift; } inline void @@ -1198,7 +1209,8 @@ ArenaHeader::setNextAllocDuringSweep(ArenaHeader* aheader) { MOZ_ASSERT(!auxNextLink && !allocatedDuringIncremental); allocatedDuringIncremental = 1; - auxNextLink = aheader->arenaAddress() >> ArenaShift; + if (aheader) + auxNextLink = aheader->arenaAddress() >> ArenaShift; } inline void diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 20c5e88d85..2cb9649a1a 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -39,6 +39,7 @@ using mozilla::DebugOnly; using mozilla::IsBaseOf; using mozilla::IsSame; using mozilla::MakeRange; +using mozilla::PodCopy; // Tracing Overview // ================ @@ -218,10 +219,10 @@ js::CheckTracedThing(JSTracer* trc, T thing) if (isGcMarkingTracer) { GCMarker* gcMarker = static_cast(trc); MOZ_ASSERT_IF(gcMarker->shouldCheckCompartments(), - zone->isCollecting() || rt->isAtomsZone(zone)); + zone->isCollecting() || zone->isAtomsZone()); MOZ_ASSERT_IF(gcMarker->markColor() == GRAY, - !zone->isGCMarkingBlack() || rt->isAtomsZone(zone)); + !zone->isGCMarkingBlack() || zone->isAtomsZone()); MOZ_ASSERT(!(zone->isGCSweeping() || zone->isGCFinished() || zone->isGCCompacting())); } @@ -326,8 +327,7 @@ AssertZoneIsMarking(JSString* str) { #ifdef DEBUG Zone* zone = TenuredCell::fromPointer(str)->zone(); - JSRuntime* rt = str->runtimeFromMainThread(); - MOZ_ASSERT(zone->isGCMarking() || rt->isAtomsZone(zone)); + MOZ_ASSERT(zone->isGCMarking() || zone->isAtomsZone()); #endif } @@ -336,8 +336,7 @@ AssertZoneIsMarking(JS::Symbol* sym) { #ifdef DEBUG Zone* zone = TenuredCell::fromPointer(sym)->zone(); - JSRuntime* rt = sym->runtimeFromMainThread(); - MOZ_ASSERT(zone->isGCMarking() || rt->isAtomsZone(zone)); + MOZ_ASSERT(zone->isGCMarking() || zone->isAtomsZone()); #endif } @@ -671,6 +670,7 @@ DoMarking(GCMarker* gcmarker, T thing) if (MustSkipMarking(thing)) return; + CheckTracedThing(gcmarker, thing); gcmarker->traverse(thing); // Mark the compartment as live. @@ -761,10 +761,10 @@ GCMarker::traverse(AccessorShape* thing) { template void -js::GCMarker::traverse(S source, T target) +js::GCMarker::traverseEdge(S source, T target) { MOZ_ASSERT_IF(!ThingIsPermanentAtomOrWellKnownSymbol(target), - runtime()->isAtomsZone(target->zone()) || target->zone() == source->zone()); + target->zone()->isAtomsZone() || target->zone() == source->zone()); traverse(target); } @@ -772,31 +772,37 @@ namespace js { // Special-case JSObject->JSObject edges to check the compartment too. template <> void -GCMarker::traverse(JSObject* source, JSObject* target) +GCMarker::traverseEdge(JSObject* source, JSObject* target) { MOZ_ASSERT(target->compartment() == source->compartment()); traverse(target); } } // namespace js -template struct TraverseFunctor : public VoidDefaultAdaptor { +template struct TraverseEdgeFunctor : public VoidDefaultAdaptor { template void operator()(T t, GCMarker* gcmarker, S s) { - return gcmarker->traverse(s, t); + return gcmarker->traverseEdge(s, t); } }; template void -js::GCMarker::traverse(S source, jsid id) +js::GCMarker::traverseEdge(S source, jsid id) { - DispatchIdTyped(TraverseFunctor(), id, this, source); + DispatchIdTyped(TraverseEdgeFunctor(), id, this, source); +} + +template +void +js::GCMarker::traverseEdge(S source, Value v) +{ + DispatchValueTyped(TraverseEdgeFunctor(), v, this, source); } template bool js::GCMarker::mark(T* thing) { - CheckTracedThing(this, thing); AssertZoneIsMarking(thing); MOZ_ASSERT(!IsInsideNursery(gc::TenuredCell::fromPointer(thing))); return gc::ParticipatesInCC::value @@ -823,9 +829,6 @@ LazyScript::traceChildren(JSTracer* trc) if (enclosingScope_) TraceEdge(trc, &enclosingScope_, "enclosingScope"); - if (script_) - TraceEdge(trc, &script_, "realScript"); - // We rely on the fact that atoms are always tenured. FreeVariable* freeVariables = this->freeVariables(); for (auto i : MakeRange(numFreeVariables())) { @@ -841,25 +844,22 @@ inline void js::GCMarker::eagerlyMarkChildren(LazyScript *thing) { if (thing->function_) - traverse(thing, static_cast(thing->function_)); + traverseEdge(thing, static_cast(thing->function_)); if (thing->sourceObject_) - traverse(thing, static_cast(thing->sourceObject_)); + traverseEdge(thing, static_cast(thing->sourceObject_)); if (thing->enclosingScope_) - traverse(thing, static_cast(thing->enclosingScope_)); - - if (thing->script_) - traverse(thing, static_cast(thing->script_)); + traverseEdge(thing, static_cast(thing->enclosingScope_)); // We rely on the fact that atoms are always tenured. LazyScript::FreeVariable* freeVariables = thing->freeVariables(); for (auto i : MakeRange(thing->numFreeVariables())) - traverse(thing, static_cast(freeVariables[i].atom())); + traverseEdge(thing, static_cast(freeVariables[i].atom())); HeapPtrFunction* innerFunctions = thing->innerFunctions(); for (auto i : MakeRange(thing->numInnerFunctions())) - traverse(thing, static_cast(innerFunctions[i])); + traverseEdge(thing, static_cast(innerFunctions[i])); } void @@ -880,16 +880,16 @@ js::GCMarker::eagerlyMarkChildren(Shape* shape) { MOZ_ASSERT(shape->isMarked(this->markColor())); do { - traverse(shape, shape->base()); - traverse(shape, shape->propidRef().get()); + traverseEdge(shape, shape->base()); + traverseEdge(shape, shape->propidRef().get()); // When triggered between slices on belhalf of a barrier, these // objects may reside in the nursery, so require an extra check. // FIXME: Bug 1157967 - remove the isTenured checks. if (shape->hasGetterObject() && shape->getterObject()->isTenured()) - traverse(shape, shape->getterObject()); + traverseEdge(shape, shape->getterObject()); if (shape->hasSetterObject() && shape->setterObject()->isTenured()) - traverse(shape, shape->setterObject()); + traverseEdge(shape, shape->setterObject()); shape = shape->previous(); } while (shape && mark(shape)); @@ -1040,16 +1040,16 @@ js::GCMarker::lazilyMarkChildren(ObjectGroup* group) unsigned count = group->getPropertyCount(); for (unsigned i = 0; i < count; i++) { if (ObjectGroup::Property* prop = group->getProperty(i)) - traverse(group, prop->id.get()); + traverseEdge(group, prop->id.get()); } if (group->proto().isObject()) - traverse(group, group->proto().toObject()); + traverseEdge(group, group->proto().toObject()); group->compartment()->mark(); if (GlobalObject* global = group->compartment()->unsafeUnbarrieredMaybeGlobal()) - traverse(group, static_cast(global)); + traverseEdge(group, static_cast(global)); if (group->newScript()) group->newScript()->trace(this); @@ -1061,13 +1061,101 @@ js::GCMarker::lazilyMarkChildren(ObjectGroup* group) group->unboxedLayout().trace(this); if (ObjectGroup* unboxedGroup = group->maybeOriginalUnboxedGroup()) - traverse(group, unboxedGroup); + traverseEdge(group, unboxedGroup); if (TypeDescr* descr = group->maybeTypeDescr()) - traverse(group, static_cast(descr)); + traverseEdge(group, static_cast(descr)); if (JSFunction* fun = group->maybeInterpretedFunction()) - traverse(group, static_cast(fun)); + traverseEdge(group, static_cast(fun)); +} + +struct TraverseObjectFunctor +{ + template + void operator()(T* thing, GCMarker* gcmarker, JSObject* src) { + gcmarker->traverseEdge(src, *thing); + } +}; + +// Call the trace hook set on the object, if present. If further tracing of +// NativeObject fields is required, this will return the native object. +enum class CheckGeneration { DoChecks, NoChecks}; +template +static inline NativeObject* +CallTraceHook(Functor f, JSTracer* trc, JSObject* obj, CheckGeneration check, Args&&... args) +{ + const Class* clasp = obj->getClass(); + MOZ_ASSERT(clasp); + MOZ_ASSERT(obj->isNative() == clasp->isNative()); + + if (!clasp->trace) + return &obj->as(); + + // Global objects all have the same trace hook. That hook is safe without barriers + // if the global has no custom trace hook of its own, or has been moved to a different + // compartment, and so can't have one. + MOZ_ASSERT_IF(!(clasp->trace == JS_GlobalObjectTraceHook && + (!obj->compartment()->options().getTrace() || !obj->isOwnGlobal())), + clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS); + + if (clasp->trace == InlineTypedObject::obj_trace) { + Shape** pshape = obj->as().addressOfShapeFromGC(); + f(pshape, mozilla::Forward(args)...); + + InlineTypedObject& tobj = obj->as(); + if (tobj.typeDescr().hasTraceList()) { + VisitTraceList(f, tobj.typeDescr().traceList(), tobj.inlineTypedMem(), + mozilla::Forward(args)...); + } + + return nullptr; + } + + if (clasp == &UnboxedPlainObject::class_) { + JSObject** pexpando = obj->as().addressOfExpando(); + if (*pexpando) + f(pexpando, mozilla::Forward(args)...); + + UnboxedPlainObject& unboxed = obj->as(); + const UnboxedLayout& layout = check == CheckGeneration::DoChecks + ? unboxed.layout() + : unboxed.layoutDontCheckGeneration(); + if (layout.traceList()) { + VisitTraceList(f, layout.traceList(), unboxed.data(), + mozilla::Forward(args)...); + } + + return nullptr; + } + + clasp->trace(trc, obj); + + if (!clasp->isNative()) + return nullptr; + return &obj->as(); +} + +template +static void +VisitTraceList(F f, const int32_t* traceList, uint8_t* memory, Args&&... args) +{ + while (*traceList != -1) { + f(reinterpret_cast(memory + *traceList), mozilla::Forward(args)...); + traceList++; + } + traceList++; + while (*traceList != -1) { + JSObject** objp = reinterpret_cast(memory + *traceList); + if (*objp) + f(objp, mozilla::Forward(args)...); + traceList++; + } + traceList++; + while (*traceList != -1) { + f(reinterpret_cast(memory + *traceList), mozilla::Forward(args)...); + traceList++; + } } @@ -1128,9 +1216,6 @@ GCMarker::processMarkStackTop(SliceBudget& budget) HeapSlot* end; JSObject* obj; - const int32_t* unboxedTraceList; - uint8_t* unboxedMemory; - // Decode uintptr_t addr = stack.pop(); uintptr_t tag = addr & StackTagMask; @@ -1192,7 +1277,7 @@ GCMarker::processMarkStackTop(SliceBudget& budget) const Value& v = *vp++; if (v.isString()) { - traverse(obj, v.toString()); + traverseEdge(obj, v.toString()); } else if (v.isObject()) { JSObject* obj2 = &v.toObject(); MOZ_ASSERT(obj->compartment() == obj2->compartment()); @@ -1203,41 +1288,11 @@ GCMarker::processMarkStackTop(SliceBudget& budget) goto scan_obj; } } else if (v.isSymbol()) { - traverse(obj, v.toSymbol()); + traverseEdge(obj, v.toSymbol()); } } return; - scan_unboxed: - { - while (*unboxedTraceList != -1) { - JSString* str = *reinterpret_cast(unboxedMemory + *unboxedTraceList); - traverse(obj, str); - unboxedTraceList++; - } - unboxedTraceList++; - while (*unboxedTraceList != -1) { - JSObject* obj2 = *reinterpret_cast(unboxedMemory + *unboxedTraceList); - MOZ_ASSERT_IF(obj2, obj->compartment() == obj2->compartment()); - if (obj2) - traverse(obj, obj2); - unboxedTraceList++; - } - unboxedTraceList++; - while (*unboxedTraceList != -1) { - const Value& v = *reinterpret_cast(unboxedMemory + *unboxedTraceList); - if (v.isString()) { - traverse(obj, v.toString()); - } else if (v.isObject()) { - traverse(obj, &v.toObject()); - } else if (v.isSymbol()) { - traverse(obj, v.toSymbol()); - } - unboxedTraceList++; - } - return; - } - scan_obj: { AssertZoneIsMarking(obj); @@ -1249,48 +1304,15 @@ GCMarker::processMarkStackTop(SliceBudget& budget) } ObjectGroup* group = obj->groupFromGC(); - traverse(obj, group); + traverseEdge(obj, group); - /* Call the trace hook if necessary. */ - const Class* clasp = group->clasp(); - if (clasp->trace) { - // Global objects all have the same trace hook. That hook is safe without barriers - // if the global has no custom trace hook of its own, or has been moved to a different - // compartment, and so can't have one. - MOZ_ASSERT_IF(!(clasp->trace == JS_GlobalObjectTraceHook && - (!obj->compartment()->options().getTrace() || !obj->isOwnGlobal())), - clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS); - if (clasp->trace == InlineTypedObject::obj_trace) { - Shape* shape = obj->as().shapeFromGC(); - traverse(obj, shape); - TypeDescr* descr = &obj->as().typeDescr(); - if (!descr->hasTraceList()) - return; - unboxedTraceList = descr->traceList(); - unboxedMemory = obj->as().inlineTypedMem(); - goto scan_unboxed; - } - if (clasp == &UnboxedPlainObject::class_) { - JSObject* expando = obj->as().maybeExpando(); - if (expando) - traverse(obj, expando); - const UnboxedLayout& layout = obj->as().layout(); - unboxedTraceList = layout.traceList(); - if (!unboxedTraceList) - return; - unboxedMemory = obj->as().data(); - goto scan_unboxed; - } - clasp->trace(this, obj); - } - - if (!clasp->isNative()) + NativeObject *nobj = CallTraceHook(TraverseObjectFunctor(), this, obj, + CheckGeneration::DoChecks, this, obj); + if (!nobj) return; - NativeObject* nobj = &obj->as(); - Shape* shape = nobj->lastProperty(); - traverse(obj, shape); + traverseEdge(obj, shape); unsigned nslots = nobj->slotSpan(); @@ -1301,7 +1323,7 @@ GCMarker::processMarkStackTop(SliceBudget& budget) if (nobj->denseElementsAreCopyOnWrite()) { JSObject* owner = nobj->getElementsHeader()->ownerObject(); if (owner != nobj) { - traverse(obj, owner); + traverseEdge(obj, owner); break; } } @@ -1922,34 +1944,22 @@ js::Nursery::collectToFixedPoint(TenuringTracer& mover, TenureCountCache& tenure } } +struct TenuringFunctor +{ + template + void operator()(T* thing, TenuringTracer& mover) { + mover.traverse(thing); + } +}; + // Visit all object children of the object and trace them. void js::TenuringTracer::traceObject(JSObject* obj) { - const Class* clasp = obj->getClass(); - if (clasp->trace) { - if (clasp->trace == InlineTypedObject::obj_trace) { - TypeDescr* descr = &obj->as().typeDescr(); - if (descr->hasTraceList()) - markTraceList(descr->traceList(), obj->as().inlineTypedMem()); - return; - } - if (clasp == &UnboxedPlainObject::class_) { - JSObject** pexpando = obj->as().addressOfExpando(); - if (*pexpando) - traverse(pexpando); - const UnboxedLayout& layout = obj->as().layoutDontCheckGeneration(); - if (layout.traceList()) - markTraceList(layout.traceList(), obj->as().data()); - return; - } - clasp->trace(this, obj); - } - - MOZ_ASSERT(obj->isNative() == clasp->isNative()); - if (!clasp->isNative()) + NativeObject *nobj = CallTraceHook(TenuringFunctor(), this, obj, + CheckGeneration::NoChecks, *this); + if (!nobj) return; - NativeObject* nobj = &obj->as(); // Note: the contents of copy on write elements pointers are filled in // during parsing and cannot contain nursery pointers. @@ -1969,8 +1979,10 @@ js::TenuringTracer::traceObjectSlots(NativeObject* nobj, uint32_t start, uint32_ HeapSlot* dynStart; HeapSlot* dynEnd; nobj->getSlotRange(start, length, &fixedStart, &fixedEnd, &dynStart, &dynEnd); - traceSlots(fixedStart->unsafeGet(), fixedEnd->unsafeGet()); - traceSlots(dynStart->unsafeGet(), dynEnd->unsafeGet()); + if (fixedStart) + traceSlots(fixedStart->unsafeGet(), fixedEnd->unsafeGet()); + if (dynStart) + traceSlots(dynStart->unsafeGet(), dynEnd->unsafeGet()); } void @@ -1980,27 +1992,6 @@ js::TenuringTracer::traceSlots(Value* vp, Value* end) traverse(vp); } -void -js::TenuringTracer::markTraceList(const int32_t* traceList, uint8_t* memory) -{ - while (*traceList != -1) { - // Strings are not in the nursery and do not need tracing. - traceList++; - } - traceList++; - while (*traceList != -1) { - JSObject** pobj = reinterpret_cast(memory + *traceList); - traverse(pobj); - traceList++; - } - traceList++; - while (*traceList != -1) { - Value* pslot = reinterpret_cast(memory + *traceList); - traverse(pslot); - traceList++; - } -} - size_t js::TenuringTracer::moveObjectToTenured(JSObject* dst, JSObject* src, AllocKind dstKind) { diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 288e0f4fd8..5c6e131e62 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -149,10 +149,10 @@ class GCMarker : public JSTracer template void traverse(T thing); // Calls traverse on target after making additional assertions. - template void traverse(S source, T target); - + template void traverseEdge(S source, T target); // C++ requires explicit declarations of partial template instantiations. - template void traverse(S source, jsid target); + template void traverseEdge(S source, jsid target); + template void traverseEdge(S source, Value target); /* * Care must be taken changing the mark color from gray to black. The cycle diff --git a/js/src/gc/Nursery-inl.h b/js/src/gc/Nursery-inl.h index 8e873bb548..ffd809db45 100644 --- a/js/src/gc/Nursery-inl.h +++ b/js/src/gc/Nursery-inl.h @@ -25,7 +25,6 @@ js::Nursery::getForwardedPointer(JSObject** ref) const const gc::RelocationOverlay* overlay = reinterpret_cast(*ref); if (!overlay->isForwarded()) return false; - /* This static cast from Cell* restricts T to valid (GC thing) types. */ *ref = static_cast(overlay->forwardingAddress()); return true; } diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index b5d9e15c53..ef991d815c 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -88,7 +88,6 @@ class TenuringTracer : public JSTracer size_t moveSlotsToTenured(NativeObject* dst, NativeObject* src, gc::AllocKind dstKind); void traceSlots(JS::Value* vp, JS::Value* end); - void markTraceList(const int32_t* traceList, uint8_t* memory); }; class Nursery diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 794035ae5e..9f7de435b6 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -318,6 +318,7 @@ static const PhaseInfo phases[] = { { PHASE_GC_BEGIN, "Begin Callback", PHASE_NO_PARENT }, { PHASE_WAIT_BACKGROUND_THREAD, "Wait Background Thread", PHASE_NO_PARENT }, { PHASE_MARK_DISCARD_CODE, "Mark Discard Code", PHASE_NO_PARENT }, + { PHASE_RELAZIFY_FUNCTIONS, "Relazify Functions", PHASE_NO_PARENT }, { PHASE_PURGE, "Purge", PHASE_NO_PARENT }, { PHASE_MARK, "Mark", PHASE_NO_PARENT }, { PHASE_UNMARK, "Unmark", PHASE_MARK }, @@ -522,6 +523,9 @@ Statistics::formatData(StatisticsSerializer& ss, uint64_t timestamp) continue; } + char budgetDescription[200]; + slices[i].budget.describe(budgetDescription, sizeof(budgetDescription) - 1); + ss.beginObject(nullptr); ss.extra(" "); ss.appendNumber("Slice", "%d", "", i); @@ -529,6 +533,7 @@ Statistics::formatData(StatisticsSerializer& ss, uint64_t timestamp) ss.extra(" ("); ss.appendDecimal("When", "ms", t(slices[i].start - slices[0].start)); ss.appendString("Reason", ExplainReason(slices[i].reason)); + ss.appendString("Budget", budgetDescription); if (ss.isJSON()) { ss.appendDecimal("Page Faults", "", double(slices[i].endFaults - slices[i].startFaults)); @@ -622,13 +627,16 @@ Statistics::formatDescription() UniqueChars Statistics::formatSliceDescription(unsigned i, const SliceData& slice) { + char budgetDescription[200]; + slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1); + const char* format = "\ ---- Slice %u ----\n\ Reason: %s\n\ Reset: %s%s\n\ Page Faults: %ld\n\ - Pause: %.3fms (@ %.3fms)\n\ + Pause: %.3fms of %s budget (@ %.3fms)\n\ "; char buffer[1024]; memset(buffer, 0, sizeof(buffer)); @@ -636,7 +644,7 @@ Statistics::formatSliceDescription(unsigned i, const SliceData& slice) ExplainReason(slice.reason), slice.resetReason ? "yes - " : "no", slice.resetReason ? slice.resetReason : "", uint64_t(slice.endFaults - slice.startFaults), - t(slice.duration()), t(slice.start - slices[0].start)); + t(slice.duration()), budgetDescription, t(slice.start - slices[0].start)); return make_string_copy(buffer); } @@ -964,9 +972,6 @@ Statistics::endGC() if (fp) printStats(); - if (!aborted) - Debugger::onGarbageCollection(runtime, *this); - // Clear the timers at the end of a GC because we accumulate time in // between GCs for some (which come before PHASE_GC_BEGIN in the list.) PodZero(&phaseStartTimes[PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN); @@ -978,7 +983,7 @@ Statistics::endGC() void Statistics::beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind, - JS::gcreason::Reason reason) + SliceBudget budget, JS::gcreason::Reason reason) { this->zoneStats = zoneStats; @@ -986,7 +991,7 @@ Statistics::beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind, if (first) beginGC(gckind); - SliceData data(reason, PRMJ_Now(), GetPageFaultCount()); + SliceData data(budget, reason, PRMJ_Now(), GetPageFaultCount()); if (!slices.append(data)) { // OOM testing fails if we CrashAtUnhandlableOOM here. aborted = true; diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 285f7a21b7..43ca2254d0 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -29,6 +29,7 @@ enum Phase { PHASE_GC_BEGIN, PHASE_WAIT_BACKGROUND_THREAD, PHASE_MARK_DISCARD_CODE, + PHASE_RELAZIFY_FUNCTIONS, PHASE_PURGE, PHASE_MARK, PHASE_UNMARK, @@ -162,7 +163,7 @@ struct Statistics void endParallelPhase(Phase phase, const GCParallelTask* task); void beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind, - JS::gcreason::Reason reason); + SliceBudget budget, JS::gcreason::Reason reason); void endSlice(); void startTimingMutator(); @@ -206,13 +207,16 @@ struct Statistics static const size_t MAX_NESTING = 20; struct SliceData { - SliceData(JS::gcreason::Reason reason, int64_t start, size_t startFaults) - : reason(reason), resetReason(nullptr), start(start), startFaults(startFaults) + SliceData(SliceBudget budget, JS::gcreason::Reason reason, int64_t start, size_t startFaults) + : budget(budget), reason(reason), + resetReason(nullptr), + start(start), startFaults(startFaults) { for (size_t i = 0; i < MAX_MULTIPARENT_PHASES + 1; i++) mozilla::PodArrayZero(phaseTimes[i]); } + SliceBudget budget; JS::gcreason::Reason reason; const char* resetReason; int64_t start, end; @@ -318,12 +322,12 @@ struct Statistics struct AutoGCSlice { AutoGCSlice(Statistics& stats, const ZoneGCStats& zoneStats, JSGCInvocationKind gckind, - JS::gcreason::Reason reason + SliceBudget budget, JS::gcreason::Reason reason MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : stats(stats) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; - stats.beginSlice(zoneStats, gckind, reason); + stats.beginSlice(zoneStats, gckind, budget, reason); } ~AutoGCSlice() { stats.endSlice(); } diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index da43419e8b..f464c931c0 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -14,6 +14,7 @@ #include "vm/Debugger.h" #include "vm/Runtime.h" +#include "jscompartmentinlines.h" #include "jsgcinlines.h" using namespace js; @@ -73,9 +74,7 @@ Zone::setNeedsIncrementalBarrier(bool needs, ShouldUpdateJit updateJit) jitUsingBarriers_ = needs; } - if (needs && runtimeFromMainThread()->isAtomsZone(this)) - MOZ_ASSERT(!runtimeFromMainThread()->exclusiveThreadsPresent()); - + MOZ_ASSERT_IF(needs && isAtomsZone(), !runtimeFromMainThread()->exclusiveThreadsPresent()); MOZ_ASSERT_IF(needs, canCollect()); needsIncrementalBarrier_ = needs; } @@ -132,24 +131,31 @@ Zone::sweepBreakpoints(FreeOp *fop) MOZ_ASSERT(isGCSweepingOrCompacting()); for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) { - JSScript *script = i.get(); - MOZ_ASSERT_IF(isGCSweeping(), script->zone()->isGCSweeping()); + JSScript* script = i.get(); if (!script->hasAnyBreakpointsOrStepMode()) continue; bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script); MOZ_ASSERT(script == i.get()); for (unsigned i = 0; i < script->length(); i++) { - BreakpointSite *site = script->getBreakpointSite(script->offsetToPC(i)); + BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i)); if (!site) continue; - Breakpoint *nextbp; - for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) { + Breakpoint* nextbp; + for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) { nextbp = bp->nextInSite(); HeapPtrNativeObject& dbgobj = bp->debugger->toJSObjectRef(); + + // If we are sweeping, then we expect the script and the + // debugger object to be swept in the same zone group, except if + // the breakpoint was added after we computed the zone + // groups. In this case both script and debugger object must be + // live. MOZ_ASSERT_IF(isGCSweeping() && dbgobj->zone()->isCollecting(), - dbgobj->zone()->isGCSweeping()); + dbgobj->zone()->isGCSweeping() || + (!scriptGone && dbgobj->asTenured().isMarked())); + bool dying = scriptGone || IsAboutToBeFinalized(&dbgobj); MOZ_ASSERT_IF(!dying, !IsAboutToBeFinalized(&bp->getHandlerRef())); if (dying) @@ -160,7 +166,7 @@ Zone::sweepBreakpoints(FreeOp *fop) } void -Zone::discardJitCode(FreeOp *fop) +Zone::discardJitCode(FreeOp* fop) { if (!jitZone()) return; @@ -242,7 +248,7 @@ Zone::canCollect() if (usedByExclusiveThread) return false; JSRuntime* rt = runtimeFromAnyThread(); - if (rt->isAtomsZone(this) && rt->exclusiveThreadsPresent()) + if (isAtomsZone() && rt->exclusiveThreadsPresent()) return false; return true; } @@ -251,7 +257,8 @@ void Zone::notifyObservingDebuggers() { for (CompartmentsInZoneIter comps(this); !comps.done(); comps.next()) { - RootedGlobalObject global(runtimeFromAnyThread(), comps->maybeGlobal()); + JSRuntime* rt = runtimeFromAnyThread(); + RootedGlobalObject global(rt, comps->maybeGlobal()); if (!global) continue; @@ -259,8 +266,16 @@ Zone::notifyObservingDebuggers() if (!dbgs) continue; - for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty(); r.popFront()) - r.front()->debuggeeIsBeingCollected(); + for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty(); r.popFront()) { + if (!r.front()->debuggeeIsBeingCollected(rt->gc.majorGCCount())) { +#ifdef DEBUG + fprintf(stderr, + "OOM while notifying observing Debuggers of a GC: The onGarbageCollection\n" + "hook will not be fired for this GC for some Debuggers!\n"); +#endif + return; + } + } } } diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index c905b391f8..c0cb0d574f 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -222,6 +222,9 @@ struct Zone : public JS::shadow::Zone, js::jit::JitZone* getJitZone(JSContext* cx) { return jitZone_ ? jitZone_ : createJitZone(cx); } js::jit::JitZone* jitZone() { return jitZone_; } + bool isAtomsZone() const { return runtimeFromAnyThread()->isAtomsZone(this); } + bool isSelfHostingZone() const { return runtimeFromAnyThread()->isSelfHostingZone(this); } + #ifdef DEBUG // For testing purposes, return the index of the zone group which this zone // was swept in in the last GC. diff --git a/js/src/irregexp/NativeRegExpMacroAssembler.cpp b/js/src/irregexp/NativeRegExpMacroAssembler.cpp index f9f704abdf..0dac67511c 100644 --- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp +++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp @@ -37,6 +37,8 @@ #endif #include "vm/MatchPairs.h" +#include "jit/MacroAssembler-inl.h" + using namespace js; using namespace js::irregexp; using namespace js::jit; diff --git a/js/src/jit-test/lib/asserts.js b/js/src/jit-test/lib/asserts.js index a873645422..1443168f79 100644 --- a/js/src/jit-test/lib/asserts.js +++ b/js/src/jit-test/lib/asserts.js @@ -65,3 +65,35 @@ if (typeof assertNoWarning === 'undefined') { } }; } + +if (typeof assertErrorMessage === 'undefined') { + var assertErrorMessage = function assertErrorMessage(f, ctor, test) { + try { + f(); + } catch (e) { + if (!(e instanceof ctor)) + throw new Error("Assertion failed: expected exception " + ctor.name + ", got " + e); + if (typeof test == "string") { + if (test != e.message) + throw new Error("Assertion failed: expeceted " + test + ", got " + e.message); + } else { + if (!test.test(e.message)) + throw new Error("Assertion failed: expeceted " + test.toString() + ", got " + e.message); + } + return; + } + throw new Error("Assertion failed: expected exception " + ctor.name + ", no exception thrown"); + }; +} + +if (typeof assertTypeErrorMessage === 'undefined') { + var assertTypeErrorMessage = function assertTypeErrorMessage(f, test) { + assertErrorMessage(f, TypeError, test); + }; +} + +if (typeof assertRangeErrorMessage === 'undefined') { + var assertRangeErrorMessage = function assertRangeErrorMessage(f, test) { + assertErrorMessage(f, RangeError, test); + }; +} diff --git a/js/src/jit-test/lib/class.js b/js/src/jit-test/lib/class.js new file mode 100644 index 0000000000..77772d640c --- /dev/null +++ b/js/src/jit-test/lib/class.js @@ -0,0 +1 @@ +load(libdir + "../../tests/ecma_6/Class/shell.js"); diff --git a/js/src/jit-test/tests/SIMD/load.js b/js/src/jit-test/tests/SIMD/load.js index cc1339d9f9..01db207106 100644 --- a/js/src/jit-test/tests/SIMD/load.js +++ b/js/src/jit-test/tests/SIMD/load.js @@ -34,65 +34,65 @@ function f() { - function testLoadX() { - assertEqX4(SIMD.float32x4.loadX(f64, 0), [1,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(f32, 1), [2,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(i32, 2), [3,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(i16, 3 << 1), [4,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(u16, 4 << 1), [5,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(i8 , 5 << 2), [6,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(u8 , 6 << 2), [7,0,0,0]); + function testLoad1() { + assertEqX4(SIMD.float32x4.load1(f64, 0), [1,0,0,0]); + assertEqX4(SIMD.float32x4.load1(f32, 1), [2,0,0,0]); + assertEqX4(SIMD.float32x4.load1(i32, 2), [3,0,0,0]); + assertEqX4(SIMD.float32x4.load1(i16, 3 << 1), [4,0,0,0]); + assertEqX4(SIMD.float32x4.load1(u16, 4 << 1), [5,0,0,0]); + assertEqX4(SIMD.float32x4.load1(i8 , 5 << 2), [6,0,0,0]); + assertEqX4(SIMD.float32x4.load1(u8 , 6 << 2), [7,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(f64, (16 >> 1) - (4 >> 1)), [13,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(f32, 16 - 4), [13,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(i32, 16 - 4), [13,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(i16, (16 << 1) - (4 << 1)), [13,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(u16, (16 << 1) - (4 << 1)), [13,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(i8, (16 << 2) - (4 << 2)), [13,0,0,0]); - assertEqX4(SIMD.float32x4.loadX(u8, (16 << 2) - (4 << 2)), [13,0,0,0]); + assertEqX4(SIMD.float32x4.load1(f64, (16 >> 1) - (4 >> 1)), [13,0,0,0]); + assertEqX4(SIMD.float32x4.load1(f32, 16 - 4), [13,0,0,0]); + assertEqX4(SIMD.float32x4.load1(i32, 16 - 4), [13,0,0,0]); + assertEqX4(SIMD.float32x4.load1(i16, (16 << 1) - (4 << 1)), [13,0,0,0]); + assertEqX4(SIMD.float32x4.load1(u16, (16 << 1) - (4 << 1)), [13,0,0,0]); + assertEqX4(SIMD.float32x4.load1(i8, (16 << 2) - (4 << 2)), [13,0,0,0]); + assertEqX4(SIMD.float32x4.load1(u8, (16 << 2) - (4 << 2)), [13,0,0,0]); } - function testLoadXY() { - assertEqX4(SIMD.float32x4.loadXY(f64, 0), [1,2,0,0]); - assertEqX4(SIMD.float32x4.loadXY(f32, 1), [2,3,0,0]); - assertEqX4(SIMD.float32x4.loadXY(i32, 2), [3,4,0,0]); - assertEqX4(SIMD.float32x4.loadXY(i16, 3 << 1), [4,5,0,0]); - assertEqX4(SIMD.float32x4.loadXY(u16, 4 << 1), [5,6,0,0]); - assertEqX4(SIMD.float32x4.loadXY(i8 , 5 << 2), [6,7,0,0]); - assertEqX4(SIMD.float32x4.loadXY(u8 , 6 << 2), [7,8,0,0]); + function testLoad2() { + assertEqX4(SIMD.float32x4.load2(f64, 0), [1,2,0,0]); + assertEqX4(SIMD.float32x4.load2(f32, 1), [2,3,0,0]); + assertEqX4(SIMD.float32x4.load2(i32, 2), [3,4,0,0]); + assertEqX4(SIMD.float32x4.load2(i16, 3 << 1), [4,5,0,0]); + assertEqX4(SIMD.float32x4.load2(u16, 4 << 1), [5,6,0,0]); + assertEqX4(SIMD.float32x4.load2(i8 , 5 << 2), [6,7,0,0]); + assertEqX4(SIMD.float32x4.load2(u8 , 6 << 2), [7,8,0,0]); - assertEqX4(SIMD.float32x4.loadXY(f64, (16 >> 1) - (4 >> 1)), [13,14,0,0]); - assertEqX4(SIMD.float32x4.loadXY(f32, 16 - 4), [13,14,0,0]); - assertEqX4(SIMD.float32x4.loadXY(i32, 16 - 4), [13,14,0,0]); - assertEqX4(SIMD.float32x4.loadXY(i16, (16 << 1) - (4 << 1)), [13,14,0,0]); - assertEqX4(SIMD.float32x4.loadXY(u16, (16 << 1) - (4 << 1)), [13,14,0,0]); - assertEqX4(SIMD.float32x4.loadXY(i8, (16 << 2) - (4 << 2)), [13,14,0,0]); - assertEqX4(SIMD.float32x4.loadXY(u8, (16 << 2) - (4 << 2)), [13,14,0,0]); + assertEqX4(SIMD.float32x4.load2(f64, (16 >> 1) - (4 >> 1)), [13,14,0,0]); + assertEqX4(SIMD.float32x4.load2(f32, 16 - 4), [13,14,0,0]); + assertEqX4(SIMD.float32x4.load2(i32, 16 - 4), [13,14,0,0]); + assertEqX4(SIMD.float32x4.load2(i16, (16 << 1) - (4 << 1)), [13,14,0,0]); + assertEqX4(SIMD.float32x4.load2(u16, (16 << 1) - (4 << 1)), [13,14,0,0]); + assertEqX4(SIMD.float32x4.load2(i8, (16 << 2) - (4 << 2)), [13,14,0,0]); + assertEqX4(SIMD.float32x4.load2(u8, (16 << 2) - (4 << 2)), [13,14,0,0]); } - function testLoadXYZ() { - assertEqX4(SIMD.float32x4.loadXYZ(f64, 0), [1,2,3,0]); - assertEqX4(SIMD.float32x4.loadXYZ(f32, 1), [2,3,4,0]); - assertEqX4(SIMD.float32x4.loadXYZ(i32, 2), [3,4,5,0]); - assertEqX4(SIMD.float32x4.loadXYZ(i16, 3 << 1), [4,5,6,0]); - assertEqX4(SIMD.float32x4.loadXYZ(u16, 4 << 1), [5,6,7,0]); - assertEqX4(SIMD.float32x4.loadXYZ(i8 , 5 << 2), [6,7,8,0]); - assertEqX4(SIMD.float32x4.loadXYZ(u8 , 6 << 2), [7,8,9,0]); + function testLoad3() { + assertEqX4(SIMD.float32x4.load3(f64, 0), [1,2,3,0]); + assertEqX4(SIMD.float32x4.load3(f32, 1), [2,3,4,0]); + assertEqX4(SIMD.float32x4.load3(i32, 2), [3,4,5,0]); + assertEqX4(SIMD.float32x4.load3(i16, 3 << 1), [4,5,6,0]); + assertEqX4(SIMD.float32x4.load3(u16, 4 << 1), [5,6,7,0]); + assertEqX4(SIMD.float32x4.load3(i8 , 5 << 2), [6,7,8,0]); + assertEqX4(SIMD.float32x4.load3(u8 , 6 << 2), [7,8,9,0]); - assertEqX4(SIMD.float32x4.loadXYZ(f64, (16 >> 1) - (4 >> 1)), [13,14,15,0]); - assertEqX4(SIMD.float32x4.loadXYZ(f32, 16 - 4), [13,14,15,0]); - assertEqX4(SIMD.float32x4.loadXYZ(i32, 16 - 4), [13,14,15,0]); - assertEqX4(SIMD.float32x4.loadXYZ(i16, (16 << 1) - (4 << 1)), [13,14,15,0]); - assertEqX4(SIMD.float32x4.loadXYZ(u16, (16 << 1) - (4 << 1)), [13,14,15,0]); - assertEqX4(SIMD.float32x4.loadXYZ(i8, (16 << 2) - (4 << 2)), [13,14,15,0]); - assertEqX4(SIMD.float32x4.loadXYZ(u8, (16 << 2) - (4 << 2)), [13,14,15,0]); + assertEqX4(SIMD.float32x4.load3(f64, (16 >> 1) - (4 >> 1)), [13,14,15,0]); + assertEqX4(SIMD.float32x4.load3(f32, 16 - 4), [13,14,15,0]); + assertEqX4(SIMD.float32x4.load3(i32, 16 - 4), [13,14,15,0]); + assertEqX4(SIMD.float32x4.load3(i16, (16 << 1) - (4 << 1)), [13,14,15,0]); + assertEqX4(SIMD.float32x4.load3(u16, (16 << 1) - (4 << 1)), [13,14,15,0]); + assertEqX4(SIMD.float32x4.load3(i8, (16 << 2) - (4 << 2)), [13,14,15,0]); + assertEqX4(SIMD.float32x4.load3(u8, (16 << 2) - (4 << 2)), [13,14,15,0]); } for (var i = 0; i < 150; i++) { testLoad(); - testLoadX(); - testLoadXY(); - testLoadXYZ(); + testLoad1(); + testLoad2(); + testLoad3(); } } diff --git a/js/src/jit-test/tests/SIMD/store.js b/js/src/jit-test/tests/SIMD/store.js index c23cac9d4d..47439ae969 100644 --- a/js/src/jit-test/tests/SIMD/store.js +++ b/js/src/jit-test/tests/SIMD/store.js @@ -48,68 +48,68 @@ function f() { check(4); } - function testStoreX() { - SIMD.float32x4.storeX(f64, 0, f4); + function testStore1() { + SIMD.float32x4.store1(f64, 0, f4); check(1); - SIMD.float32x4.storeX(f32, 0, f4); + SIMD.float32x4.store1(f32, 0, f4); check(1); - SIMD.float32x4.storeX(i32, 0, f4); + SIMD.float32x4.store1(i32, 0, f4); check(1); - SIMD.float32x4.storeX(u32, 0, f4); + SIMD.float32x4.store1(u32, 0, f4); check(1); - SIMD.float32x4.storeX(i16, 0, f4); + SIMD.float32x4.store1(i16, 0, f4); check(1); - SIMD.float32x4.storeX(u16, 0, f4); + SIMD.float32x4.store1(u16, 0, f4); check(1); - SIMD.float32x4.storeX(i8, 0, f4); + SIMD.float32x4.store1(i8, 0, f4); check(1); - SIMD.float32x4.storeX(u8, 0, f4); + SIMD.float32x4.store1(u8, 0, f4); check(1); } - function testStoreXY() { - SIMD.float32x4.storeXY(f64, 0, f4); + function testStore2() { + SIMD.float32x4.store2(f64, 0, f4); check(2); - SIMD.float32x4.storeXY(f32, 0, f4); + SIMD.float32x4.store2(f32, 0, f4); check(2); - SIMD.float32x4.storeXY(i32, 0, f4); + SIMD.float32x4.store2(i32, 0, f4); check(2); - SIMD.float32x4.storeXY(u32, 0, f4); + SIMD.float32x4.store2(u32, 0, f4); check(2); - SIMD.float32x4.storeXY(i16, 0, f4); + SIMD.float32x4.store2(i16, 0, f4); check(2); - SIMD.float32x4.storeXY(u16, 0, f4); + SIMD.float32x4.store2(u16, 0, f4); check(2); - SIMD.float32x4.storeXY(i8, 0, f4); + SIMD.float32x4.store2(i8, 0, f4); check(2); - SIMD.float32x4.storeXY(u8, 0, f4); + SIMD.float32x4.store2(u8, 0, f4); check(2); } - function testStoreXYZ() { - SIMD.float32x4.storeXYZ(f64, 0, f4); + function testStore3() { + SIMD.float32x4.store3(f64, 0, f4); check(3); - SIMD.float32x4.storeXYZ(f32, 0, f4); + SIMD.float32x4.store3(f32, 0, f4); check(3); - SIMD.float32x4.storeXYZ(i32, 0, f4); + SIMD.float32x4.store3(i32, 0, f4); check(3); - SIMD.float32x4.storeXYZ(u32, 0, f4); + SIMD.float32x4.store3(u32, 0, f4); check(3); - SIMD.float32x4.storeXYZ(i16, 0, f4); + SIMD.float32x4.store3(i16, 0, f4); check(3); - SIMD.float32x4.storeXYZ(u16, 0, f4); + SIMD.float32x4.store3(u16, 0, f4); check(3); - SIMD.float32x4.storeXYZ(i8, 0, f4); + SIMD.float32x4.store3(i8, 0, f4); check(3); - SIMD.float32x4.storeXYZ(u8, 0, f4); + SIMD.float32x4.store3(u8, 0, f4); check(3); } for (var i = 0; i < 150; i++) { testStore(); - testStoreX(); - testStoreXY(); - testStoreXYZ(); + testStore1(); + testStore2(); + testStore3(); } } @@ -141,4 +141,3 @@ print('Testing range checks...'); testBailout(-1); testBailout(-15); testBailout(12 * 4 + 1); - diff --git a/js/src/jit-test/tests/asm.js/testBasic.js b/js/src/jit-test/tests/asm.js/testBasic.js index 27dab67049..c4054c814d 100644 --- a/js/src/jit-test/tests/asm.js/testBasic.js +++ b/js/src/jit-test/tests/asm.js/testBasic.js @@ -37,7 +37,7 @@ assertEq(asmLink(asmCompile(USE_ASM + 'function f(){;} return f'))(), undefined) assertAsmTypeFail(USE_ASM + 'function f(i,j){;} return f'); assertEq(asmLink(asmCompile('"use asm";; function f(){};;; return f;;'))(), undefined); assertAsmTypeFail(USE_ASM + 'function f(x){} return f'); -assertAsmTypeFail(USE_ASM + 'function f(){return; return 1} return f'); +assertAsmTypeFail(USE_ASM + 'function f(){if (0) return; return 1} return f'); assertEq(asmLink(asmCompile(USE_ASM + 'function f(x){x=x|0} return f'))(42), undefined); assertEq(asmLink(asmCompile(USE_ASM + 'function f(x){x=x|0; return x|0} return f'))(42), 42); assertEq(asmLink(asmCompile(USE_ASM + 'function f(x){x=x|0; return x|0;;;} return f'))(42), 42); diff --git a/js/src/jit-test/tests/asm.js/testControlFlow.js b/js/src/jit-test/tests/asm.js/testControlFlow.js index 112b82ed64..ac7f1a9c9c 100644 --- a/js/src/jit-test/tests/asm.js/testControlFlow.js +++ b/js/src/jit-test/tests/asm.js/testControlFlow.js @@ -12,7 +12,7 @@ assertEq(asmLink(asmCompile(USE_ASM + "function f(i) { i=i|0; if ((i|0) == 0) i assertAsmTypeFail(USE_ASM + "function f(i) { i=i|0; if (i) return 0; } return f"); assertAsmTypeFail(USE_ASM + "function f(i) { i=i|0; if (i) return 0; else return 1 } return f"); assertAsmTypeFail(USE_ASM + "function f(i) { i=i|0; if (i) return 0; return 1.0 } return f"); -assertAsmTypeFail(USE_ASM + "function f() { return 0; 1 } return f"); +assertAsmTypeFail(USE_ASM + "function f() { if (0) return 0; 1 } return f"); assertEq(asmLink(asmCompile(USE_ASM + "function f() { while (0) {} return 0} return f"))(), 0); assertEq(asmLink(asmCompile(USE_ASM + "function f() { for (;0;) {} return 0} return f"))(), 0); @@ -178,11 +178,15 @@ assertEq(f(2), 13); assertEq(f(3), 12); assertEq(asmLink(asmCompile(USE_ASM + "function f() { var i=8,sum=0; a:for(; (i|0)<20; i=(i+1)|0) { switch(i&3) { case 0:case 1:sum=(sum+i)|0;break;case 2:sum=(sum+100)|0;continue;default:break a} sum=(sum+10)|0; } sum=(sum+1000)|0; return sum|0 } return f"))(), 1137); +assertEq(asmLink(asmCompile(USE_ASM + "function f() { a: do{break a;}while(0); a: do{break a;}while(0); return 42 } return f"))(), 42); assertEq(asmLink(asmCompile('g', USE_ASM + "function f() { g:{ return 42 } return 13 } return f"), null)(), 42); +assertEq(asmLink(asmCompile(USE_ASM + "function f(x) {x=x|0;switch (x|0) {case 31:return 13;case 9: {x = 3;if ((x|0) <= 0) {return -1;}}}return 1;} return f"))(31), 13); var imp = { ffi:function() { throw "Wrong" } }; assertEq(asmLink(asmCompile('glob','imp', USE_ASM + "var ffi=imp.ffi; function f() { var i=0; return (i+1)|0; return ffi(i|0)|0 } return f"), null, imp)(), 1); +assertEq(asmLink(asmCompile(USE_ASM + 'function f() {var k = 1;if (1) {for(k = 1; k|0; k=k-1|0) {}} return 1}; return f'))(), 1); + // Ternaries conditionals // // Basic ternaries @@ -211,6 +215,14 @@ assertEq(f(1), 0); assertEq(f(0), 0); assertEq(guard.called(), false); +var f = asmLink(asmCompile('glob', 'ffi', USE_ASM + "var func=ffi.func; function f(x,y) { x=x|0;y=y|0; var a=2;if(x?func()|0:y)a=1;else a=0; return a|0 } return f"), this, {func: guard.call}); +assertEq(f(0,1), 1); +assertEq(guard.called(), false); + +var f = asmLink(asmCompile('glob', 'ffi', USE_ASM + "var func=ffi.func; function f(x,y) { x=x|0;y=y|0; var a=2;if(x?y:func()|0)a=1;else a=0; return a|0 } return f"), this, {func: guard.call}); +assertEq(f(1,0), 0); +assertEq(guard.called(), false); + var f = asmLink(asmCompile(USE_ASM + "function f(x,y) { x=x|0;y=y|0; var a=2;if(x?0:y)a=1;else a=0; return a|0 } return f")); assertEq(f(1,1), 0); assertEq(f(1,0), 0); diff --git a/js/src/jit-test/tests/asm.js/testExpressions.js b/js/src/jit-test/tests/asm.js/testExpressions.js index 5afda65d99..1910329c86 100644 --- a/js/src/jit-test/tests/asm.js/testExpressions.js +++ b/js/src/jit-test/tests/asm.js/testExpressions.js @@ -115,12 +115,35 @@ assertEq(f(0, INT32_MIN), 1); assertEq(f(UINT32_MAX, 0), 0); assertEq(f(0, UINT32_MAX), 1); -assertEq(asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)==(j|0); return k|0 } return f"))(1,2), 0); -assertEq(asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)!=(j|0); return k|0 } return f"))(1,2), 1); -assertEq(asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)<(j|0); return k|0 } return f"))(1,2), 1); -assertEq(asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)>(j|0); return k|0 } return f"))(1,2), 0); -assertEq(asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)<=(j|0); return k|0 } return f"))(1,2), 1); -assertEq(asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)>=(j|0); return k|0 } return f"))(1,2), 0); +var f = asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)==(j|0); return k|0 } return f")); +assertEq(f(1,2), 0); +assertEq(f(1,1), 1); +assertEq(f(2,1), 0); + +var f = asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)!=(j|0); return k|0 } return f")); +assertEq(f(1,2), 1); +assertEq(f(1,1), 0); +assertEq(f(2,1), 1); + +var f = asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)<(j|0); return k|0 } return f")); +assertEq(f(1,2), 1); +assertEq(f(1,1), 0); +assertEq(f(1,0), 0); + +var f = asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)>(j|0); return k|0 } return f")); +assertEq(f(1,2), 0); +assertEq(f(1,1), 0); +assertEq(f(1,0), 1); + +var f = asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)<=(j|0); return k|0 } return f")); +assertEq(f(1,2), 1); +assertEq(f(1,1), 1); +assertEq(f(1,0), 0); + +var f = asmLink(asmCompile(USE_ASM + "function f(i,j) { i=i|0;j=j|0; var k=0; k=(i|0)>=(j|0); return k|0 } return f")); +assertEq(f(1,2), 0); +assertEq(f(1,1), 1); +assertEq(f(1,0), 1); assertEq(asmLink(asmCompile(USE_ASM + "const I=2; function f(i) { i=i|0; var k=0; k=(i|0)>>0)>>0) } return f'); -assertAsmTypeFail('glob', 'imp', USE_ASM + 'var inc=imp.inc; function f() { return inc(); return } return f'); +assertAsmTypeFail('glob', 'imp', USE_ASM + 'var inc=imp.inc; function f() { if (0) return inc(); return } return f'); assertAsmTypeFail('glob', 'imp', USE_ASM + 'var inc=imp.inc; function f() { inc(inc()) } return f'); assertAsmTypeFail('glob', 'imp', USE_ASM + 'var inc=imp.inc; function f() { g(inc()) } function g() {} return f'); assertAsmTypeFail('glob', 'imp', USE_ASM + 'var inc=imp.inc; function f() { inc()|inc() } return f'); diff --git a/js/src/jit-test/tests/asm.js/testFloat32.js b/js/src/jit-test/tests/asm.js/testFloat32.js index 77f101309a..7b18d57404 100644 --- a/js/src/jit-test/tests/asm.js/testFloat32.js +++ b/js/src/jit-test/tests/asm.js/testFloat32.js @@ -63,6 +63,8 @@ assertEq(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + TO_FLOAT32 + HEAP32 assertEq(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + TO_FLOAT32 + HEAP32 + HEAP64 + "function f() { f64[0] = 1.5; return toF(f64[0]); } return f"), this, null, heap)(), 1.5); assertEq(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + TO_FLOAT32 + HEAP32 + HEAP64 + "function f() { f32[0] = toF(42); f64[0] = f32[0]; return +f64[0]; } return f"), this, null, heap)(), 42); +assertEq(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + TO_FLOAT32 + HEAP32 + "function f() { f32[0] = toF(-1.4013e-45) / toF(42.); } return f"), this, null, heap)(), undefined); + // Coercions // -> from Float32 assertAsmTypeFail('glob', USE_ASM + TO_FLOAT32 + "function f() { var n = 0; n = toF(4.5) | 0; } return f"); diff --git a/js/src/jit-test/tests/asm.js/testResize.js b/js/src/jit-test/tests/asm.js/testResize.js index 9487c76366..4e0e2d9bf9 100644 --- a/js/src/jit-test/tests/asm.js/testResize.js +++ b/js/src/jit-test/tests/asm.js/testResize.js @@ -137,7 +137,7 @@ assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i8=new I8(b2); b=b2; return } function f() { return 42 } return f'); assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i8=new I8(b2); b=b2; return 1 } function f() { return 42 } return f'); assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i8=new I8(b2); b=b2; return false } function f() { return 42 } return f'); -assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i8=new I8(b2); b=b2; return true; 1 } function f() { return 42 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i8=new I8(b2); b=b2; if (0) return true; 1 } function f() { return 42 } return f'); assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return f'); assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i32=new I32(b2); b=b2; return true } function f() { return 42 } return f'); assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i32=new I32(b2); i8=new I8(b2); b=b2; return true } function f() { return 42 } return f'); diff --git a/js/src/jit-test/tests/asm.js/testSIMD-load-store.js b/js/src/jit-test/tests/asm.js/testSIMD-load-store.js index 81c3f89aee..cccf5771c2 100644 --- a/js/src/jit-test/tests/asm.js/testSIMD-load-store.js +++ b/js/src/jit-test/tests/asm.js/testSIMD-load-store.js @@ -280,45 +280,45 @@ function MakeCodeFor(typeName) { var type = glob.SIMD.${typeName}; var c = type.check; - var lx = type.loadX; - var lxy = type.loadXY; - var lxyz = type.loadXYZ; + var l1 = type.load1; + var l2 = type.load2; + var l3 = type.load3; - var sx = type.storeX; - var sxy = type.storeXY; - var sxyz = type.storeXYZ; + var s1 = type.store1; + var s2 = type.store2; + var s3 = type.store3; var u8 = new glob.Uint8Array(heap); - function loadX(i) { i=i|0; return lx(u8, i); } - function loadXY(i) { i=i|0; return lxy(u8, i); } - function loadXYZ(i) { i=i|0; return lxyz(u8, i); } + function load1(i) { i=i|0; return l1(u8, i); } + function load2(i) { i=i|0; return l2(u8, i); } + function load3(i) { i=i|0; return l3(u8, i); } - function loadCstX() { return lx(u8, 41 << 2); } - function loadCstXY() { return lxy(u8, 41 << 2); } - function loadCstXYZ() { return lxyz(u8, 41 << 2); } + function loadCst1() { return l1(u8, 41 << 2); } + function loadCst2() { return l2(u8, 41 << 2); } + function loadCst3() { return l3(u8, 41 << 2); } - function storeX(i, x) { i=i|0; x=c(x); return sx(u8, i, x); } - function storeXY(i, x) { i=i|0; x=c(x); return sxy(u8, i, x); } - function storeXYZ(i, x) { i=i|0; x=c(x); return sxyz(u8, i, x); } + function store1(i, x) { i=i|0; x=c(x); return s1(u8, i, x); } + function store2(i, x) { i=i|0; x=c(x); return s2(u8, i, x); } + function store3(i, x) { i=i|0; x=c(x); return s3(u8, i, x); } - function storeCstX(x) { x=c(x); return sx(u8, 41 << 2, x); } - function storeCstXY(x) { x=c(x); return sxy(u8, 41 << 2, x); } - function storeCstXYZ(x) { x=c(x); return sxyz(u8, 41 << 2, x); } + function storeCst1(x) { x=c(x); return s1(u8, 41 << 2, x); } + function storeCst2(x) { x=c(x); return s2(u8, 41 << 2, x); } + function storeCst3(x) { x=c(x); return s3(u8, 41 << 2, x); } return { - loadX: loadX, - loadXY: loadXY, - loadXYZ: loadXYZ, - loadCstX: loadCstX, - loadCstXY: loadCstXY, - loadCstXYZ: loadCstXYZ, - storeX: storeX, - storeXY: storeXY, - storeXYZ: storeXYZ, - storeCstX: storeCstX, - storeCstXY: storeCstXY, - storeCstXYZ: storeCstXYZ, + load1: load1, + load2: load2, + load3: load3, + loadCst1: loadCst1, + loadCst2: loadCst2, + loadCst3: loadCst3, + store1: store1, + store2: store2, + store3: store3, + storeCst1: storeCst1, + storeCst2: storeCst2, + storeCst3: storeCst3, } `; } @@ -336,45 +336,45 @@ function TestPartialLoads(m, typedArray, x, y, z, w) { // Test correct loads var i = 0, j = 0; // i in elems, j in bytes - assertEqX4(m.loadX(j), [x(i), 0, 0, 0]); - assertEqX4(m.loadXY(j), [x(i), y(i), 0, 0]); - assertEqX4(m.loadXYZ(j), [x(i), y(i), z(i), 0]); + assertEqX4(m.load1(j), [x(i), 0, 0, 0]); + assertEqX4(m.load2(j), [x(i), y(i), 0, 0]); + assertEqX4(m.load3(j), [x(i), y(i), z(i), 0]); j += 4; - assertEqX4(m.loadX(j), [y(i), 0, 0, 0]); - assertEqX4(m.loadXY(j), [y(i), z(i), 0, 0]); - assertEqX4(m.loadXYZ(j), [y(i), z(i), w(i), 0]); + assertEqX4(m.load1(j), [y(i), 0, 0, 0]); + assertEqX4(m.load2(j), [y(i), z(i), 0, 0]); + assertEqX4(m.load3(j), [y(i), z(i), w(i), 0]); j += 4; - assertEqX4(m.loadX(j), [z(i), 0, 0, 0]); - assertEqX4(m.loadXY(j), [z(i), w(i), 0, 0]); - assertEqX4(m.loadXYZ(j), [z(i), w(i), x(i+4), 0]); + assertEqX4(m.load1(j), [z(i), 0, 0, 0]); + assertEqX4(m.load2(j), [z(i), w(i), 0, 0]); + assertEqX4(m.load3(j), [z(i), w(i), x(i+4), 0]); j += 4; - assertEqX4(m.loadX(j), [w(i), 0, 0, 0]); - assertEqX4(m.loadXY(j), [w(i), x(i+4), 0, 0]); - assertEqX4(m.loadXYZ(j), [w(i), x(i+4), y(i+4), 0]); + assertEqX4(m.load1(j), [w(i), 0, 0, 0]); + assertEqX4(m.load2(j), [w(i), x(i+4), 0, 0]); + assertEqX4(m.load3(j), [w(i), x(i+4), y(i+4), 0]); j += 4; i += 4; - assertEqX4(m.loadX(j), [x(i), 0, 0, 0]); - assertEqX4(m.loadXY(j), [x(i), y(i), 0, 0]); - assertEqX4(m.loadXYZ(j), [x(i), y(i), z(i), 0]); + assertEqX4(m.load1(j), [x(i), 0, 0, 0]); + assertEqX4(m.load2(j), [x(i), y(i), 0, 0]); + assertEqX4(m.load3(j), [x(i), y(i), z(i), 0]); // Test loads with constant indexes (41) - assertEqX4(m.loadCstX(), [y(40), 0, 0, 0]); - assertEqX4(m.loadCstXY(), [y(40), z(40), 0, 0]); - assertEqX4(m.loadCstXYZ(), [y(40), z(40), w(40), 0]); + assertEqX4(m.loadCst1(), [y(40), 0, 0, 0]); + assertEqX4(m.loadCst2(), [y(40), z(40), 0, 0]); + assertEqX4(m.loadCst3(), [y(40), z(40), w(40), 0]); // Test limit and OOB accesses - assertEqX4(m.loadX((SIZE - 1) << 2), [w(SIZE - 4), 0, 0, 0]); - assertThrowsInstanceOf(() => m.loadX(((SIZE - 1) << 2) + 1), RangeError); + assertEqX4(m.load1((SIZE - 1) << 2), [w(SIZE - 4), 0, 0, 0]); + assertThrowsInstanceOf(() => m.load1(((SIZE - 1) << 2) + 1), RangeError); - assertEqX4(m.loadXY((SIZE - 2) << 2), [z(SIZE - 4), w(SIZE - 4), 0, 0]); - assertThrowsInstanceOf(() => m.loadXY(((SIZE - 2) << 2) + 1), RangeError); + assertEqX4(m.load2((SIZE - 2) << 2), [z(SIZE - 4), w(SIZE - 4), 0, 0]); + assertThrowsInstanceOf(() => m.load2(((SIZE - 2) << 2) + 1), RangeError); - assertEqX4(m.loadXYZ((SIZE - 3) << 2), [y(SIZE - 4), z(SIZE - 4), w(SIZE - 4), 0]); - assertThrowsInstanceOf(() => m.loadXYZ(((SIZE - 3) << 2) + 1), RangeError); + assertEqX4(m.load3((SIZE - 3) << 2), [y(SIZE - 4), z(SIZE - 4), w(SIZE - 4), 0]); + assertThrowsInstanceOf(() => m.load3(((SIZE - 3) << 2) + 1), RangeError); } // Partial stores @@ -390,16 +390,16 @@ function TestPartialStores(m, typedArray, typeName, x, y, z, w) { assertEq(typedArray[i], i + 1); } - function TestStoreX(i) { - m.storeX(i, val); + function TestStore1(i) { + m.store1(i, val); CheckNotModified(0, i >> 2); assertEq(typedArray[i >> 2], x); CheckNotModified((i >> 2) + 1, SIZE); typedArray[i >> 2] = (i >> 2) + 1; } - function TestStoreXY(i) { - m.storeXY(i, val); + function TestStore2(i) { + m.store2(i, val); CheckNotModified(0, i >> 2); assertEq(typedArray[i >> 2], x); assertEq(typedArray[(i >> 2) + 1], y); @@ -408,8 +408,8 @@ function TestPartialStores(m, typedArray, typeName, x, y, z, w) { typedArray[(i >> 2) + 1] = (i >> 2) + 2; } - function TestStoreXYZ(i) { - m.storeXYZ(i, val); + function TestStore3(i) { + m.store3(i, val); CheckNotModified(0, i >> 2); assertEq(typedArray[i >> 2], x); assertEq(typedArray[(i >> 2) + 1], y); @@ -427,48 +427,48 @@ function TestPartialStores(m, typedArray, typeName, x, y, z, w) { Reset(); - TestStoreX(0); - TestStoreX(1 << 2); - TestStoreX(2 << 2); - TestStoreX(3 << 2); - TestStoreX(1337 << 2); + TestStore1(0); + TestStore1(1 << 2); + TestStore1(2 << 2); + TestStore1(3 << 2); + TestStore1(1337 << 2); var i = (SIZE - 1) << 2; - TestStoreX(i); - TestOOBStore(() => m.storeX(i + 1, val)); - TestOOBStore(() => m.storeX(-1, val)); + TestStore1(i); + TestOOBStore(() => m.store1(i + 1, val)); + TestOOBStore(() => m.store1(-1, val)); - TestStoreXY(0); - TestStoreXY(1 << 2); - TestStoreXY(2 << 2); - TestStoreXY(3 << 2); - TestStoreXY(1337 << 2); + TestStore2(0); + TestStore2(1 << 2); + TestStore2(2 << 2); + TestStore2(3 << 2); + TestStore2(1337 << 2); var i = (SIZE - 2) << 2; - TestStoreXY(i); - TestOOBStore(() => m.storeXY(i + 1, val)); - TestOOBStore(() => m.storeXY(-1, val)); + TestStore2(i); + TestOOBStore(() => m.store2(i + 1, val)); + TestOOBStore(() => m.store2(-1, val)); - TestStoreXYZ(0); - TestStoreXYZ(1 << 2); - TestStoreXYZ(2 << 2); - TestStoreXYZ(3 << 2); - TestStoreXYZ(1337 << 2); + TestStore3(0); + TestStore3(1 << 2); + TestStore3(2 << 2); + TestStore3(3 << 2); + TestStore3(1337 << 2); var i = (SIZE - 3) << 2; - TestStoreXYZ(i); - TestOOBStore(() => m.storeXYZ(i + 1, val)); - TestOOBStore(() => m.storeXYZ(-1, val)); - TestOOBStore(() => m.storeXYZ(-9, val)); + TestStore3(i); + TestOOBStore(() => m.store3(i + 1, val)); + TestOOBStore(() => m.store3(-1, val)); + TestOOBStore(() => m.store3(-9, val)); // Constant indexes (41) - m.storeCstX(val); + m.storeCst1(val); CheckNotModified(0, 41); assertEq(typedArray[41], x); CheckNotModified(42, SIZE); typedArray[41] = 42; - m.storeCstXY(val); + m.storeCst2(val); CheckNotModified(0, 41); assertEq(typedArray[41], x); assertEq(typedArray[42], y); @@ -476,7 +476,7 @@ function TestPartialStores(m, typedArray, typeName, x, y, z, w) { typedArray[41] = 42; typedArray[42] = 43; - m.storeCstXYZ(val); + m.storeCst3(val); CheckNotModified(0, 41); assertEq(typedArray[41], x); assertEq(typedArray[42], y); diff --git a/js/src/jit-test/tests/asm.js/testZOOB.js b/js/src/jit-test/tests/asm.js/testZOOB.js new file mode 100644 index 0000000000..870e0fc6dc --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testZOOB.js @@ -0,0 +1,244 @@ +// |jit-test| test-also-noasmjs +load(libdir + "asm.js"); +load(libdir + "asserts.js"); + +setIonCheckGraphCoherency(false); +setCachingEnabled(false); + +var ab = new ArrayBuffer(BUF_MIN); + +// Compute a set of interesting indices. +indices = [0] +for (var i of [4,1024,BUF_MIN,Math.pow(2,30),Math.pow(2,31),Math.pow(2,32),Math.pow(2,33)]) { + for (var j of [-2,-1,0,1,2]) { + for (var k of [1,-1]) + indices.push((i+j)*k); + } +} + +function testInt(ctor, shift, scale, disp) { + var arr = new ctor(ab); + + var c = asmCompile('glob', 'imp', 'b', + USE_ASM + + 'var arr=new glob.' + ctor.name + '(b); ' + + 'function load(i) {i=i|0; return arr[((i<<' + scale + ')+' + disp + ')>>' + shift + ']|0 } ' + + 'function store(i,j) {i=i|0;j=j|0; arr[((i<<' + scale + ')+' + disp + ')>>' + shift + '] = j } ' + + 'function storeZero(i) {i=i|0; arr[((i<<' + scale + ')+' + disp + ')>>' + shift + '] = 0 } ' + + 'function storeNegOne(i) {i=i|0; arr[((i<<' + scale + ')+' + disp + ')>>' + shift + '] = -1 } ' + + 'return { load: load, store: store, storeZero: storeZero, storeNegOne: storeNegOne }'); + var f = asmLink(c, this, null, ab); + + var v = arr[0]; + arr[0] = -1; + var negOne = arr[0]|0; + arr[0] = v; + + for (var i of indices) { + var index = ((i<>shift; + v = arr[index]|0; + + // Loads + assertEq(f.load(i), v); + + // Stores of immediates + arr[index] = 1; + f.storeZero(i); + assertEq(arr[index]|0, 0); + f.storeNegOne(i); + assertEq(arr[index]|0, index>>>0 < arr.length ? negOne : 0); + + // Stores + arr[index] = ~v; + f.store(i, v); + assertEq(arr[index]|0, v); + } +} + +function testFloat(ctor, shift, scale, disp, coercion) { + var arr = new ctor(ab); + + var c = asmCompile('glob', 'imp', 'b', + USE_ASM + + 'var arr=new glob.' + ctor.name + '(b); ' + + 'var toF = glob.Math.fround; ' + + 'function load(i) {i=i|0; return ' + coercion + '(arr[((i<<' + scale + ')+' + disp + ')>>' + shift + ']) } ' + + 'function store(i,j) {i=i|0;j=+j; arr[((i<<' + scale + ')+' + disp + ')>>' + shift + '] = j } ' + + 'return { load: load, store: store }'); + var f = asmLink(c, this, null, ab); + + for (var i of indices) { + var index = ((i<>shift; + var v = +arr[index]; + + // Loads + assertEq(f.load(i), v); + + // Stores + arr[index] = ~v; + f.store(i, v); + assertEq(+arr[index], v); + } +} + +function testFloat32(ctor, shift, scale, disp) { + testFloat(ctor, shift, scale, disp, "toF"); +} +function testFloat64(ctor, shift, scale, disp) { + testFloat(ctor, shift, scale, disp, "+"); +} + +function assertEqX4(observed, expected) { + assertEq(observed.x, expected.x); + assertEq(observed.y, expected.y); + assertEq(observed.z, expected.z); + assertEq(observed.w, expected.w); +} + +function testSimdX4(ctor, shift, scale, disp, simdName, simdCtor) { + var arr = new ctor(ab); + + var c = asmCompile('glob', 'imp', 'b', + USE_ASM + + 'var arr=new glob.' + ctor.name + '(b); ' + + 'var SIMD_' + simdName + ' = glob.SIMD.' + simdName + '; ' + + 'var SIMD_' + simdName + '_check = SIMD_' + simdName + '.check; ' + + 'var SIMD_' + simdName + '_load = SIMD_' + simdName + '.load; ' + + 'var SIMD_' + simdName + '_load3 = SIMD_' + simdName + '.load3; ' + + 'var SIMD_' + simdName + '_load2 = SIMD_' + simdName + '.load2; ' + + 'var SIMD_' + simdName + '_load1 = SIMD_' + simdName + '.load1; ' + + 'var SIMD_' + simdName + '_store = SIMD_' + simdName + '.store; ' + + 'var SIMD_' + simdName + '_store3 = SIMD_' + simdName + '.store3; ' + + 'var SIMD_' + simdName + '_store2 = SIMD_' + simdName + '.store2; ' + + 'var SIMD_' + simdName + '_store1 = SIMD_' + simdName + '.store1; ' + + 'function load(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' + + 'function load3(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load3(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' + + 'function load2(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load2(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' + + 'function load1(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load1(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' + + 'function store(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' + + 'function store3(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store3(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' + + 'function store2(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store2(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' + + 'function store1(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store1(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' + + 'return { load: load, load3: load3, load2: load2, load1: load1, store: store, store3: store3, store2 : store2, store1 : store1 }'); + var f = asmLink(c, this, null, ab); + + for (var i of indices) { + var index = ((i<>shift; + + var v, v3, v2, v1; + var t = false, t3 = false, t2 = false, t1 = false; + try { v = simdCtor.load(arr, index); } + catch (e) { + assertEq(e instanceof RangeError, true); + t = true; + } + try { v3 = simdCtor.load3(arr, index); } + catch (e) { + assertEq(e instanceof RangeError, true); + t3 = true; + } + try { v2 = simdCtor.load2(arr, index); } + catch (e) { + assertEq(e instanceof RangeError, true); + t2 = true; + } + try { v1 = simdCtor.load1(arr, index); } + catch (e) { + assertEq(e instanceof RangeError, true); + t1 = true; + } + + // Loads + var l, l3, l2, l1; + var r = false, r3 = false, r2 = false, r1 = false; + try { l = f.load(i); } + catch (e) { + assertEq(e instanceof RangeError, true); + r = true; + } + try { l3 = f.load3(i); } + catch (e) { + assertEq(e instanceof RangeError, true); + r3 = true; + } + try { l2 = f.load2(i); } + catch (e) { + assertEq(e instanceof RangeError, true); + r2 = true; + } + try { l1 = f.load1(i); } + catch (e) { + assertEq(e instanceof RangeError, true); + r1 = true; + } + assertEq(t, r); + assertEq(t3, r3); + assertEq(t2, r2); + assertEq(t1, r1); + if (!t) assertEqX4(v, l); + if (!t3) assertEqX4(v3, l3); + if (!t2) assertEqX4(v2, l2); + if (!t1) assertEqX4(v1, l1); + + // Stores + if (!t) { + simdCtor.store(arr, index, simdCtor.not(v)); + f.store(i, v); + assertEqX4(simdCtor.load(arr, index), v); + } else + assertThrowsInstanceOf(() => f.store(i, simdCtor()), RangeError); + if (!t3) { + simdCtor.store3(arr, index, simdCtor.not(v3)); + f.store3(i, v3); + assertEqX4(simdCtor.load3(arr, index), v3); + } else + assertThrowsInstanceOf(() => f.store3(i, simdCtor()), RangeError); + if (!t2) { + simdCtor.store2(arr, index, simdCtor.not(v2)); + f.store2(i, v2); + assertEqX4(simdCtor.load2(arr, index), v2); + } else + assertThrowsInstanceOf(() => f.store2(i, simdCtor()), RangeError); + if (!t1) { + simdCtor.store1(arr, index, simdCtor.not(v1)); + f.store1(i, v1); + assertEqX4(simdCtor.load1(arr, index), v1); + } else + assertThrowsInstanceOf(() => f.store1(i, simdCtor()), RangeError); + } +} + +function testFloat32x4(ctor, shift, scale, disp) { + testSimdX4(ctor, shift, scale, disp, 'float32x4', SIMD.float32x4); +} +function testInt32x4(ctor, shift, scale, disp) { + testSimdX4(ctor, shift, scale, disp, 'int32x4', SIMD.int32x4); +} + +function test(tester, ctor, shift) { + var arr = new ctor(ab); + for (var i = 0; i < arr.length; i++) + arr[i] = Math.imul(i, Math.imul((i & 1), 2) - 1); + for (scale of [0,1,2,3]) { + for (disp of [0,1,2,8,Math.pow(2,31)-1,Math.pow(2,31),Math.pow(2,32)-1]) + tester(ctor, shift, scale, disp); + } + for (var i = 0; i < arr.length; i++) { + var v = arr[i]; + arr[i] = Math.imul(i, Math.imul((i & 1), 2) - 1); + assertEq(arr[i], v); + } +} + +test(testInt, Int8Array, 0); +test(testInt, Uint8Array, 0); +test(testInt, Int16Array, 1); +test(testInt, Uint16Array, 1); +test(testInt, Int32Array, 2); +test(testInt, Uint32Array, 2); +test(testFloat32, Float32Array, 2); +test(testFloat64, Float64Array, 3); +if (typeof SIMD !== 'undefined' && isSimdAvailable()) { + test(testInt32x4, Uint8Array, 0); + test(testFloat32x4, Uint8Array, 0); +} diff --git a/js/src/jit-test/tests/auto-regress/bug590772.js b/js/src/jit-test/tests/auto-regress/bug590772.js index 2464863ae7..a5e81091d8 100644 --- a/js/src/jit-test/tests/auto-regress/bug590772.js +++ b/js/src/jit-test/tests/auto-regress/bug590772.js @@ -1,4 +1,4 @@ // Binary: cache/js-dbg-32-f561f17e6c27-linux // Flags: // -Reflect.parse("for (var x = 3 in []) { }") +Reflect.parse("for (var x in []) { }") diff --git a/js/src/jit-test/tests/auto-regress/bug596817.js b/js/src/jit-test/tests/auto-regress/bug596817.js index b6170d3698..247bb0c2ce 100644 --- a/js/src/jit-test/tests/auto-regress/bug596817.js +++ b/js/src/jit-test/tests/auto-regress/bug596817.js @@ -3,4 +3,10 @@ // load(libdir + 'asserts.js'); // value is not iterable -assertThrowsInstanceOf(function(){for(var[x]=x>>x in[[]<[]]){[]}}, TypeError); +(function() { + for (var [x] in [[] < []]) + { + // Just a useless expression. + []; + } +})(); diff --git a/js/src/jit-test/tests/basic/semicolon-less-return.js b/js/src/jit-test/tests/basic/statement-after-return.js similarity index 75% rename from js/src/jit-test/tests/basic/semicolon-less-return.js rename to js/src/jit-test/tests/basic/statement-after-return.js index 934f0a6d0c..bfb9bed6ec 100644 --- a/js/src/jit-test/tests/basic/semicolon-less-return.js +++ b/js/src/jit-test/tests/basic/statement-after-return.js @@ -1,5 +1,6 @@ -// Warning should be shown for expression-like statement after semicolon-less -// return (bug 1005110). +// Warning should be shown for unreachable statement after return (bug 1151931). + +load(libdir + "class.js"); if (options().indexOf("werror") == -1) options("werror"); @@ -10,7 +11,7 @@ function testWarn(code, lineNumber, columnNumber) { eval(code); } catch (e) { caught = true; - assertEq(e.message, "unreachable expression after semicolon-less return statement", code); + assertEq(e.constructor, SyntaxError); assertEq(e.lineNumber, lineNumber); assertEq(e.columnNumber, columnNumber); } @@ -21,7 +22,7 @@ function testWarn(code, lineNumber, columnNumber) { Reflect.parse(code); } catch (e) { caught = true; - assertEq(e.message, "unreachable expression after semicolon-less return statement", code); + assertEq(e.constructor, SyntaxError); } assertEq(caught, true, "warning should be caught for " + code); } @@ -44,8 +45,6 @@ function testPass(code) { assertEq(caught, false, "warning should not be caught for " + code); } -// not EOL - testPass(` function f() { return ( @@ -53,16 +52,8 @@ function f() { ); } `); -testPass(` -function f() { - return; - 1 + 2; -} -`); -// starts expression - -// TOK_INC +// unary expression testWarn(` function f() { var i = 0; @@ -70,8 +61,6 @@ function f() { ++i; } `, 5, 4); - -// TOK_DEC testWarn(` function f() { var i = 0; @@ -80,7 +69,7 @@ function f() { } `, 5, 4); -// TOK_LB +// array testWarn(` function f() { return @@ -88,7 +77,7 @@ function f() { } `, 4, 4); -// TOK_LC +// block (object) testWarn(` function f() { return @@ -107,7 +96,7 @@ function f() { } `, 4, 2); -// TOK_LP +// expression in paren testWarn(` function f() { return @@ -115,7 +104,7 @@ function f() { } `, 4, 4); -// TOK_NAME +// name testWarn(` function f() { return @@ -123,7 +112,7 @@ function f() { } `, 4, 4); -// TOK_NUMBER +// binary expression testWarn(` function f() { return @@ -137,7 +126,7 @@ function f() { } `, 4, 4); -// TOK_STRING +// string testWarn(` function f() { return @@ -157,15 +146,13 @@ function f() { } `, 4, 4); -// TOK_TEMPLATE_HEAD +// template string testWarn(` function f() { return \`foo\${1 + 2}\`; } `, 4, 4); - -// TOK_NO_SUBS_TEMPLATE testWarn(` function f() { return @@ -173,7 +160,7 @@ function f() { } `, 4, 4); -// TOK_REGEXP +// RegExp testWarn(` function f() { return @@ -181,15 +168,13 @@ function f() { } `, 4, 4); -// TOK_TRUE +// boolean testWarn(` function f() { return true; } `, 4, 4); - -// TOK_FALSE testWarn(` function f() { return @@ -197,7 +182,7 @@ function f() { } `, 4, 4); -// TOK_NULL +// null testWarn(` function f() { return @@ -205,7 +190,7 @@ function f() { } `, 4, 4); -// TOK_THIS +// this testWarn(` function f() { return @@ -213,7 +198,7 @@ function f() { } `, 4, 4); -// TOK_NEW +// new testWarn(` function f() { return @@ -221,7 +206,7 @@ function f() { } `, 4, 4); -// TOK_DELETE +// delete testWarn(` function f() { var a = {x: 10}; @@ -230,7 +215,7 @@ function f() { } `, 5, 4); -// TOK_YIELD +// yield testWarn(` function* f() { return @@ -238,39 +223,35 @@ function* f() { } `, 4, 4); -// TOK_CLASS -testWarn(` +// class +if (classesEnabled()) { + testWarn(` function f() { return class A { constructor() {} }; } `, 4, 4); +} -// TOK_ADD +// unary expression testWarn(` function f() { return +1; } `, 4, 4); - -// TOK_SUB testWarn(` function f() { return -1; } `, 4, 4); - -// TOK_NOT testWarn(` function f() { return !1; } `, 4, 4); - -// TOK_BITNOT testWarn(` function f() { return @@ -278,14 +259,12 @@ function f() { } `, 4, 4); -// don't start expression - -// TOK_EOF +// eof testPass(` var f = new Function("return\\n"); `); -// TOK_SEMI +// empty statement testPass(` function f() { return @@ -293,7 +272,7 @@ function f() { } `); -// TOK_RC +// end of block testPass(` function f() { { @@ -302,7 +281,7 @@ function f() { } `); -// TOK_FUNCTION +// function (hosted) testPass(` function f() { g(); @@ -311,16 +290,16 @@ function f() { } `); -// TOK_IF -testPass(` +// if +testWarn(` function f() { return if (true) 1 + 2; } -`); +`, 4, 2); -// TOK_ELSE +// else testPass(` function f() { if (true) @@ -330,8 +309,8 @@ function f() { } `); -// TOK_SWITCH -testPass(` +// switch +testWarn(` function f() { return switch (1) { @@ -339,9 +318,32 @@ function f() { break; } } +`, 4, 2); + +// return in switch +testWarn(` +function f() { + switch (1) { + case 1: + return; + 1 + 2; + break; + } +} +`, 6, 6); + +// break in switch +testPass(` +function f() { + switch (1) { + case 1: + return; + break; + } +} `); -// TOK_CASE +// case testPass(` function f() { switch (1) { @@ -353,7 +355,7 @@ function f() { } `); -// TOK_DEFAULT +// default testPass(` function f() { switch (1) { @@ -365,14 +367,14 @@ function f() { } `); -// TOK_WHILE -testPass(` +// while +testWarn(` function f() { return while (false) 1 + 2; } -`); +`, 4, 2); testPass(` function f() { do @@ -381,27 +383,27 @@ function f() { } `); -// TOK_DO -testPass(` +// do +testWarn(` function f() { return do { 1 + 2; } while (false); } -`); +`, 4, 2); -// TOK_FOR -testPass(` +// for +testWarn(` function f() { return for (;;) { break; } } -`); +`, 4, 2); -// TOK_BREAK +// break in for testPass(` function f() { for (;;) { @@ -409,19 +411,19 @@ function f() { break; } } -`); +`, 5, 4); -// TOK_CONTINUE -testPass(` +// continue +testWarn(` function f() { for (;;) { return continue; } } -`); +`, 5, 4); -// TOK_VAR +// var (hosted) testPass(` function f() { return @@ -429,43 +431,43 @@ function f() { } `); -// TOK_CONST -testPass(` +// const +testWarn(` function f() { return const a = 1; } -`); +`, 4, 2); -// TOK_WITH -testPass(` +// with +testWarn(` function f() { return with ({}) { 1; } } -`); +`, 4, 2); -// TOK_RETURN -testPass(` +// return +testWarn(` function f() { return return; } -`); +`, 4, 2); -// TOK_TRY -testPass(` +// try +testWarn(` function f() { return try { } catch (e) { } } -`); +`, 4, 2); -// TOK_THROW +// throw testPass(` function f() { return @@ -473,29 +475,37 @@ function f() { } `); -// TOK_DEBUGGER -testPass(` +// debugger +testWarn(` function f() { return debugger; } -`); +`, 4, 2); -// TOK_LET -testPass(` +// let +testWarn(` function f() { return let a = 1; } -`); +`, 4, 2); -// exceptional case +// skip hoisted -// It's not possible to distinguish between a label statement and an expression -// starts with identifier, by checking a token next to return. testWarn(` function f() { return - a: 1; + var a = 0; + (1 + 2); } -`, 4, 2); +`, 5, 2); + +testWarn(` +function f() { + return + function f() {} + var a = 0; + (1 + 2); +} +`, 6, 2); diff --git a/js/src/jit-test/tests/closures/bug540136.js b/js/src/jit-test/tests/closures/bug540136.js deleted file mode 100644 index 54b57713a7..0000000000 --- a/js/src/jit-test/tests/closures/bug540136.js +++ /dev/null @@ -1,17 +0,0 @@ -// |jit-test| error: TypeError - -(eval("\ - (function () {\ - for (var[x] = function(){} in \ - (function m(a) {\ - if (a < 1) {\ - x;\ - return\ - }\ - return m(a - 1) + m(a - 2)\ - })(7)\ - (eval(\"\"))\ - )\ - ([])\ - })\ -"))() diff --git a/js/src/jit-test/tests/closures/bug540348.js b/js/src/jit-test/tests/closures/bug540348.js deleted file mode 100644 index 6a0b6f0b35..0000000000 --- a/js/src/jit-test/tests/closures/bug540348.js +++ /dev/null @@ -1,3 +0,0 @@ -(function() { - for (var [e] = [] in (eval("for (b = 0; b < 6; ++b) gc()"))) {} -})() diff --git a/js/src/jit-test/tests/ctypes/AddressOfField.js b/js/src/jit-test/tests/ctypes/AddressOfField.js new file mode 100644 index 0000000000..52f4d1cd17 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/AddressOfField.js @@ -0,0 +1,12 @@ +load(libdir + 'asserts.js'); + +function test() { + let strcut = ctypes.StructType("a", [ { "x": ctypes.int32_t, } ])(); + for (let arg of [1, undefined, null, false, {}, [], Symbol("foo")]) { + assertThrowsInstanceOf(() => { struct.addressOfField(arg); }, + Error); + } +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-abi.js b/js/src/jit-test/tests/ctypes/argument-length-abi.js new file mode 100644 index 0000000000..a18a8059ed --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-abi.js @@ -0,0 +1,9 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.default_abi.toSource(1); }, + "ABI.prototype.toSource takes no arguments"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-array.js b/js/src/jit-test/tests/ctypes/argument-length-array.js new file mode 100644 index 0000000000..5afbcc46eb --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-array.js @@ -0,0 +1,15 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.ArrayType(); }, + "ArrayType takes one or two arguments"); + assertTypeErrorMessage(() => { ctypes.int32_t.array(10)(1, 2); }, + "size defined ArrayType constructor takes at most one argument"); + assertTypeErrorMessage(() => { ctypes.int32_t.array()(1, 2); }, + "size undefined ArrayType constructor takes one argument"); + assertTypeErrorMessage(() => { ctypes.int32_t.array(10)().addressOfElement(); }, + "ArrayType.prototype.addressOfElement takes one argument"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-cdata.js b/js/src/jit-test/tests/ctypes/argument-length-cdata.js new file mode 100644 index 0000000000..56a1497f48 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-cdata.js @@ -0,0 +1,15 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.int32_t(0).address(1); }, + "CData.prototype.address takes no arguments"); + assertTypeErrorMessage(() => { ctypes.char.array(10)().readString(1); }, + "CData.prototype.readString takes no arguments"); + assertTypeErrorMessage(() => { ctypes.char.array(10)().readStringReplaceMalformed(1); }, + "CData.prototype.readStringReplaceMalformed takes no arguments"); + assertTypeErrorMessage(() => { ctypes.int32_t(0).toSource(1); }, + "CData.prototype.toSource takes no arguments"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-ctypes.js b/js/src/jit-test/tests/ctypes/argument-length-ctypes.js new file mode 100644 index 0000000000..84b0b9ea54 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-ctypes.js @@ -0,0 +1,11 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.cast(); }, + "ctypes.cast takes two arguments"); + assertTypeErrorMessage(() => { ctypes.getRuntime(); }, + "ctypes.getRuntime takes one argument"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-finalizer.js b/js/src/jit-test/tests/ctypes/argument-length-finalizer.js new file mode 100644 index 0000000000..70e15bf8f0 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-finalizer.js @@ -0,0 +1,16 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.CDataFinalizer(1); }, + "CDataFinalizer constructor takes two arguments"); + + let fin = ctypes.CDataFinalizer(ctypes.int32_t(0), ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, [ctypes.int32_t]).ptr(x => x)); + assertTypeErrorMessage(() => { fin.forget(1); }, + "CDataFinalizer.prototype.forget takes no arguments"); + assertTypeErrorMessage(() => { fin.dispose(1); }, + "CDataFinalizer.prototype.dispose takes no arguments"); + fin.forget(); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-function.js b/js/src/jit-test/tests/ctypes/argument-length-function.js new file mode 100644 index 0000000000..2d26596620 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-function.js @@ -0,0 +1,11 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.FunctionType(); }, + "FunctionType takes two or three arguments"); + assertTypeErrorMessage(() => { ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, []).ptr({}, 1); }, + "FunctionType constructor takes one argument"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-int64.js b/js/src/jit-test/tests/ctypes/argument-length-int64.js new file mode 100644 index 0000000000..888713accf --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-int64.js @@ -0,0 +1,36 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.Int64(1).toString(1, 2); }, + "Int64.prototype.toString takes at most one argument"); + assertTypeErrorMessage(() => { ctypes.Int64(1).toSource(1); }, + "Int64.prototype.toSource takes no arguments"); + assertTypeErrorMessage(() => { ctypes.Int64(); }, + "Int64 constructor takes one argument"); + assertTypeErrorMessage(() => { ctypes.Int64.compare(); }, + "Int64.compare takes two arguments"); + assertTypeErrorMessage(() => { ctypes.Int64.lo(); }, + "Int64.lo takes one argument"); + assertTypeErrorMessage(() => { ctypes.Int64.hi(); }, + "Int64.hi takes one argument"); + assertTypeErrorMessage(() => { ctypes.Int64.join(); }, + "Int64.join takes two arguments"); + + assertTypeErrorMessage(() => { ctypes.UInt64(1).toString(1, 2); }, + "UInt64.prototype.toString takes at most one argument"); + assertTypeErrorMessage(() => { ctypes.UInt64(1).toSource(1); }, + "UInt64.prototype.toSource takes no arguments"); + assertTypeErrorMessage(() => { ctypes.UInt64(); }, + "UInt64 constructor takes one argument"); + assertTypeErrorMessage(() => { ctypes.UInt64.compare(); }, + "UInt64.compare takes two arguments"); + assertTypeErrorMessage(() => { ctypes.UInt64.lo(); }, + "UInt64.lo takes one argument"); + assertTypeErrorMessage(() => { ctypes.UInt64.hi(); }, + "UInt64.hi takes one argument"); + assertTypeErrorMessage(() => { ctypes.UInt64.join(); }, + "UInt64.join takes two arguments"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-pointer.js b/js/src/jit-test/tests/ctypes/argument-length-pointer.js new file mode 100644 index 0000000000..8ac404aaf7 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-pointer.js @@ -0,0 +1,11 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.PointerType(); }, + "PointerType takes one argument"); + assertTypeErrorMessage(() => { ctypes.int32_t.ptr(1, 2, 3, 4); }, + "PointerType constructor takes 0, 1, 2, or 3 arguments"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-primitive.js b/js/src/jit-test/tests/ctypes/argument-length-primitive.js new file mode 100644 index 0000000000..161ebd880c --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-primitive.js @@ -0,0 +1,11 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.int32_t(1, 2, 3); }, + "CType constructor takes at most one argument"); + assertTypeErrorMessage(() => { ctypes.int32_t.array(1, 2); }, + "CType.prototype.array takes at most one argument"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-length-struct.js b/js/src/jit-test/tests/ctypes/argument-length-struct.js new file mode 100644 index 0000000000..0e8efbb6ab --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-length-struct.js @@ -0,0 +1,17 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.StructType(); }, + "StructType takes one or two arguments"); + assertTypeErrorMessage(() => { ctypes.StructType("a").define(); }, + "StructType.prototype.define takes one argument"); + assertTypeErrorMessage(() => { ctypes.StructType("a", [])(1, 2, 3); }, + "StructType constructor takes at most one argument"); + assertTypeErrorMessage(() => { ctypes.StructType("a", [ {"x": ctypes.int32_t }, {"y": ctypes.int32_t }, {"z": ctypes.int32_t }])(1, 2); }, + "StructType constructor takes 0, 1, or 3 arguments"); + assertTypeErrorMessage(() => { ctypes.StructType("a", [ {"x": ctypes.int32_t } ])().addressOfField(); }, + "StructType.prototype.addressOfField takes one argument"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-type-array.js b/js/src/jit-test/tests/ctypes/argument-type-array.js new file mode 100644 index 0000000000..e78578d86b --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-type-array.js @@ -0,0 +1,17 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.int32_t.array({}); }, + "argument of CType.prototype.array must be a nonnegative integer"); + assertTypeErrorMessage(() => { ctypes.ArrayType(1); }, + "first argument of ArrayType must be a CType"); + assertTypeErrorMessage(() => { ctypes.ArrayType(ctypes.int32_t, {}); }, + "second argument of ArrayType must be a nonnegative integer"); + assertTypeErrorMessage(() => { ctypes.char.array()({}); }, + "argument of size undefined ArrayType constructor must be an array object or integer"); + assertTypeErrorMessage(() => { ctypes.char.array()(false); }, + "argument of size undefined ArrayType constructor must be an array object or integer"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-type-ctypes.js b/js/src/jit-test/tests/ctypes/argument-type-ctypes.js new file mode 100644 index 0000000000..168c8fb746 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-type-ctypes.js @@ -0,0 +1,13 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.cast(1, 2); }, + "first argument of ctypes.cast must be a CData"); + assertTypeErrorMessage(() => { ctypes.cast(ctypes.int32_t(0), 2); }, + "second argument of ctypes.cast must be a CType"); + assertTypeErrorMessage(() => { ctypes.getRuntime(1); }, + "argument of ctypes.getRuntime must be a CType"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-type-function.js b/js/src/jit-test/tests/ctypes/argument-type-function.js new file mode 100644 index 0000000000..a341415f1d --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-type-function.js @@ -0,0 +1,9 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, 1); }, + "third argument of FunctionType must be an array"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-type-int64.js b/js/src/jit-test/tests/ctypes/argument-type-int64.js new file mode 100644 index 0000000000..e147b98a48 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-type-int64.js @@ -0,0 +1,28 @@ +load(libdir + 'asserts.js'); + +function test() { + assertRangeErrorMessage(() => { ctypes.Int64(0).toString("a"); }, + "argument of Int64.prototype.toString must be an integer at least 2 and no greater than 36"); + assertTypeErrorMessage(() => { ctypes.Int64.compare(1, 2); }, + "first argument of Int64.compare must be a Int64"); + assertTypeErrorMessage(() => { ctypes.Int64.compare(ctypes.Int64(0), 2); }, + "second argument of Int64.compare must be a Int64"); + assertTypeErrorMessage(() => { ctypes.Int64.lo(1); }, + "argument of Int64.lo must be a Int64"); + assertTypeErrorMessage(() => { ctypes.Int64.hi(1); }, + "argument of Int64.hi must be a Int64"); + + assertRangeErrorMessage(() => { ctypes.UInt64(0).toString("a"); }, + "argument of UInt64.prototype.toString must be an integer at least 2 and no greater than 36"); + assertTypeErrorMessage(() => { ctypes.UInt64.compare(1, 2); }, + "first argument of UInt64.compare must be a UInt64"); + assertTypeErrorMessage(() => { ctypes.UInt64.compare(ctypes.UInt64(0), 2); }, + "second argument of UInt64.compare must be a UInt64"); + assertTypeErrorMessage(() => { ctypes.UInt64.lo(1); }, + "argument of UInt64.lo must be a UInt64"); + assertTypeErrorMessage(() => { ctypes.UInt64.hi(1); }, + "argument of UInt64.hi must be a UInt64"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-type-pointer.js b/js/src/jit-test/tests/ctypes/argument-type-pointer.js new file mode 100644 index 0000000000..80f96ad905 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-type-pointer.js @@ -0,0 +1,9 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.PointerType({}); }, + "argument of PointerType must be a CType"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/argument-type-struct.js b/js/src/jit-test/tests/ctypes/argument-type-struct.js new file mode 100644 index 0000000000..703c0f059f --- /dev/null +++ b/js/src/jit-test/tests/ctypes/argument-type-struct.js @@ -0,0 +1,17 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.StructType(1); }, + "first argument of StructType must be a string"); + assertTypeErrorMessage(() => { ctypes.StructType("a", 1); }, + "second argument of StructType must be an array"); + assertTypeErrorMessage(() => { ctypes.StructType("a").define(1); }, + "argument of StructType.prototype.define must be an array"); + assertTypeErrorMessage(() => { ctypes.StructType("a").define({}); }, + "argument of StructType.prototype.define must be an array"); + assertTypeErrorMessage(() => { ctypes.StructType("a", [{x:ctypes.int32_t}])().addressOfField(1); }, + "argument of StructType.prototype.addressOfField must be a string"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-array.js b/js/src/jit-test/tests/ctypes/conversion-array.js new file mode 100644 index 0000000000..840c1a0feb --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-array.js @@ -0,0 +1,36 @@ +// Type conversion error should report its type. + +load(libdir + 'asserts.js'); + +function test() { + // constructor + assertTypeErrorMessage(() => { ctypes.int32_t.array()("foo"); }, + "can't convert the string \"foo\" to the type ctypes.int32_t.array()"); + assertTypeErrorMessage(() => { ctypes.int32_t.array(10)("foo"); }, + "can't convert the string \"foo\" to the type ctypes.int32_t.array(10)"); + assertTypeErrorMessage(() => { ctypes.char.array(2)("foo"); }, + "length of the string \"foo\" does not fit to the length of the type ctypes.char.array(2) (expected 2 or lower, got 3)"); + assertTypeErrorMessage(() => { ctypes.char16_t.array(2)("foo"); }, + "length of the string \"foo\" does not fit to the length of the type ctypes.char16_t.array(2) (expected 2 or lower, got 3)"); + assertTypeErrorMessage(() => { ctypes.int8_t.array(2)(new ArrayBuffer(8)); }, + "length of the array buffer ({}) does not match to the length of the type ctypes.int8_t.array(2) (expected 2, got 8)"); + assertTypeErrorMessage(() => { ctypes.int8_t.array(2)(new Int8Array(8)); }, + "length of the typed array ({0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0}) does not match to the length of the type ctypes.int8_t.array(2) (expected 2, got 8)"); + + // elem setter + assertTypeErrorMessage(() => { ctypes.int32_t.array(10)()[0] = "foo"; }, + "can't convert the string \"foo\" to element 0 of the type ctypes.int32_t.array(10)"); + assertTypeErrorMessage(() => { ctypes.int32_t.array(10)()[1] = "foo"; }, + "can't convert the string \"foo\" to element 1 of the type ctypes.int32_t.array(10)"); + + // value setter + assertTypeErrorMessage(() => { ctypes.int32_t.array(1)().value = ["foo"]; }, + "can't convert the string \"foo\" to element 0 of the type ctypes.int32_t.array(1)"); + assertTypeErrorMessage(() => { ctypes.int32_t.array(1)().value = [2, "foo"]; }, + "length of the array [2, \"foo\"] does not match to the length of the type ctypes.int32_t.array(1) (expected 1, got 2)"); + assertTypeErrorMessage(() => { ctypes.int32_t.array(2)().value = [2, "foo"]; }, + "can't convert the string \"foo\" to element 1 of the type ctypes.int32_t.array(2)"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-error.js b/js/src/jit-test/tests/ctypes/conversion-error.js new file mode 100644 index 0000000000..a6823fe5ce --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-error.js @@ -0,0 +1,14 @@ +load(libdir + 'asserts.js'); + +function test() { + let obj = { + toSource() { + throw 1; + } + }; + assertTypeErrorMessage(() => { ctypes.double().value = obj; }, + "can't convert <> to the type double"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-finalizer.js b/js/src/jit-test/tests/ctypes/conversion-finalizer.js new file mode 100644 index 0000000000..a04a40cfa2 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-finalizer.js @@ -0,0 +1,61 @@ +load(libdir + 'asserts.js'); + +function test() { + // non object + assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, "foo"); }, + "expected _a CData object_ of a function pointer type, got the string \"foo\""); + // non CData object + assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, ["foo"]); }, + "expected a _CData_ object of a function pointer type, got the array [\"foo\"]"); + + // a CData which is not a pointer + assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, ctypes.int32_t(0)); }, + "expected a CData object of a function _pointer_ type, got ctypes.int32_t(0)"); + // a pointer CData which is not a function + assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, ctypes.int32_t.ptr(0)); }, + "expected a CData object of a _function_ pointer type, got ctypes.int32_t.ptr(ctypes.UInt64(\"0x0\"))"); + + // null function + let func_type = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, + [ctypes.int32_t, ctypes.int32_t]).ptr; + let f0 = func_type(0); + assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, f0); }, + "expected a CData object of a _non-NULL_ function pointer type, got ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [ctypes.int32_t, ctypes.int32_t]).ptr(ctypes.UInt64(\"0x0\"))"); + + // a function with 2 arguments + let f1 = func_type(x => x); + assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, f1); }, + "expected a function accepting exactly one argument, got ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [ctypes.int32_t, ctypes.int32_t])"); + + // non CData in argument 1 + let func_type2 = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, + [ctypes.int32_t.ptr]).ptr; + let f2 = func_type2(x => x); + assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, f2); }, + "can't convert the number 0 to the type of argument 1 of ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [ctypes.int32_t.ptr]).ptr"); + + // wrong struct in argument 1 + let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]); + let func_type3 = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, + [test_struct]).ptr; + let f3 = func_type3(x => x); + assertTypeErrorMessage(() => { ctypes.CDataFinalizer({ "x": "foo" }, f3); }, + "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct at argument 1 of ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [test_struct]).ptr"); + + // different size in argument 1 + let func_type4 = ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, + [ctypes.int32_t]).ptr; + let f4 = func_type4(x => x); + assertTypeErrorMessage(() => { ctypes.CDataFinalizer(ctypes.int16_t(0), f4); }, + "expected an object with the same size as argument 1 of ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, [ctypes.int32_t]).ptr, got ctypes.int16_t(0)"); + + let fin = ctypes.CDataFinalizer(ctypes.int32_t(0), f4); + fin.dispose(); + assertTypeErrorMessage(() => { ctypes.int32_t(0).value = fin; }, + "attempting to convert an empty CDataFinalizer"); + assertTypeErrorMessage(() => { f4(fin); }, + /attempting to convert an empty CDataFinalizer at argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes\.int32_t, \[ctypes\.int32_t\]\)\.ptr\(ctypes\.UInt64\(\"[x0-9A-Fa-f]+\"\)\)/); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-function.js b/js/src/jit-test/tests/ctypes/conversion-function.js new file mode 100644 index 0000000000..cd90f1b606 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-function.js @@ -0,0 +1,33 @@ +// Type conversion error should report its type. + +load(libdir + 'asserts.js'); + +function test() { + // Note: js shell cannot handle the exception in return value. + + // primitive + let func_type = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, + [ctypes.int32_t]).ptr; + let f1 = func_type(function() {}); + assertTypeErrorMessage(() => { f1("foo"); }, + /can't pass the string "foo" to argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes\.voidptr_t, \[ctypes\.int32_t\]\)\.ptr\(ctypes\.UInt64\("[x0-9A-Fa-f]+"\)\)/); + + // struct + let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]); + let func_type2 = ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, + [test_struct]).ptr; + let f2 = func_type2(function() {}); + assertTypeErrorMessage(() => { f2({ "x": "foo" }); }, + /can't convert the string \"foo\" to the 'x' field \(int32_t\) of test_struct at argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes.int32_t, \[test_struct\]\)\.ptr\(ctypes\.UInt64\(\"[x0-9A-Fa-f]+\"\)\)/); + assertTypeErrorMessage(() => { f2({ "x": "foo", "y": "bar" }); }, + /property count of the object \(\{x:\"foo\", y:\"bar\"\}\) does not match to field count of the type test_struct \(expected 1, got 2\) at argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes\.int32_t, \[test_struct\]\)\.ptr\(ctypes\.UInt64\(\"[x0-9A-Fa-f]+\"\)\)/); + assertTypeErrorMessage(() => { f2({ 0: "foo" }); }, + /property name the number 0 of the object \(\{0:\"foo\"\}\) is not a string at argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes\.int32_t, \[test_struct\]\)\.ptr\(ctypes\.UInt64\(\"[x0-9A-Fa-f]+\"\)\)/); + + // error sentinel + assertTypeErrorMessage(() => { func_type(function() {}, null, "foo"); }, + "can't convert the string \"foo\" to the return type of ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [ctypes.int32_t])"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-int64.js b/js/src/jit-test/tests/ctypes/conversion-int64.js new file mode 100644 index 0000000000..f1751bdc05 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-int64.js @@ -0,0 +1,20 @@ +load(libdir + 'asserts.js'); + +function test() { + assertTypeErrorMessage(() => { ctypes.Int64("0xfffffffffffffffffffffff"); }, + "can't pass the string \"0xfffffffffffffffffffffff\" to argument 1 of Int64"); + assertTypeErrorMessage(() => { ctypes.Int64.join("foo", 0); }, + "can't pass the string \"foo\" to argument 1 of Int64.join"); + assertTypeErrorMessage(() => { ctypes.Int64.join(0, "foo"); }, + "can't pass the string \"foo\" to argument 2 of Int64.join"); + + assertTypeErrorMessage(() => { ctypes.UInt64("0xfffffffffffffffffffffff"); }, + "can't pass the string \"0xfffffffffffffffffffffff\" to argument 1 of UInt64"); + assertTypeErrorMessage(() => { ctypes.UInt64.join("foo", 0); }, + "can't pass the string \"foo\" to argument 1 of UInt64.join"); + assertTypeErrorMessage(() => { ctypes.UInt64.join(0, "foo"); }, + "can't pass the string \"foo\" to argument 2 of UInt64.join"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-native-function.js b/js/src/jit-test/tests/ctypes/conversion-native-function.js new file mode 100644 index 0000000000..d2196813e9 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-native-function.js @@ -0,0 +1,37 @@ +// Type conversion error for native function should report its name and type +// in C style. + +load(libdir + 'asserts.js'); + +function test() { + let lib; + try { + lib = ctypes.open(ctypes.libraryName("c")); + } catch (e) { + } + if (!lib) + return; + + let func = lib.declare("hypot", + ctypes.default_abi, + ctypes.double, + ctypes.double, ctypes.double); + assertTypeErrorMessage(() => { func(1, "xyzzy"); }, + "can't pass the string \"xyzzy\" to argument 2 of double hypot(double, double)"); + + // test C style source for various types + let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]); + let test_func = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, + [ctypes.int32_t]).ptr; + func = lib.declare("hypot", + ctypes.default_abi, + ctypes.double, + ctypes.double, ctypes.int32_t.ptr.ptr.ptr.array(), + test_struct, test_struct.ptr.ptr, + test_func, test_func.ptr.ptr.ptr, "..."); + assertTypeErrorMessage(() => { func("xyzzy", 1, 2, 3, 4, 5); }, + "can't pass the string \"xyzzy\" to argument 1 of double hypot(double, int32_t****, struct test_struct, struct test_struct**, void* (*)(int32_t), void* (****)(int32_t), ...)"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-pointer.js b/js/src/jit-test/tests/ctypes/conversion-pointer.js new file mode 100644 index 0000000000..cfdcc8ed87 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-pointer.js @@ -0,0 +1,29 @@ +// Type conversion error should report its type. + +load(libdir + 'asserts.js'); + +function test() { + let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]); + let struct_val = test_struct(); + + // constructor + assertTypeErrorMessage(() => { ctypes.int32_t.ptr("foo"); }, + "can't convert the string \"foo\" to the type ctypes.int32_t.ptr"); + + // value setter + assertTypeErrorMessage(() => { test_struct.ptr().value = "foo"; }, + "can't convert the string \"foo\" to the type test_struct.ptr"); + assertTypeErrorMessage(() => { test_struct.ptr().value = {}; }, + "can't convert the object ({}) to the type test_struct.ptr"); + assertTypeErrorMessage(() => { test_struct.ptr().value = [1, 2]; }, + "can't convert the array [1, 2] to the type test_struct.ptr"); + assertTypeErrorMessage(() => { test_struct.ptr().value = Int8Array([1, 2]); }, + "can't convert the typed array ({0:1, 1:2}) to the type test_struct.ptr"); + + // contents setter + assertTypeErrorMessage(() => { ctypes.int32_t().address().contents = {}; }, + "can't convert the object ({}) to the type int32_t"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-primitive.js b/js/src/jit-test/tests/ctypes/conversion-primitive.js new file mode 100644 index 0000000000..aaaa95e6b8 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-primitive.js @@ -0,0 +1,44 @@ +// Type conversion error should report its type. + +load(libdir + 'asserts.js'); + +function test() { + // constructor + assertTypeErrorMessage(() => { ctypes.int32_t("foo"); }, + "can't convert the string \"foo\" to the type int32_t"); + assertTypeErrorMessage(() => { ctypes.int32_t(null); }, + "can't convert null to the type int32_t"); + assertTypeErrorMessage(() => { ctypes.int32_t(undefined); }, + "can't convert undefined to the type int32_t"); + assertTypeErrorMessage(() => { ctypes.int32_t({}); }, + "can't convert the object ({}) to the type int32_t"); + assertTypeErrorMessage(() => { ctypes.int32_t([]); }, + "can't convert the array [] to the type int32_t"); + assertTypeErrorMessage(() => { ctypes.int32_t(new Int8Array([])); }, + "can't convert the typed array ({}) to the type int32_t"); + assertTypeErrorMessage(() => { ctypes.int32_t(ctypes.int32_t); }, + "can't convert ctypes.int32_t to the type int32_t"); + assertTypeErrorMessage(() => { ctypes.int32_t("0xfffffffffffffffffffffff"); }, + "can't convert the string \"0xfffffffffffffffffffffff\" to the type int32_t"); + if (typeof Symbol === "function") { + assertTypeErrorMessage(() => { ctypes.int32_t(Symbol.iterator); }, + "can't convert Symbol.iterator to the type int32_t"); + assertTypeErrorMessage(() => { ctypes.int32_t(Symbol("foo")); }, + "can't convert Symbol(\"foo\") to the type int32_t"); + } + + // value setter + let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]); + let struct_val = test_struct(); + assertTypeErrorMessage(() => { ctypes.bool().value = struct_val; }, + "can't convert test_struct(0) to the type boolean"); + assertTypeErrorMessage(() => { ctypes.char16_t().value = struct_val; }, + "can't convert test_struct(0) to the type char16_t"); + assertTypeErrorMessage(() => { ctypes.int8_t().value = struct_val; }, + "can't convert test_struct(0) to the type int8_t"); + assertTypeErrorMessage(() => { ctypes.double().value = struct_val; }, + "can't convert test_struct(0) to the type double"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-struct.js b/js/src/jit-test/tests/ctypes/conversion-struct.js new file mode 100644 index 0000000000..f5e43ffa56 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-struct.js @@ -0,0 +1,36 @@ +// Type conversion error should report its type. + +load(libdir + 'asserts.js'); + +function test() { + let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }, + { "bar": ctypes.int32_t }]); + + // constructor + assertTypeErrorMessage(() => { new test_struct("foo"); }, + "can't convert the string \"foo\" to the type test_struct"); + assertTypeErrorMessage(() => { new test_struct("foo", "x"); }, + "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct"); + assertTypeErrorMessage(() => { new test_struct({ "x": "foo", "bar": 1 }); }, + "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct"); + assertTypeErrorMessage(() => { new test_struct({ 0: 1, "bar": 1 }); }, + "property name the number 0 of the object ({0:1, bar:1}) is not a string"); + + // field setter + let struct_val = test_struct(); + assertTypeErrorMessage(() => { struct_val.x = "foo"; }, + "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct"); + assertTypeErrorMessage(() => { struct_val.bar = "foo"; }, + "can't convert the string \"foo\" to the 'bar' field (int32_t) of test_struct"); + + // value setter + assertTypeErrorMessage(() => { struct_val.value = { "x": "foo" }; }, + "property count of the object ({x:\"foo\"}) does not match to field count of the type test_struct (expected 2, got 1)"); + assertTypeErrorMessage(() => { struct_val.value = { "x": "foo", "bar": 1 }; }, + "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct"); + assertTypeErrorMessage(() => { struct_val.value = "foo"; }, + "can't convert the string \"foo\" to the type test_struct"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/ctypes/conversion-to-primitive.js b/js/src/jit-test/tests/ctypes/conversion-to-primitive.js new file mode 100644 index 0000000000..cdb9a4a051 --- /dev/null +++ b/js/src/jit-test/tests/ctypes/conversion-to-primitive.js @@ -0,0 +1,20 @@ +// Accessing `value` property of non primitive type should report its type. + +load(libdir + 'asserts.js'); + +function test() { + let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.voidptr_t }]); + assertTypeErrorMessage(() => test_struct().value, + ".value only works on character and numeric types, not `test_struct`"); + + let test_array = ctypes.ArrayType(test_struct); + assertTypeErrorMessage(() => test_array(10).value, + ".value only works on character and numeric types, not `test_struct.array(10)`"); + + let test_pointer = ctypes.PointerType(test_struct); + assertTypeErrorMessage(() => test_pointer(10).value, + ".value only works on character and numeric types, not `test_struct.ptr`"); +} + +if (typeof ctypes === "object") + test(); diff --git a/js/src/jit-test/tests/debug/Memory-onGarbageCollection-01.js b/js/src/jit-test/tests/debug/Memory-onGarbageCollection-01.js deleted file mode 100644 index 8604ef40ba..0000000000 --- a/js/src/jit-test/tests/debug/Memory-onGarbageCollection-01.js +++ /dev/null @@ -1,57 +0,0 @@ -// Test basic usage of onGarbageCollection - -const root = newGlobal(); -const dbg = new Debugger(); -const wrappedRoot = dbg.addDebuggee(root) - -const NUM_SLICES = root.NUM_SLICES = 10; - -let fired = false; -let slicesFound = 0; - -dbg.memory.onGarbageCollection = data => { - fired = true; - - print("Got onGarbageCollection: " + JSON.stringify(data, null, 2)); - - assertEq(typeof data.reason, "string"); - assertEq(typeof data.nonincrementalReason == "string" || data.nonincrementalReason === null, - true); - - let lastStartTimestamp = 0; - for (let i = 0; i < data.collections.length; i++) { - let slice = data.collections[i]; - - assertEq(slice.startTimestamp >= lastStartTimestamp, true); - assertEq(slice.startTimestamp <= slice.endTimestamp, true); - - lastStartTimestamp = slice.startTimestamp; - } - - assertEq(data.collections.length >= 1, true); - slicesFound += data.collections.length; -} - -root.eval( - ` - this.allocs = []; - - // GC slices - for (var i = 0; i < NUM_SLICES; i++) { - this.allocs.push({}); - gcslice(); - } - - // Full GC - this.allocs.push({}); - gc(); - ` -); - -// The hook should have been fired at least once. -assertEq(fired, true); - -// NUM_SLICES + 1 full gc + however many were triggered naturally (due to -// whatever zealousness setting). -print("Found " + slicesFound + " slices"); -assertEq(slicesFound >= NUM_SLICES + 1, true); diff --git a/js/src/jit-test/tests/debug/Memory-onGarbageCollection-02.js b/js/src/jit-test/tests/debug/Memory-onGarbageCollection-02.js deleted file mode 100644 index 41e15de27c..0000000000 --- a/js/src/jit-test/tests/debug/Memory-onGarbageCollection-02.js +++ /dev/null @@ -1,69 +0,0 @@ -// Test multiple debuggers, GCs, and zones interacting with each other. - -gczeal(0); - -const root1 = newGlobal(); -const dbg1 = new Debugger(); -dbg1.addDebuggee(root1) - -const root2 = newGlobal(); -const dbg2 = new Debugger(); -dbg2.addDebuggee(root2) - -let fired1 = false; -let fired2 = false; -dbg1.memory.onGarbageCollection = _ => fired1 = true; -dbg2.memory.onGarbageCollection = _ => fired2 = true; - -function reset() { - fired1 = false; - fired2 = false; -} - -// GC 1 only -root1.eval(`gc(this)`); -assertEq(fired1, true); -assertEq(fired2, false); - -// GC 2 only -reset(); -root2.eval(`gc(this)`); -assertEq(fired1, false); -assertEq(fired2, true); - -// Full GC -reset(); -gc(); -assertEq(fired1, true); -assertEq(fired2, true); - -// Full GC with no debuggees -reset(); -dbg1.removeAllDebuggees(); -dbg2.removeAllDebuggees(); -gc(); -assertEq(fired1, false); -assertEq(fired2, false); - -// One debugger with multiple debuggees in different zones. - -dbg1.addDebuggee(root1); -dbg1.addDebuggee(root2); - -// Just debuggee 1 -reset(); -root1.eval(`gc(this)`); -assertEq(fired1, true); -assertEq(fired2, false); - -// Just debuggee 2 -reset(); -root2.eval(`gc(this)`); -assertEq(fired1, true); -assertEq(fired2, false); - -// All debuggees -reset(); -gc(); -assertEq(fired1, true); -assertEq(fired2, false); diff --git a/js/src/jit-test/tests/debug/Memory-onGarbageCollection-03.js b/js/src/jit-test/tests/debug/Memory-onGarbageCollection-03.js deleted file mode 100644 index 7a748bd151..0000000000 --- a/js/src/jit-test/tests/debug/Memory-onGarbageCollection-03.js +++ /dev/null @@ -1,18 +0,0 @@ -// Test that the onGarbageCollection hook is not reentrant. - -gczeal(0); - -const root = newGlobal(); -const dbg = new Debugger(); -const wrappedRoot = dbg.addDebuggee(root) - -let timesFired = 0; - -dbg.memory.onGarbageCollection = _ => { - timesFired++; - root.eval(`gc()`); -} - -root.eval(`gc()`); - -assertEq(timesFired, 1); diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js index 4a4480f4c9..145643d3b6 100644 --- a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js +++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js @@ -18,4 +18,4 @@ global.eval("function f(n){var w0,x1=3,y2=4,z3=9} debugger;"); global.f(3); // Should have hit each variable declared. -assertEq(global.log, "18 21 26 31 33 "); +assertEq(global.log, "18 21 26 31 35 "); diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js index dfc9c176f1..23a537f53f 100644 --- a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js +++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js @@ -17,4 +17,4 @@ global.log = ''; global.eval("function f(n){var o={a:1,b:2,c:3}} debugger;"); global.f(3); // Should hit each property in the object. -assertEq(global.log, "18 21 25 29 19 "); +assertEq(global.log, "18 21 25 29 33 "); diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js index a410070066..0e1704ee2e 100644 --- a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js +++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js @@ -17,4 +17,4 @@ global.log = ''; global.eval("function f(n){var a=[1,2,n]} debugger;"); global.f(3); // Should hit each item in the array. -assertEq(global.log, "18 21 23 25 19 "); +assertEq(global.log, "18 21 23 25 27 "); diff --git a/js/src/jit-test/tests/debug/Script-lineCount.js b/js/src/jit-test/tests/debug/Script-lineCount.js new file mode 100644 index 0000000000..8eff081129 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-lineCount.js @@ -0,0 +1,23 @@ +// Test Script.lineCount. + +var g = newGlobal(); +var dbg = Debugger(g); + +function test(scriptText, expectedLineCount) { + let found = false; + + dbg.onNewScript = function(script, global) { + assertEq(script.lineCount, expectedLineCount); + found = true; + }; + + g.evaluate(scriptText); + assertEq(found, true); +} + +src = 'var a = (function(){\n' + // 0 + 'var b = 9;\n' + // 1 + 'console.log("x", b);\n'+ // 2 + 'return b;\n' + // 3 + '})();\n'; // 4 +test(src, 5); diff --git a/js/src/jit-test/tests/gc/bug-1155455.js b/js/src/jit-test/tests/gc/bug-1155455.js new file mode 100644 index 0000000000..6516d2105d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1155455.js @@ -0,0 +1,17 @@ +// |jit-test| error: TypeError +if (!("gczeal" in this)) + quit(); +var g = newGlobal(); +gczeal(10, 2) +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame1) { + function hit(frame2) + hit[0] = "mutated"; + var s = frame1.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], {hit: hit}); + return; +}; +var lfGlobal = newGlobal(); +g.eval("var line0 = Error().lineNumber;\n debugger;\nx = 1;\n"); diff --git a/js/src/jit-test/tests/gc/incremental-abort.js b/js/src/jit-test/tests/gc/incremental-abort.js new file mode 100644 index 0000000000..b649c41caf --- /dev/null +++ b/js/src/jit-test/tests/gc/incremental-abort.js @@ -0,0 +1,49 @@ +// Test aborting an incremental GC in all possible states + +if (!("gcstate" in this && "gczeal" in this && "abortgc" in this)) + quit(); + +function testAbort(zoneCount, objectCount, sliceCount, abortState) +{ + // Allocate objectCount objects in zoneCount zones and run a incremental + // shrinking GC. + + var zones = []; + for (var i = 0; i < zoneCount; i++) { + var zone = newGlobal(); + evaluate("var objects; " + + "function makeObjectGraph(objectCount) { " + + " objects = []; " + + " for (var i = 0; i < objectCount; i++) " + + " objects.push({i: i}); " + + "}", + { global: zone }); + zone.makeObjectGraph(objectCount); + zones.push(zone); + } + + var didAbort = false; + startgc(sliceCount, "shrinking"); + while (gcstate() !== "none") { + var state = gcstate(); + if (state == abortState) { + abortgc(); + didAbort = true; + break; + } + + gcslice(sliceCount); + } + + assertEq(gcstate(), "none"); + if (abortState) + assertEq(didAbort, true); + + return zones; +} + +gczeal(0); +testAbort(10, 10000, 10000); +testAbort(10, 10000, 10000, "mark"); +testAbort(10, 10000, 10000, "sweep"); +testAbort(10, 10000, 10000, "compact"); diff --git a/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js b/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js index ae452decb3..c9748fdcfd 100644 --- a/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js @@ -79,5 +79,5 @@ assertEq(tByteSize([1, 2, 3, 4, 5, 6, 7, 8]), s(112, 128)); // Various forms of functions. assertEq(tByteSize(function () {}), s(32, 64)); assertEq(tByteSize(function () {}.bind()), s(96, 128)); -assertEq(tByteSize(() => 1), s(48, 96)); +assertEq(tByteSize(() => 1), s(48, 80)); assertEq(tByteSize(Math.sin), s(32, 64)); diff --git a/js/src/jit-test/tests/ion/bug1154971.js b/js/src/jit-test/tests/ion/bug1154971.js new file mode 100644 index 0000000000..ec49a88fcd --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1154971.js @@ -0,0 +1,10 @@ + + +function f(x, y) { + return Math.imul(0, Math.imul(y | 0, x >> 0)) +} +for (var i = 0; i < 2; i++) { + try { + (f(1 ? 0 : undefined))() + } catch (e) {} +} diff --git a/js/src/jit-test/tests/ion/bug1159899.js b/js/src/jit-test/tests/ion/bug1159899.js new file mode 100644 index 0000000000..10d930f3b9 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1159899.js @@ -0,0 +1,5 @@ +function f(x) { + return ~~(x >>> 0) / (x >>> 0) | 0 +} +f(1) +assertEq(f(-1), 0); diff --git a/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js b/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js index bf52735148..7c27d39543 100644 --- a/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js +++ b/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js @@ -3,7 +3,7 @@ function test() { function f() k.apply(this, arguments); if (undefined >> undefined !== 0) {} - for (var [ v , c ] = 0 in this.tracemonkey) { } + for (var [ v , c ] in this.tracemonkey) { } } try { test(); } catch(exc1) {} try { test(); } catch(exc1) {} diff --git a/js/src/jit-test/tests/jaeger/bug583684.js b/js/src/jit-test/tests/jaeger/bug583684.js deleted file mode 100644 index c034362118..0000000000 --- a/js/src/jit-test/tests/jaeger/bug583684.js +++ /dev/null @@ -1,8 +0,0 @@ -// |jit-test| error: TypeError -(function () { - var b = e - for (var [e] = b in w) c -})() - -/* Don't assert. */ - diff --git a/js/src/jit-test/tests/jaeger/bug719758.js b/js/src/jit-test/tests/jaeger/bug719758.js deleted file mode 100644 index e1569a901a..0000000000 --- a/js/src/jit-test/tests/jaeger/bug719758.js +++ /dev/null @@ -1,10 +0,0 @@ - -function test() { - try { - for (var i = 0 in this) throw p; - } catch (e) { - if (i !== 94) - return "what"; - } -} -test(); diff --git a/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js b/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js index 3a9ceb9ea6..4717897ac3 100644 --- a/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js +++ b/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js @@ -13,7 +13,14 @@ function recur(n, limit) { function checkRecursion(n, limit) { print("checkRecursion(" + uneval(n) + ", " + uneval(limit) + ")"); - var stack = recur(n, limit); + try { + var stack = recur(n, limit); + } catch (e) { + // Some platforms, like ASAN builds, can end up overrecursing. Tolerate + // these failures. + assertEq(/too much recursion/.test("" + e), true); + return; + } // Async stacks are limited even if we didn't ask for a limit. There is a // default limit on frames attached on top of any synchronous frames. In this diff --git a/js/src/jit-test/tests/xdr/lazy.js b/js/src/jit-test/tests/xdr/lazy.js index c3c4b89976..e0ad97a5ef 100644 --- a/js/src/jit-test/tests/xdr/lazy.js +++ b/js/src/jit-test/tests/xdr/lazy.js @@ -158,3 +158,7 @@ evaluate(code, {global:g1, compileAndGo: true, saveBytecode: {value: true}}); evaluate(code, {global:g2, loadBytecode: true}); gc(); assertEq(g2.f.toString(), res); + +// Another relazification case. +var src = "function f() { return 3; }; f(); relazifyFunctions(); 4"; +evalWithCache(src, {assertEqBytecode: true, assertEqResult: true}); diff --git a/js/src/jit/AliasAnalysis.cpp b/js/src/jit/AliasAnalysis.cpp index b97f6ec378..bd67745d08 100644 --- a/js/src/jit/AliasAnalysis.cpp +++ b/js/src/jit/AliasAnalysis.cpp @@ -14,6 +14,8 @@ #include "jit/MIR.h" #include "jit/MIRGraph.h" +#include "vm/Printer.h" + using namespace js; using namespace js::jit; @@ -128,11 +130,12 @@ IonSpewDependency(MInstruction* load, MInstruction* store, const char* verb, con if (!JitSpewEnabled(JitSpew_Alias)) return; - fprintf(JitSpewFile, "Load "); - load->printName(JitSpewFile); - fprintf(JitSpewFile, " %s on store ", verb); - store->printName(JitSpewFile); - fprintf(JitSpewFile, " (%s)\n", reason); + Fprinter& out = JitSpewPrinter(); + out.printf("Load "); + load->printName(out); + out.printf(" %s on store ", verb); + store->printName(out); + out.printf(" (%s)\n", reason); } static void @@ -141,9 +144,10 @@ IonSpewAliasInfo(const char* pre, MInstruction* ins, const char* post) if (!JitSpewEnabled(JitSpew_Alias)) return; - fprintf(JitSpewFile, "%s ", pre); - ins->printName(JitSpewFile); - fprintf(JitSpewFile, " %s\n", post); + Fprinter& out = JitSpewPrinter(); + out.printf("%s ", pre); + ins->printName(out); + out.printf(" %s\n", post); } // This pass annotates every load instruction with the last store instruction @@ -208,9 +212,10 @@ AliasAnalysis::analyze() } if (JitSpewEnabled(JitSpew_Alias)) { - fprintf(JitSpewFile, "Processing store "); - def->printName(JitSpewFile); - fprintf(JitSpewFile, " (flags %x)\n", set.flags()); + Fprinter& out = JitSpewPrinter(); + out.printf("Processing store "); + def->printName(out); + out.printf(" (flags %x)\n", set.flags()); } } else { // Find the most recent store on which this instruction depends. diff --git a/js/src/jit/BacktrackingAllocator.cpp b/js/src/jit/BacktrackingAllocator.cpp index fc8ce830e8..d718a2ba91 100644 --- a/js/src/jit/BacktrackingAllocator.cpp +++ b/js/src/jit/BacktrackingAllocator.cpp @@ -12,9 +12,398 @@ using namespace js::jit; using mozilla::DebugOnly; +///////////////////////////////////////////////////////////////////// +// Utility +///////////////////////////////////////////////////////////////////// + +static inline bool +SortBefore(UsePosition* a, UsePosition* b) +{ + return a->pos <= b->pos; +} + +static inline bool +SortBefore(LiveRange::BundleLink* a, LiveRange::BundleLink* b) +{ + LiveRange* rangea = LiveRange::get(a); + LiveRange* rangeb = LiveRange::get(b); + MOZ_ASSERT(!rangea->intersects(rangeb)); + return rangea->from() < rangeb->from(); +} + +static inline bool +SortBefore(LiveRange::RegisterLink* a, LiveRange::RegisterLink* b) +{ + return LiveRange::get(a)->from() <= LiveRange::get(b)->from(); +} + +template +static inline void +InsertSortedList(InlineForwardList &list, T* value) +{ + if (list.empty()) { + list.pushFront(value); + return; + } + + if (SortBefore(list.back(), value)) { + list.pushBack(value); + return; + } + + T* prev = nullptr; + for (InlineForwardListIterator iter = list.begin(); iter; iter++) { + if (SortBefore(value, *iter)) + break; + prev = *iter; + } + + if (prev) + list.insertAfter(prev, value); + else + list.pushFront(value); +} + +///////////////////////////////////////////////////////////////////// +// LiveRange +///////////////////////////////////////////////////////////////////// + +void +LiveRange::addUse(UsePosition* use) +{ + MOZ_ASSERT(covers(use->pos)); + InsertSortedList(uses_, use); +} + +void +LiveRange::distributeUses(LiveRange* other) +{ + MOZ_ASSERT(other->vreg() == vreg()); + MOZ_ASSERT(this != other); + + // Move over all uses which fit in |other|'s boundaries. + for (UsePositionIterator iter = usesBegin(); iter; ) { + UsePosition* use = *iter; + if (other->covers(use->pos)) { + uses_.removeAndIncrement(iter); + other->addUse(use); + } else { + iter++; + } + } + + // Distribute the definition to |other| as well, if possible. + if (hasDefinition() && from() == other->from()) + other->setHasDefinition(); +} + +bool +LiveRange::contains(LiveRange* other) const +{ + return from() <= other->from() && to() >= other->to(); +} + +void +LiveRange::intersect(LiveRange* other, Range* pre, Range* inside, Range* post) const +{ + MOZ_ASSERT(pre->empty() && inside->empty() && post->empty()); + + CodePosition innerFrom = from(); + if (from() < other->from()) { + if (to() < other->from()) { + *pre = range_; + return; + } + *pre = Range(from(), other->from()); + innerFrom = other->from(); + } + + CodePosition innerTo = to(); + if (to() > other->to()) { + if (from() >= other->to()) { + *post = range_; + return; + } + *post = Range(other->to(), to()); + innerTo = other->to(); + } + + if (innerFrom != innerTo) + *inside = Range(innerFrom, innerTo); +} + +bool +LiveRange::intersects(LiveRange* other) const +{ + Range pre, inside, post; + intersect(other, &pre, &inside, &post); + return !inside.empty(); +} + +///////////////////////////////////////////////////////////////////// +// SpillSet +///////////////////////////////////////////////////////////////////// + +void +SpillSet::setAllocation(LAllocation alloc) +{ + for (size_t i = 0; i < numSpilledBundles(); i++) + spilledBundle(i)->setAllocation(alloc); +} + +///////////////////////////////////////////////////////////////////// +// LiveBundle +///////////////////////////////////////////////////////////////////// + +#ifdef DEBUG +size_t +LiveBundle::numRanges() const +{ + size_t count = 0; + for (LiveRange::BundleLinkIterator iter = rangesBegin(); iter; iter++) + count++; + return count; +} +#endif // DEBUG + +LiveRange* +LiveBundle::rangeFor(CodePosition pos) const +{ + for (LiveRange::BundleLinkIterator iter = rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + if (range->covers(pos)) + return range; + } + return nullptr; +} + +void +LiveBundle::addRange(LiveRange* range) +{ + MOZ_ASSERT(!range->bundle()); + range->setBundle(this); + InsertSortedList(ranges_, &range->bundleLink); +} + +bool +LiveBundle::addRange(TempAllocator& alloc, uint32_t vreg, CodePosition from, CodePosition to) +{ + LiveRange* range = LiveRange::New(alloc, vreg, from, to); + if (!range) + return false; + addRange(range); + return true; +} + +bool +LiveBundle::addRangeAndDistributeUses(TempAllocator& alloc, LiveRange* oldRange, + CodePosition from, CodePosition to) +{ + LiveRange* range = LiveRange::New(alloc, oldRange->vreg(), from, to); + if (!range) + return false; + addRange(range); + oldRange->distributeUses(range); + return true; +} + +LiveRange* +LiveBundle::popFirstRange() +{ + LiveRange::BundleLinkIterator iter = rangesBegin(); + if (!iter) + return nullptr; + + LiveRange* range = LiveRange::get(*iter); + ranges_.removeAt(iter); + + range->setBundle(nullptr); + return range; +} + +void +LiveBundle::removeRange(LiveRange* range) +{ + for (LiveRange::BundleLinkIterator iter = rangesBegin(); iter; iter++) { + LiveRange* existing = LiveRange::get(*iter); + if (existing == range) { + ranges_.removeAt(iter); + return; + } + } + MOZ_CRASH(); +} + +///////////////////////////////////////////////////////////////////// +// VirtualRegister +///////////////////////////////////////////////////////////////////// + +bool +VirtualRegister::addInitialRange(TempAllocator& alloc, CodePosition from, CodePosition to) +{ + MOZ_ASSERT(from < to); + + // Mark [from,to) as a live range for this register during the initial + // liveness analysis, coalescing with any existing overlapping ranges. + + LiveRange* prev = nullptr; + LiveRange* merged = nullptr; + for (LiveRange::RegisterLinkIterator iter(rangesBegin()); iter; ) { + LiveRange* existing = LiveRange::get(*iter); + + if (from > existing->to()) { + // The new range should go after this one. + prev = existing; + iter++; + continue; + } + + if (to.next() < existing->from()) { + // The new range should go before this one. + break; + } + + if (!merged) { + // This is the first old range we've found that overlaps the new + // range. Extend this one to cover its union with the new range. + merged = existing; + + if (from < existing->from()) + existing->setFrom(from); + if (to > existing->to()) + existing->setTo(to); + + // Continue searching to see if any other old ranges can be + // coalesced with the new merged range. + iter++; + continue; + } + + // Coalesce this range into the previous range we merged into. + MOZ_ASSERT(existing->from() >= merged->from()); + if (existing->to() > merged->to()) + merged->setTo(existing->to()); + + MOZ_ASSERT(!existing->hasDefinition()); + existing->distributeUses(merged); + MOZ_ASSERT(!existing->hasUses()); + + ranges_.removeAndIncrement(iter); + } + + if (!merged) { + // The new range does not overlap any existing range for the vreg. + LiveRange* range = LiveRange::New(alloc, vreg(), from, to); + if (!range) + return false; + + if (prev) + ranges_.insertAfter(&prev->registerLink, &range->registerLink); + else + ranges_.pushFront(&range->registerLink); + } + + return true; +} + +void +VirtualRegister::addInitialUse(UsePosition* use) +{ + LiveRange::get(*rangesBegin())->addUse(use); +} + +void +VirtualRegister::setInitialDefinition(CodePosition from) +{ + LiveRange* first = LiveRange::get(*rangesBegin()); + MOZ_ASSERT(from >= first->from()); + first->setFrom(from); + first->setHasDefinition(); +} + +LiveRange* +VirtualRegister::rangeFor(CodePosition pos) const +{ + for (LiveRange::RegisterLinkIterator iter = rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + if (range->covers(pos)) + return range; + } + return nullptr; +} + +void +VirtualRegister::addRange(LiveRange* range) +{ + InsertSortedList(ranges_, &range->registerLink); +} + +void +VirtualRegister::removeRange(LiveRange* range) +{ + for (LiveRange::RegisterLinkIterator iter = rangesBegin(); iter; iter++) { + LiveRange* existing = LiveRange::get(*iter); + if (existing == range) { + ranges_.removeAt(iter); + return; + } + } + MOZ_CRASH(); +} + +///////////////////////////////////////////////////////////////////// +// BacktrackingAllocator +///////////////////////////////////////////////////////////////////// + +// This function pre-allocates and initializes as much global state as possible +// to avoid littering the algorithms with memory management cruft. bool BacktrackingAllocator::init() { + if (!RegisterAllocator::init()) + return false; + + liveIn = mir->allocate(graph.numBlockIds()); + if (!liveIn) + return false; + + callRanges = LiveBundle::New(alloc(), nullptr, nullptr); + + size_t numVregs = graph.numVirtualRegisters(); + if (!vregs.init(mir->alloc(), numVregs)) + return false; + memset(&vregs[0], 0, sizeof(VirtualRegister) * numVregs); + for (uint32_t i = 0; i < numVregs; i++) + new(&vregs[i]) VirtualRegister(); + + // Build virtual register objects. + for (size_t i = 0; i < graph.numBlocks(); i++) { + if (mir->shouldCancel("Create data structures (main loop)")) + return false; + + LBlock* block = graph.getBlock(i); + for (LInstructionIterator ins = block->begin(); ins != block->end(); ins++) { + for (size_t j = 0; j < ins->numDefs(); j++) { + LDefinition* def = ins->getDef(j); + if (def->isBogusTemp()) + continue; + vreg(def).init(*ins, def, /* isTemp = */ false); + } + + for (size_t j = 0; j < ins->numTemps(); j++) { + LDefinition* def = ins->getTemp(j); + if (def->isBogusTemp()) + continue; + vreg(def).init(*ins, def, /* isTemp = */ true); + } + } + for (size_t j = 0; j < block->numPhis(); j++) { + LPhi* phi = block->getPhi(j); + LDefinition* def = phi->getDef(0); + vreg(def).init(phi, def, /* isTemp = */ false); + } + } + LiveRegisterSet remainingRegisters(allRegisters_.asLiveSet()); while (!remainingRegisters.emptyGeneral()) { AnyRegister reg = AnyRegister(remainingRegisters.takeAnyGeneral()); @@ -29,13 +418,6 @@ BacktrackingAllocator::init() for (size_t i = 0; i < AnyRegister::Total; i++) { registers[i].reg = AnyRegister::FromCode(i); registers[i].allocations.setAllocator(lifoAlloc); - - LiveInterval* fixed = fixedIntervals[i]; - for (size_t j = 0; j < fixed->numRanges(); j++) { - AllocatedRange range(fixed, fixed->getRange(j)); - if (!registers[i].allocations.insert(range)) - return false; - } } hotcode.setAllocator(lifoAlloc); @@ -45,8 +427,6 @@ BacktrackingAllocator::init() // crapshoot, so just mark the bodies of inner loops as hot and everything // else as cold. - LiveInterval* hotcodeInterval = LiveInterval::New(alloc(), 0); - LBlock* backedge = nullptr; for (size_t i = 0; i < graph.numBlocks(); i++) { LBlock* block = graph.getBlock(i); @@ -59,19 +439,351 @@ BacktrackingAllocator::init() if (block == backedge) { LBlock* header = block->mir()->loopHeaderOfBackedge()->lir(); - CodePosition from = entryOf(header); - CodePosition to = exitOf(block).next(); - if (!hotcodeInterval->addRange(from, to)) + LiveRange* range = LiveRange::New(alloc(), 0, entryOf(header), exitOf(block).next()); + if (!range || !hotcode.insert(range)) return false; } } - for (size_t i = 0; i < hotcodeInterval->numRanges(); i++) { - AllocatedRange range(hotcodeInterval, hotcodeInterval->getRange(i)); - if (!hotcode.insert(range)) - return false; + return true; +} + +bool +BacktrackingAllocator::addInitialFixedRange(AnyRegister reg, CodePosition from, CodePosition to) +{ + LiveRange* range = LiveRange::New(alloc(), 0, from, to); + return range && registers[reg.code()].allocations.insert(range); +} + +#ifdef DEBUG +// Returns true iff ins has a def/temp reusing the input allocation. +static bool +IsInputReused(LInstruction* ins, LUse* use) +{ + for (size_t i = 0; i < ins->numDefs(); i++) { + if (ins->getDef(i)->policy() == LDefinition::MUST_REUSE_INPUT && + ins->getOperand(ins->getDef(i)->getReusedInput())->toUse() == use) + { + return true; + } } + for (size_t i = 0; i < ins->numTemps(); i++) { + if (ins->getTemp(i)->policy() == LDefinition::MUST_REUSE_INPUT && + ins->getOperand(ins->getTemp(i)->getReusedInput())->toUse() == use) + { + return true; + } + } + + return false; +} +#endif + +/* + * This function builds up liveness ranges for all virtual registers + * defined in the function. Additionally, it populates the liveIn array with + * information about which registers are live at the beginning of a block, to + * aid resolution and reification in a later phase. + * + * The algorithm is based on the one published in: + * + * Wimmer, Christian, and Michael Franz. "Linear Scan Register Allocation on + * SSA Form." Proceedings of the International Symposium on Code Generation + * and Optimization. Toronto, Ontario, Canada, ACM. 2010. 170-79. PDF. + * + * The algorithm operates on blocks ordered such that dominators of a block + * are before the block itself, and such that all blocks of a loop are + * contiguous. It proceeds backwards over the instructions in this order, + * marking registers live at their uses, ending their live ranges at + * definitions, and recording which registers are live at the top of every + * block. To deal with loop backedges, registers live at the beginning of + * a loop gain a range covering the entire loop. + */ +bool +BacktrackingAllocator::buildLivenessInfo() +{ + JitSpew(JitSpew_RegAlloc, "Beginning liveness analysis"); + + Vector loopWorkList; + BitSet loopDone(graph.numBlockIds()); + if (!loopDone.init(alloc())) + return false; + + for (size_t i = graph.numBlocks(); i > 0; i--) { + if (mir->shouldCancel("Build Liveness Info (main loop)")) + return false; + + LBlock* block = graph.getBlock(i - 1); + MBasicBlock* mblock = block->mir(); + + BitSet& live = liveIn[mblock->id()]; + new (&live) BitSet(graph.numVirtualRegisters()); + if (!live.init(alloc())) + return false; + + // Propagate liveIn from our successors to us. + for (size_t i = 0; i < mblock->lastIns()->numSuccessors(); i++) { + MBasicBlock* successor = mblock->lastIns()->getSuccessor(i); + // Skip backedges, as we fix them up at the loop header. + if (mblock->id() < successor->id()) + live.insertAll(liveIn[successor->id()]); + } + + // Add successor phis. + if (mblock->successorWithPhis()) { + LBlock* phiSuccessor = mblock->successorWithPhis()->lir(); + for (unsigned int j = 0; j < phiSuccessor->numPhis(); j++) { + LPhi* phi = phiSuccessor->getPhi(j); + LAllocation* use = phi->getOperand(mblock->positionInPhiSuccessor()); + uint32_t reg = use->toUse()->virtualRegister(); + live.insert(reg); + } + } + + // Registers are assumed alive for the entire block, a define shortens + // the range to the point of definition. + for (BitSet::Iterator liveRegId(live); liveRegId; ++liveRegId) { + if (!vregs[*liveRegId].addInitialRange(alloc(), entryOf(block), exitOf(block).next())) + return false; + } + + // Shorten the front end of ranges for live variables to their point of + // definition, if found. + for (LInstructionReverseIterator ins = block->rbegin(); ins != block->rend(); ins++) { + // Calls may clobber registers, so force a spill and reload around the callsite. + if (ins->isCall()) { + for (AnyRegisterIterator iter(allRegisters_.asLiveSet()); iter.more(); iter++) { + bool found = false; + for (size_t i = 0; i < ins->numDefs(); i++) { + if (ins->getDef(i)->isFixed() && + ins->getDef(i)->output()->aliases(LAllocation(*iter))) { + found = true; + break; + } + } + if (!found) { + if (!addInitialFixedRange(*iter, outputOf(*ins), outputOf(*ins).next())) + return false; + } + } + if (!callRanges->addRange(alloc(), 0, outputOf(*ins), outputOf(*ins).next())) + return false; + } + DebugOnly hasDoubleDef = false; + DebugOnly hasFloat32Def = false; + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition* def = ins->getDef(i); + if (def->isBogusTemp()) + continue; +#ifdef DEBUG + if (def->type() == LDefinition::DOUBLE) + hasDoubleDef = true; + if (def->type() == LDefinition::FLOAT32) + hasFloat32Def = true; +#endif + CodePosition from = outputOf(*ins); + + if (def->policy() == LDefinition::MUST_REUSE_INPUT) { + // MUST_REUSE_INPUT is implemented by allocating an output + // register and moving the input to it. Register hints are + // used to avoid unnecessary moves. We give the input an + // LUse::ANY policy to avoid allocating a register for the + // input. + LUse* inputUse = ins->getOperand(def->getReusedInput())->toUse(); + MOZ_ASSERT(inputUse->policy() == LUse::REGISTER); + MOZ_ASSERT(inputUse->usedAtStart()); + *inputUse = LUse(inputUse->virtualRegister(), LUse::ANY, /* usedAtStart = */ true); + } + + if (!vreg(def).addInitialRange(alloc(), from, from.next())) + return false; + vreg(def).setInitialDefinition(from); + live.remove(def->virtualRegister()); + } + + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition* temp = ins->getTemp(i); + if (temp->isBogusTemp()) + continue; + + // Normally temps are considered to cover both the input + // and output of the associated instruction. In some cases + // though we want to use a fixed register as both an input + // and clobbered register in the instruction, so watch for + // this and shorten the temp to cover only the output. + CodePosition from = inputOf(*ins); + if (temp->policy() == LDefinition::FIXED) { + AnyRegister reg = temp->output()->toRegister(); + for (LInstruction::InputIterator alloc(**ins); alloc.more(); alloc.next()) { + if (alloc->isUse()) { + LUse* use = alloc->toUse(); + if (use->isFixedRegister()) { + if (GetFixedRegister(vreg(use).def(), use) == reg) + from = outputOf(*ins); + } + } + } + } + + CodePosition to = + ins->isCall() ? outputOf(*ins) : outputOf(*ins).next(); + + if (!vreg(temp).addInitialRange(alloc(), from, to)) + return false; + vreg(temp).setInitialDefinition(from); + } + + DebugOnly hasUseRegister = false; + DebugOnly hasUseRegisterAtStart = false; + + for (LInstruction::InputIterator inputAlloc(**ins); inputAlloc.more(); inputAlloc.next()) { + if (inputAlloc->isUse()) { + LUse* use = inputAlloc->toUse(); + + // Call uses should always be at-start or fixed, since + // calls use all registers. + MOZ_ASSERT_IF(ins->isCall() && !inputAlloc.isSnapshotInput(), + use->isFixedRegister() || use->usedAtStart()); + +#ifdef DEBUG + // Don't allow at-start call uses if there are temps of the same kind, + // so that we don't assign the same register. + if (ins->isCall() && use->usedAtStart()) { + for (size_t i = 0; i < ins->numTemps(); i++) + MOZ_ASSERT(vreg(ins->getTemp(i)).type() != vreg(use).type()); + } + + // If there are both useRegisterAtStart(x) and useRegister(y) + // uses, we may assign the same register to both operands + // (bug 772830). Don't allow this for now. + if (use->policy() == LUse::REGISTER) { + if (use->usedAtStart()) { + if (!IsInputReused(*ins, use)) + hasUseRegisterAtStart = true; + } else { + hasUseRegister = true; + } + } + MOZ_ASSERT(!(hasUseRegister && hasUseRegisterAtStart)); +#endif + + // Don't treat RECOVERED_INPUT uses as keeping the vreg alive. + if (use->policy() == LUse::RECOVERED_INPUT) + continue; + + // Fixed uses on calls are specially overridden to happen + // at the input position. + CodePosition to = + (use->usedAtStart() || (ins->isCall() && use->isFixedRegister())) + ? inputOf(*ins) + : outputOf(*ins); + if (use->isFixedRegister()) { + LAllocation reg(AnyRegister::FromCode(use->registerCode())); + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition* def = ins->getDef(i); + if (def->policy() == LDefinition::FIXED && *def->output() == reg) + to = inputOf(*ins); + } + } + + if (!vreg(use).addInitialRange(alloc(), entryOf(block), to.next())) + return false; + UsePosition* usePosition = new(alloc()) UsePosition(use, to); + if (!usePosition) + return false; + vreg(use).addInitialUse(usePosition); + live.insert(use->virtualRegister()); + } + } + } + + // Phis have simultaneous assignment semantics at block begin, so at + // the beginning of the block we can be sure that liveIn does not + // contain any phi outputs. + for (unsigned int i = 0; i < block->numPhis(); i++) { + LDefinition* def = block->getPhi(i)->getDef(0); + if (live.contains(def->virtualRegister())) { + live.remove(def->virtualRegister()); + } else { + // This is a dead phi, so add a dummy range over all phis. This + // can go away if we have an earlier dead code elimination pass. + CodePosition entryPos = entryOf(block); + if (!vreg(def).addInitialRange(alloc(), entryPos, entryPos.next())) + return false; + } + } + + if (mblock->isLoopHeader()) { + // A divergence from the published algorithm is required here, as + // our block order does not guarantee that blocks of a loop are + // contiguous. As a result, a single live range spanning the + // loop is not possible. Additionally, we require liveIn in a later + // pass for resolution, so that must also be fixed up here. + MBasicBlock* loopBlock = mblock->backedge(); + while (true) { + // Blocks must already have been visited to have a liveIn set. + MOZ_ASSERT(loopBlock->id() >= mblock->id()); + + // Add a range for this entire loop block + CodePosition from = entryOf(loopBlock->lir()); + CodePosition to = exitOf(loopBlock->lir()).next(); + + for (BitSet::Iterator liveRegId(live); liveRegId; ++liveRegId) { + if (!vregs[*liveRegId].addInitialRange(alloc(), from, to)) + return false; + } + + // Fix up the liveIn set. + liveIn[loopBlock->id()].insertAll(live); + + // Make sure we don't visit this node again + loopDone.insert(loopBlock->id()); + + // If this is the loop header, any predecessors are either the + // backedge or out of the loop, so skip any predecessors of + // this block + if (loopBlock != mblock) { + for (size_t i = 0; i < loopBlock->numPredecessors(); i++) { + MBasicBlock* pred = loopBlock->getPredecessor(i); + if (loopDone.contains(pred->id())) + continue; + if (!loopWorkList.append(pred)) + return false; + } + } + + // Terminate loop if out of work. + if (loopWorkList.empty()) + break; + + // Grab the next block off the work list, skipping any OSR block. + MBasicBlock* osrBlock = graph.mir().osrBlock(); + while (!loopWorkList.empty()) { + loopBlock = loopWorkList.popCopy(); + if (loopBlock != osrBlock) + break; + } + + // If end is reached without finding a non-OSR block, then no more work items were found. + if (loopBlock == osrBlock) { + MOZ_ASSERT(loopWorkList.empty()); + break; + } + } + + // Clear the done set for other loops + loopDone.clear(); + } + + MOZ_ASSERT_IF(!mblock->numPredecessors(), live.empty()); + } + + JitSpew(JitSpew_RegAlloc, "Liveness analysis complete"); + + if (JitSpewEnabled(JitSpew_RegAlloc)) + dumpInstructions(); + return true; } @@ -80,10 +792,10 @@ BacktrackingAllocator::go() { JitSpew(JitSpew_RegAlloc, "Beginning register allocation"); - if (!buildLivenessInfo()) + if (!init()) return false; - if (!init()) + if (!buildLivenessInfo()) return false; if (JitSpewEnabled(JitSpew_RegAlloc)) @@ -93,22 +805,21 @@ BacktrackingAllocator::go() return false; JitSpew(JitSpew_RegAlloc, "Beginning grouping and queueing registers"); - if (!groupAndQueueRegisters()) + if (!mergeAndQueueRegisters()) return false; - JitSpew(JitSpew_RegAlloc, "Grouping and queueing registers complete"); if (JitSpewEnabled(JitSpew_RegAlloc)) - dumpRegisterGroups(); + dumpVregs(); JitSpew(JitSpew_RegAlloc, "Beginning main allocation loop"); - // Allocate, spill and split register intervals until finished. + // Allocate, spill and split bundles until finished. while (!allocationQueue.empty()) { if (mir->shouldCancel("Backtracking Allocation")) return false; QueueItem item = allocationQueue.removeHighest(); - if (item.interval ? !processInterval(item.interval) : !processGroup(item.group)) + if (!processBundle(item.bundle)) return false; } JitSpew(JitSpew_RegAlloc, "Main allocation loop complete"); @@ -134,44 +845,6 @@ BacktrackingAllocator::go() return true; } -static bool -LifetimesOverlap(BacktrackingVirtualRegister* reg0, BacktrackingVirtualRegister* reg1) -{ - // Registers may have been eagerly split in two, see tryGroupReusedRegister. - // In such cases, only consider the first interval. - MOZ_ASSERT(reg0->numIntervals() <= 2 && reg1->numIntervals() <= 2); - - LiveInterval* interval0 = reg0->getInterval(0); - LiveInterval* interval1 = reg1->getInterval(0); - - // Interval ranges are sorted in reverse order. The lifetimes overlap if - // any of their ranges overlap. - size_t index0 = 0, index1 = 0; - while (index0 < interval0->numRanges() && index1 < interval1->numRanges()) { - const LiveInterval::Range - *range0 = interval0->getRange(index0), - *range1 = interval1->getRange(index1); - if (range0->from >= range1->to) - index0++; - else if (range1->from >= range0->to) - index1++; - else - return true; - } - - return false; -} - -bool -BacktrackingAllocator::canAddToGroup(VirtualRegisterGroup* group, BacktrackingVirtualRegister* reg) -{ - for (size_t i = 0; i < group->registers.length(); i++) { - if (LifetimesOverlap(reg, &vregs[group->registers[i]])) - return false; - } - return true; -} - static bool IsArgumentSlotDefinition(LDefinition* def) { @@ -182,203 +855,224 @@ static bool IsThisSlotDefinition(LDefinition* def) { return IsArgumentSlotDefinition(def) && - def->output()->toArgument()->index() < THIS_FRAME_ARGSLOT + sizeof(Value); + def->output()->toArgument()->index() < THIS_FRAME_ARGSLOT + sizeof(Value); } bool -BacktrackingAllocator::tryGroupRegisters(uint32_t vreg0, uint32_t vreg1) +BacktrackingAllocator::tryMergeBundles(LiveBundle* bundle0, LiveBundle* bundle1) { - // See if reg0 and reg1 can be placed in the same group, following the - // restrictions imposed by VirtualRegisterGroup and any other registers - // already grouped with reg0 or reg1. - BacktrackingVirtualRegister* reg0 = &vregs[vreg0]; - BacktrackingVirtualRegister* reg1 = &vregs[vreg1]; + // See if bundle0 and bundle1 can be merged together. + if (bundle0 == bundle1) + return true; - if (!reg0->isCompatibleVReg(*reg1)) + // Get a representative virtual register from each bundle. + VirtualRegister& reg0 = vregs[bundle0->firstRange()->vreg()]; + VirtualRegister& reg1 = vregs[bundle1->firstRange()->vreg()]; + + if (!reg0.isCompatible(reg1)) return true; // Registers which might spill to the frame's |this| slot can only be // grouped with other such registers. The frame's |this| slot must always // hold the |this| value, as required by JitFrame tracing and by the Ion // constructor calling convention. - if (IsThisSlotDefinition(reg0->def()) || IsThisSlotDefinition(reg1->def())) { - if (*reg0->def()->output() != *reg1->def()->output()) + if (IsThisSlotDefinition(reg0.def()) || IsThisSlotDefinition(reg1.def())) { + if (*reg0.def()->output() != *reg1.def()->output()) return true; } // Registers which might spill to the frame's argument slots can only be // grouped with other such registers if the frame might access those // arguments through a lazy arguments object or rest parameter. - if (IsArgumentSlotDefinition(reg0->def()) || IsArgumentSlotDefinition(reg1->def())) { + if (IsArgumentSlotDefinition(reg0.def()) || IsArgumentSlotDefinition(reg1.def())) { if (graph.mir().entryBlock()->info().mayReadFrameArgsDirectly()) { - if (*reg0->def()->output() != *reg1->def()->output()) + if (*reg0.def()->output() != *reg1.def()->output()) return true; } } - VirtualRegisterGroup* group0 = reg0->group(); - VirtualRegisterGroup* group1 = reg1->group(); + // Make sure that ranges in the bundles do not overlap. + LiveRange::BundleLinkIterator iter0 = bundle0->rangesBegin(), iter1 = bundle1->rangesBegin(); + while (iter0 && iter1) { + LiveRange* range0 = LiveRange::get(*iter0); + LiveRange* range1 = LiveRange::get(*iter1); - if (!group0 && group1) - return tryGroupRegisters(vreg1, vreg0); - - if (group0) { - if (group1) { - if (group0 == group1) { - // The registers are already grouped together. - return true; - } - // Try to unify the two distinct groups. - for (size_t i = 0; i < group1->registers.length(); i++) { - if (!canAddToGroup(group0, &vregs[group1->registers[i]])) - return true; - } - for (size_t i = 0; i < group1->registers.length(); i++) { - uint32_t vreg = group1->registers[i]; - if (!group0->registers.append(vreg)) - return false; - vregs[vreg].setGroup(group0); - } + if (range0->from() >= range1->to()) + iter1++; + else if (range1->from() >= range0->to()) + iter0++; + else return true; - } - if (!canAddToGroup(group0, reg1)) - return true; - if (!group0->registers.append(vreg1)) - return false; - reg1->setGroup(group0); - return true; } - if (LifetimesOverlap(reg0, reg1)) - return true; + // Move all ranges from bundle1 into bundle0. + while (LiveRange* range = bundle1->popFirstRange()) + bundle0->addRange(range); - VirtualRegisterGroup* group = new(alloc()) VirtualRegisterGroup(alloc()); - if (!group->registers.append(vreg0) || !group->registers.append(vreg1)) - return false; - - reg0->setGroup(group); - reg1->setGroup(group); return true; } -bool -BacktrackingAllocator::tryGroupReusedRegister(uint32_t def, uint32_t use) +static inline LDefinition* +FindReusingDefinition(LNode* ins, LAllocation* alloc) { - BacktrackingVirtualRegister& reg = vregs[def]; - BacktrackingVirtualRegister& usedReg = vregs[use]; + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition* def = ins->getDef(i); + if (def->policy() == LDefinition::MUST_REUSE_INPUT && + ins->getOperand(def->getReusedInput()) == alloc) + return def; + } + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition* def = ins->getTemp(i); + if (def->policy() == LDefinition::MUST_REUSE_INPUT && + ins->getOperand(def->getReusedInput()) == alloc) + return def; + } + return nullptr; +} - // reg is a vreg which reuses its input usedReg for its output physical - // register. Try to group reg with usedReg if at all possible, as avoiding - // copies before reg's instruction is crucial for the quality of the - // generated code (MUST_REUSE_INPUT is used by all arithmetic instructions - // on x86/x64). +bool +BacktrackingAllocator::tryMergeReusedRegister(VirtualRegister& def, VirtualRegister& input) +{ + // def is a vreg which reuses input for its output physical register. Try + // to merge ranges for def with those of input if possible, as avoiding + // copies before def's instruction is crucial for generated code quality + // (MUST_REUSE_INPUT is used for all arithmetic on x86/x64). - if (reg.intervalFor(inputOf(reg.ins()))) { - MOZ_ASSERT(reg.isTemp()); - reg.setMustCopyInput(); + if (def.rangeFor(inputOf(def.ins()))) { + MOZ_ASSERT(def.isTemp()); + def.setMustCopyInput(); return true; } - if (!usedReg.intervalFor(outputOf(reg.ins()))) { + LiveRange* inputRange = input.rangeFor(outputOf(def.ins())); + if (!inputRange) { // The input is not live after the instruction, either in a safepoint // for the instruction or in subsequent code. The input and output // can thus be in the same group. - return tryGroupRegisters(use, def); + return tryMergeBundles(def.firstBundle(), input.firstBundle()); } // The input is live afterwards, either in future instructions or in a // safepoint for the reusing instruction. This is impossible to satisfy // without copying the input. // - // It may or may not be better to split the interval at the point of the - // definition, which may permit grouping. One case where it is definitely - // better to split is if the input never has any register uses after the - // instruction. Handle this splitting eagerly. + // It may or may not be better to split the input into two bundles at the + // point of the definition, which may permit merging. One case where it is + // definitely better to split is if the input never has any register uses + // after the instruction. Handle this splitting eagerly. - if (usedReg.numIntervals() != 1 || - (usedReg.def()->isFixed() && !usedReg.def()->output()->isRegister())) { - reg.setMustCopyInput(); - return true; - } - LiveInterval* interval = usedReg.getInterval(0); - LBlock* block = reg.ins()->block(); + LBlock* block = def.ins()->block(); // The input's lifetime must end within the same block as the definition, // otherwise it could live on in phis elsewhere. - if (interval->end() > exitOf(block)) { - reg.setMustCopyInput(); + if (inputRange != input.lastRange() || inputRange->to() > exitOf(block)) { + def.setMustCopyInput(); return true; } - for (UsePositionIterator iter = interval->usesBegin(); iter != interval->usesEnd(); iter++) { - if (iter->pos <= inputOf(reg.ins())) + // If we already split the input for some other register, don't make a + // third bundle. + if (inputRange->bundle() != input.firstRange()->bundle()) { + def.setMustCopyInput(); + return true; + } + + // If the input will start out in memory then adding a separate bundle for + // memory uses after the def won't help. + if (input.def()->isFixed() && !input.def()->output()->isRegister()) { + def.setMustCopyInput(); + return true; + } + + // The input cannot have register or reused uses after the definition. + for (UsePositionIterator iter = inputRange->usesBegin(); iter; iter++) { + if (iter->pos <= inputOf(def.ins())) continue; LUse* use = iter->use; if (FindReusingDefinition(insData[iter->pos], use)) { - reg.setMustCopyInput(); + def.setMustCopyInput(); return true; } if (use->policy() != LUse::ANY && use->policy() != LUse::KEEPALIVE) { - reg.setMustCopyInput(); + def.setMustCopyInput(); return true; } } - LiveInterval* preInterval = LiveInterval::New(alloc(), interval->vreg(), 0); - for (size_t i = 0; i < interval->numRanges(); i++) { - const LiveInterval::Range* range = interval->getRange(i); - MOZ_ASSERT(range->from <= inputOf(reg.ins())); + LiveRange* preRange = LiveRange::New(alloc(), input.vreg(), + inputRange->from(), outputOf(def.ins())); + if (!preRange) + return false; - CodePosition to = Min(range->to, outputOf(reg.ins())); - if (!preInterval->addRange(range->from, to)) - return false; - } - - // The new interval starts at reg's input position, which means it overlaps - // with the old interval at one position. This is what we want, because we + // The new range starts at reg's input position, which means it overlaps + // with the old range at one position. This is what we want, because we // need to copy the input before the instruction. - LiveInterval* postInterval = LiveInterval::New(alloc(), interval->vreg(), 0); - if (!postInterval->addRange(inputOf(reg.ins()), interval->end())) + LiveRange* postRange = LiveRange::New(alloc(), input.vreg(), + inputOf(def.ins()), inputRange->to()); + if (!postRange) return false; - LiveIntervalVector newIntervals; - if (!newIntervals.append(preInterval) || !newIntervals.append(postInterval)) - return false; - - distributeUses(interval, newIntervals); + inputRange->distributeUses(preRange); + inputRange->distributeUses(postRange); + MOZ_ASSERT(!inputRange->hasUses()); JitSpew(JitSpew_RegAlloc, " splitting reused input at %u to try to help grouping", - inputOf(reg.ins())); + inputOf(def.ins())); - if (!split(interval, newIntervals)) + LiveBundle* firstBundle = inputRange->bundle(); + input.removeRange(inputRange); + input.addRange(preRange); + input.addRange(postRange); + + firstBundle->removeRange(inputRange); + firstBundle->addRange(preRange); + + // The new range goes in a separate bundle, where it will be spilled during + // allocation. + LiveBundle* secondBundle = LiveBundle::New(alloc(), nullptr, nullptr); + if (!secondBundle) return false; + secondBundle->addRange(postRange); - MOZ_ASSERT(usedReg.numIntervals() == 2); - - usedReg.setCanonicalSpillExclude(inputOf(reg.ins())); - - return tryGroupRegisters(use, def); + return tryMergeBundles(def.firstBundle(), input.firstBundle()); } bool -BacktrackingAllocator::groupAndQueueRegisters() +BacktrackingAllocator::mergeAndQueueRegisters() { - // If there is an OSR block, group parameters in that block with the + MOZ_ASSERT(!vregs[0u].hasRanges()); + + // Create a bundle for each register containing all its ranges. + for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + if (!reg.hasRanges()) + continue; + + LiveBundle* bundle = LiveBundle::New(alloc(), nullptr, nullptr); + if (!bundle) + return false; + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + bundle->addRange(range); + } + } + + // If there is an OSR block, merge parameters in that block with the // corresponding parameters in the initial block. if (MBasicBlock* osr = graph.mir().osrBlock()) { - size_t originalVreg = 1; + size_t original = 1; for (LInstructionIterator iter = osr->lir()->begin(); iter != osr->lir()->end(); iter++) { if (iter->isParameter()) { for (size_t i = 0; i < iter->numDefs(); i++) { DebugOnly found = false; - uint32_t paramVreg = iter->getDef(i)->virtualRegister(); - for (; originalVreg < paramVreg; originalVreg++) { - if (*vregs[originalVreg].def()->output() == *iter->getDef(i)->output()) { - MOZ_ASSERT(vregs[originalVreg].ins()->isParameter()); - if (!tryGroupRegisters(originalVreg, paramVreg)) + VirtualRegister ¶mVreg = vreg(iter->getDef(i)); + for (; original < paramVreg.vreg(); original++) { + VirtualRegister &originalVreg = vregs[original]; + if (*originalVreg.def()->output() == *iter->getDef(i)->output()) { + MOZ_ASSERT(originalVreg.ins()->isParameter()); + if (!tryMergeBundles(originalVreg.firstBundle(), paramVreg.firstBundle())) return false; - MOZ_ASSERT(vregs[originalVreg].group() == vregs[paramVreg].group()); found = true; break; } @@ -389,79 +1083,47 @@ BacktrackingAllocator::groupAndQueueRegisters() } } - // Try to group registers with their reused inputs. - // Virtual register number 0 is unused. - MOZ_ASSERT(vregs[0u].numIntervals() == 0); + // Try to merge registers with their reused inputs. for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { - BacktrackingVirtualRegister& reg = vregs[i]; - if (!reg.numIntervals()) + VirtualRegister& reg = vregs[i]; + if (!reg.hasRanges()) continue; if (reg.def()->policy() == LDefinition::MUST_REUSE_INPUT) { LUse* use = reg.ins()->getOperand(reg.def()->getReusedInput())->toUse(); - if (!tryGroupReusedRegister(i, use->virtualRegister())) + if (!tryMergeReusedRegister(reg, vreg(use))) return false; } } - // Try to group phis with their inputs. + // Try to merge phis with their inputs. for (size_t i = 0; i < graph.numBlocks(); i++) { LBlock* block = graph.getBlock(i); for (size_t j = 0; j < block->numPhis(); j++) { LPhi* phi = block->getPhi(j); - uint32_t output = phi->getDef(0)->virtualRegister(); + VirtualRegister &outputVreg = vreg(phi->getDef(0)); for (size_t k = 0, kend = phi->numOperands(); k < kend; k++) { - uint32_t input = phi->getOperand(k)->toUse()->virtualRegister(); - if (!tryGroupRegisters(input, output)) + VirtualRegister& inputVreg = vreg(phi->getOperand(k)->toUse()); + if (!tryMergeBundles(inputVreg.firstBundle(), outputVreg.firstBundle())) return false; } } } - // Virtual register number 0 is unused. - MOZ_ASSERT(vregs[0u].numIntervals() == 0); + // Add all bundles to the allocation queue, and create spill sets for them. for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { - if (mir->shouldCancel("Backtracking Enqueue Registers")) - return false; - - BacktrackingVirtualRegister& reg = vregs[i]; - MOZ_ASSERT(reg.numIntervals() <= 2); - MOZ_ASSERT(!reg.canonicalSpill()); - - if (!reg.numIntervals()) - continue; - - // Eagerly set the canonical spill slot for registers which are fixed - // for that slot, and reuse it for other registers in the group. - LDefinition* def = reg.def(); - if (def->policy() == LDefinition::FIXED && !def->output()->isRegister()) { - MOZ_ASSERT(!def->output()->isStackSlot()); - reg.setCanonicalSpill(*def->output()); - if (reg.group() && reg.group()->spill.isUse()) - reg.group()->spill = *def->output(); - } - - // Place all intervals for this register on the allocation queue. - // During initial queueing use single queue items for groups of - // registers, so that they will be allocated together and reduce the - // risk of unnecessary conflicts. This is in keeping with the idea that - // register groups are effectively single registers whose value changes - // during execution. If any intervals in the group are evicted later - // then they will be reallocated individually. - size_t start = 0; - if (VirtualRegisterGroup* group = reg.group()) { - if (i == group->canonicalReg()) { - size_t priority = computePriority(group); - if (!allocationQueue.insert(QueueItem(group, priority))) + VirtualRegister& reg = vregs[i]; + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveBundle* bundle = range->bundle(); + if (range == bundle->firstRange()) { + SpillSet* spill = SpillSet::New(alloc()); + if (!spill) return false; - } - start++; - } - for (; start < reg.numIntervals(); start++) { - LiveInterval* interval = reg.getInterval(start); - if (interval->numRanges() > 0) { - size_t priority = computePriority(interval); - if (!allocationQueue.insert(QueueItem(interval, priority))) + bundle->setSpillSet(spill); + + size_t priority = computePriority(bundle); + if (!allocationQueue.insert(QueueItem(bundle, priority))) return false; } } @@ -473,117 +1135,121 @@ BacktrackingAllocator::groupAndQueueRegisters() static const size_t MAX_ATTEMPTS = 2; bool -BacktrackingAllocator::tryAllocateFixed(LiveInterval* interval, bool* success, - bool* pfixed, LiveIntervalVector& conflicting) +BacktrackingAllocator::tryAllocateFixed(LiveBundle* bundle, Requirement requirement, + bool* success, bool* pfixed, + LiveBundleVector& conflicting) { - // Spill intervals which are required to be in a certain stack slot. - if (!interval->requirement()->allocation().isRegister()) { + // Spill bundles which are required to be in a certain stack slot. + if (!requirement.allocation().isRegister()) { JitSpew(JitSpew_RegAlloc, " stack allocation requirement"); - interval->setAllocation(interval->requirement()->allocation()); + bundle->setAllocation(requirement.allocation()); *success = true; return true; } - AnyRegister reg = interval->requirement()->allocation().toRegister(); - return tryAllocateRegister(registers[reg.code()], interval, success, pfixed, conflicting); + AnyRegister reg = requirement.allocation().toRegister(); + return tryAllocateRegister(registers[reg.code()], bundle, success, pfixed, conflicting); } bool -BacktrackingAllocator::tryAllocateNonFixed(LiveInterval* interval, bool* success, - bool* pfixed, LiveIntervalVector& conflicting) +BacktrackingAllocator::tryAllocateNonFixed(LiveBundle* bundle, + Requirement requirement, Requirement hint, + bool* success, bool* pfixed, + LiveBundleVector& conflicting) { - // If we want, but do not require an interval to be in a specific - // register, only look at that register for allocating and evict - // or spill if it is not available. Picking a separate register may - // be even worse than spilling, as it will still necessitate moves - // and will tie up more registers than if we spilled. - if (interval->hint()->kind() == Requirement::FIXED) { - AnyRegister reg = interval->hint()->allocation().toRegister(); - if (!tryAllocateRegister(registers[reg.code()], interval, success, pfixed, conflicting)) + // If we want, but do not require a bundle to be in a specific register, + // only look at that register for allocating and evict or spill if it is + // not available. Picking a separate register may be even worse than + // spilling, as it will still necessitate moves and will tie up more + // registers than if we spilled. + if (hint.kind() == Requirement::FIXED) { + AnyRegister reg = hint.allocation().toRegister(); + if (!tryAllocateRegister(registers[reg.code()], bundle, success, pfixed, conflicting)) return false; if (*success) return true; } - // Spill intervals which have no hint or register requirement. - if (interval->requirement()->kind() == Requirement::NONE && - interval->hint()->kind() != Requirement::REGISTER) - { - spill(interval); + // Spill bundles which have no hint or register requirement. + if (requirement.kind() == Requirement::NONE && hint.kind() != Requirement::REGISTER) { + if (!spill(bundle)) + return false; *success = true; return true; } - if (conflicting.empty() || minimalInterval(interval)) { - // Search for any available register which the interval can be + if (conflicting.empty() || minimalBundle(bundle)) { + // Search for any available register which the bundle can be // allocated to. for (size_t i = 0; i < AnyRegister::Total; i++) { - if (!tryAllocateRegister(registers[i], interval, success, pfixed, conflicting)) + if (!tryAllocateRegister(registers[i], bundle, success, pfixed, conflicting)) return false; if (*success) return true; } } - // Spill intervals which have no register requirement if they didn't get + // Spill bundles which have no register requirement if they didn't get // allocated. - if (interval->requirement()->kind() == Requirement::NONE) { - spill(interval); + if (requirement.kind() == Requirement::NONE) { + if (!spill(bundle)) + return false; *success = true; return true; } - // We failed to allocate this interval. + // We failed to allocate this bundle. MOZ_ASSERT(!*success); return true; } bool -BacktrackingAllocator::processInterval(LiveInterval* interval) +BacktrackingAllocator::processBundle(LiveBundle* bundle) { if (JitSpewEnabled(JitSpew_RegAlloc)) { JitSpew(JitSpew_RegAlloc, "Allocating %s [priority %lu] [weight %lu]", - interval->toString(), computePriority(interval), computeSpillWeight(interval)); + bundle->toString(), computePriority(bundle), computeSpillWeight(bundle)); } - // An interval can be processed by doing any of the following: + // A bundle can be processed by doing any of the following: // - // - Assigning the interval a register. The interval cannot overlap any - // other interval allocated for that physical register. + // - Assigning the bundle a register. The bundle cannot overlap any other + // bundle allocated for that physical register. // - // - Spilling the interval, provided it has no register uses. + // - Spilling the bundle, provided it has no register uses. // - // - Splitting the interval into two or more intervals which cover the - // original one. The new intervals are placed back onto the priority - // queue for later processing. + // - Splitting the bundle into two or more bundles which cover the original + // one. The new bundles are placed back onto the priority queue for later + // processing. // - // - Evicting one or more existing allocated intervals, and then doing one - // of the above operations. Evicted intervals are placed back on the - // priority queue. Any evicted intervals must have a lower spill weight - // than the interval being processed. + // - Evicting one or more existing allocated bundles, and then doing one + // of the above operations. Evicted bundles are placed back on the + // priority queue. Any evicted bundles must have a lower spill weight + // than the bundle being processed. // // As long as this structure is followed, termination is guaranteed. - // In general, we want to minimize the amount of interval splitting - // (which generally necessitates spills), so allocate longer lived, lower - // weight intervals first and evict and split them later if they prevent - // allocation for higher weight intervals. + // In general, we want to minimize the amount of bundle splitting (which + // generally necessitates spills), so allocate longer lived, lower weight + // bundles first and evict and split them later if they prevent allocation + // for higher weight bundles. - bool canAllocate = setIntervalRequirement(interval); + Requirement requirement, hint; + bool canAllocate = computeRequirement(bundle, &requirement, &hint); bool fixed; - LiveIntervalVector conflicting; + LiveBundleVector conflicting; for (size_t attempt = 0;; attempt++) { if (canAllocate) { bool success = false; fixed = false; conflicting.clear(); - // Ok, let's try allocating for this interval. - if (interval->requirement()->kind() == Requirement::FIXED) { - if (!tryAllocateFixed(interval, &success, &fixed, conflicting)) + // Ok, let's try allocating for this bundle. + if (requirement.kind() == Requirement::FIXED) { + if (!tryAllocateFixed(bundle, requirement, &success, &fixed, conflicting)) return false; } else { - if (!tryAllocateNonFixed(interval, &success, &fixed, conflicting)) + if (!tryAllocateNonFixed(bundle, requirement, hint, &success, &fixed, conflicting)) return false; } @@ -591,146 +1257,84 @@ BacktrackingAllocator::processInterval(LiveInterval* interval) if (success) return true; - // If that didn't work, but we have one or more non-fixed intervals + // If that didn't work, but we have one or more non-fixed bundles // known to be conflicting, maybe we can evict them and try again. if (attempt < MAX_ATTEMPTS && !fixed && !conflicting.empty() && - maximumSpillWeight(conflicting) < computeSpillWeight(interval)) - { - for (size_t i = 0; i < conflicting.length(); i++) { - if (!evictInterval(conflicting[i])) - return false; + maximumSpillWeight(conflicting) < computeSpillWeight(bundle)) + { + for (size_t i = 0; i < conflicting.length(); i++) { + if (!evictBundle(conflicting[i])) + return false; + } + continue; } - continue; - } } - // A minimal interval cannot be split any further. If we try to split - // it at this point we will just end up with the same interval and will - // enter an infinite loop. Weights and the initial live intervals must - // be constructed so that any minimal interval is allocatable. - MOZ_ASSERT(!minimalInterval(interval)); + // A minimal bundle cannot be split any further. If we try to split it + // it at this point we will just end up with the same bundle and will + // enter an infinite loop. Weights and the initial live ranges must + // be constructed so that any minimal bundle is allocatable. + MOZ_ASSERT(!minimalBundle(bundle)); - LiveInterval* conflict = conflicting.empty() ? nullptr : conflicting[0]; - return chooseIntervalSplit(interval, canAllocate && fixed, conflict); + LiveBundle* conflict = conflicting.empty() ? nullptr : conflicting[0]; + return chooseBundleSplit(bundle, canAllocate && fixed, conflict); } } bool -BacktrackingAllocator::processGroup(VirtualRegisterGroup* group) +BacktrackingAllocator::computeRequirement(LiveBundle* bundle, + Requirement *requirement, Requirement *hint) { - if (JitSpewEnabled(JitSpew_RegAlloc)) { - JitSpew(JitSpew_RegAlloc, "Allocating group v%u [priority %lu] [weight %lu]", - group->registers[0], computePriority(group), computeSpillWeight(group)); - } - - bool fixed; - LiveInterval* conflict; - for (size_t attempt = 0;; attempt++) { - // Search for any available register which the group can be allocated to. - fixed = false; - conflict = nullptr; - for (size_t i = 0; i < AnyRegister::Total; i++) { - bool success; - if (!tryAllocateGroupRegister(registers[i], group, &success, &fixed, &conflict)) - return false; - if (success) { - conflict = nullptr; - break; - } - } - - if (attempt < MAX_ATTEMPTS && - !fixed && - conflict && - conflict->hasVreg() && - computeSpillWeight(conflict) < computeSpillWeight(group)) - { - if (!evictInterval(conflict)) - return false; - continue; - } - - for (size_t i = 0; i < group->registers.length(); i++) { - VirtualRegister& reg = vregs[group->registers[i]]; - MOZ_ASSERT(reg.numIntervals() <= 2); - if (!processInterval(reg.getInterval(0))) - return false; - } - - return true; - } -} - -bool -BacktrackingAllocator::setIntervalRequirement(LiveInterval* interval) -{ - // Set any requirement or hint on interval according to its definition and + // Set any requirement or hint on bundle according to its definition and // uses. Return false if there are conflicting requirements which will - // require the interval to be split. - interval->setHint(Requirement()); - interval->setRequirement(Requirement()); + // require the bundle to be split. - BacktrackingVirtualRegister* reg = &vregs[interval->vreg()]; + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + VirtualRegister ® = vregs[range->vreg()]; - // Set a hint if another interval in the same group is in a register. - if (VirtualRegisterGroup* group = reg->group()) { - if (group->allocation.isRegister()) { - if (JitSpewEnabled(JitSpew_RegAlloc)) { - JitSpew(JitSpew_RegAlloc, " Hint %s, used by group allocation", - group->allocation.toString()); - } - interval->setHint(Requirement(group->allocation)); - } - } - - if (interval->index() == 0) { - // The first interval is the definition, so deal with any definition - // constraints/hints. - - LDefinition::Policy policy = reg->def()->policy(); - if (policy == LDefinition::FIXED) { - // Fixed policies get a FIXED requirement. - if (JitSpewEnabled(JitSpew_RegAlloc)) { + if (range->hasDefinition()) { + // Deal with any definition constraints/hints. + LDefinition::Policy policy = reg.def()->policy(); + if (policy == LDefinition::FIXED) { + // Fixed policies get a FIXED requirement. JitSpew(JitSpew_RegAlloc, " Requirement %s, fixed by definition", - reg->def()->output()->toString()); + reg.def()->output()->toString()); + if (!requirement->merge(Requirement(*reg.def()->output()))) + return false; + } else if (reg.ins()->isPhi()) { + // Phis don't have any requirements, but they should prefer their + // input allocations. This is captured by the group hints above. + } else { + // Non-phis get a REGISTER requirement. + if (!requirement->merge(Requirement(Requirement::REGISTER))) + return false; } - interval->setRequirement(Requirement(*reg->def()->output())); - } else if (reg->ins()->isPhi()) { - // Phis don't have any requirements, but they should prefer their - // input allocations. This is captured by the group hints above. - } else { - // Non-phis get a REGISTER requirement. - interval->setRequirement(Requirement(Requirement::REGISTER)); } - } - // Search uses for requirements. - for (UsePositionIterator iter = interval->usesBegin(); - iter != interval->usesEnd(); - iter++) - { - LUse::Policy policy = iter->use->policy(); - if (policy == LUse::FIXED) { - AnyRegister required = GetFixedRegister(reg->def(), iter->use); + // Search uses for requirements. + for (UsePositionIterator iter = range->usesBegin(); iter; iter++) { + LUse::Policy policy = iter->use->policy(); + if (policy == LUse::FIXED) { + AnyRegister required = GetFixedRegister(reg.def(), iter->use); - if (JitSpewEnabled(JitSpew_RegAlloc)) { JitSpew(JitSpew_RegAlloc, " Requirement %s, due to use at %u", required.name(), iter->pos.bits()); - } - // If there are multiple fixed registers which the interval is - // required to use, fail. The interval will need to be split before - // it can be allocated. - if (!interval->addRequirement(Requirement(LAllocation(required)))) - return false; - } else if (policy == LUse::REGISTER) { - if (!interval->addRequirement(Requirement(Requirement::REGISTER))) - return false; - } else if (policy == LUse::ANY) { - // ANY differs from KEEPALIVE by actively preferring a register. - interval->addHint(Requirement(Requirement::REGISTER)); + // If there are multiple fixed registers which the bundle is + // required to use, fail. The bundle will need to be split before + // it can be allocated. + if (!requirement->merge(Requirement(LAllocation(required)))) + return false; + } else if (policy == LUse::REGISTER) { + if (!requirement->merge(Requirement(Requirement::REGISTER))) + return false; + } else if (policy == LUse::ANY) { + // ANY differs from KEEPALIVE by actively preferring a register. + hint->merge(Requirement(Requirement::REGISTER)); + } } } @@ -738,94 +1342,42 @@ BacktrackingAllocator::setIntervalRequirement(LiveInterval* interval) } bool -BacktrackingAllocator::tryAllocateGroupRegister(PhysicalRegister& r, VirtualRegisterGroup* group, - bool* psuccess, bool* pfixed, LiveInterval** pconflicting) -{ - *psuccess = false; - - if (!r.allocatable) - return true; - - if (!vregs[group->registers[0]].isCompatibleReg(r.reg)) - return true; - - bool allocatable = true; - LiveInterval* conflicting = nullptr; - - for (size_t i = 0; i < group->registers.length(); i++) { - VirtualRegister& reg = vregs[group->registers[i]]; - MOZ_ASSERT(reg.numIntervals() <= 2); - LiveInterval* interval = reg.getInterval(0); - - for (size_t j = 0; j < interval->numRanges(); j++) { - AllocatedRange range(interval, interval->getRange(j)), existing; - if (r.allocations.contains(range, &existing)) { - if (conflicting) { - if (conflicting != existing.interval) - return true; - } else { - conflicting = existing.interval; - } - allocatable = false; - } - } - } - - if (!allocatable) { - MOZ_ASSERT(conflicting); - if (!*pconflicting || computeSpillWeight(conflicting) < computeSpillWeight(*pconflicting)) - *pconflicting = conflicting; - if (!conflicting->hasVreg()) - *pfixed = true; - return true; - } - - *psuccess = true; - - group->allocation = LAllocation(r.reg); - return true; -} - -bool -BacktrackingAllocator::tryAllocateRegister(PhysicalRegister& r, LiveInterval* interval, - bool* success, bool* pfixed, LiveIntervalVector& conflicting) +BacktrackingAllocator::tryAllocateRegister(PhysicalRegister& r, LiveBundle* bundle, + bool* success, bool* pfixed, LiveBundleVector& conflicting) { *success = false; if (!r.allocatable) return true; - BacktrackingVirtualRegister* reg = &vregs[interval->vreg()]; - if (!reg->isCompatibleReg(r.reg)) - return true; + LiveBundleVector aliasedConflicting; - MOZ_ASSERT_IF(interval->requirement()->kind() == Requirement::FIXED, - interval->requirement()->allocation() == LAllocation(r.reg)); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + VirtualRegister ® = vregs[range->vreg()]; - LiveIntervalVector aliasedConflicting; + if (!reg.isCompatible(r.reg)) + return true; - for (size_t i = 0; i < interval->numRanges(); i++) { - AllocatedRange range(interval, interval->getRange(i)), existing; for (size_t a = 0; a < r.reg.numAliased(); a++) { PhysicalRegister& rAlias = registers[r.reg.aliased(a).code()]; + LiveRange* existing; if (!rAlias.allocations.contains(range, &existing)) continue; - if (existing.interval->hasVreg()) { - MOZ_ASSERT(existing.interval->getAllocation()->toRegister() == rAlias.reg); + if (existing->hasVreg()) { + MOZ_ASSERT(existing->bundle()->allocation().toRegister() == rAlias.reg); bool duplicate = false; for (size_t i = 0; i < aliasedConflicting.length(); i++) { - if (aliasedConflicting[i] == existing.interval) { + if (aliasedConflicting[i] == existing->bundle()) { duplicate = true; break; } } - if (!duplicate && !aliasedConflicting.append(existing.interval)) + if (!duplicate && !aliasedConflicting.append(existing->bundle())) return false; } else { - if (JitSpewEnabled(JitSpew_RegAlloc)) { - JitSpew(JitSpew_RegAlloc, " %s collides with fixed use %s", - rAlias.reg.name(), existing.range->toString()); - } + JitSpew(JitSpew_RegAlloc, " %s collides with fixed use %s", + rAlias.reg.name(), existing->toString()); *pfixed = true; return true; } @@ -833,24 +1385,22 @@ BacktrackingAllocator::tryAllocateRegister(PhysicalRegister& r, LiveInterval* in } if (!aliasedConflicting.empty()) { - // One or more aliased registers is allocated to another live interval + // One or more aliased registers is allocated to another bundle // overlapping this one. Keep track of the conflicting set, and in the // case of multiple conflicting sets keep track of the set with the // lowest maximum spill weight. if (JitSpewEnabled(JitSpew_RegAlloc)) { if (aliasedConflicting.length() == 1) { - LiveInterval* existing = aliasedConflicting[0]; - JitSpew(JitSpew_RegAlloc, " %s collides with v%u[%u] %s [weight %lu]", - r.reg.name(), existing->vreg(), existing->index(), - existing->rangesToString(), computeSpillWeight(existing)); + LiveBundle* existing = aliasedConflicting[0]; + JitSpew(JitSpew_RegAlloc, " %s collides with %s [weight %lu]", + r.reg.name(), existing->toString(), computeSpillWeight(existing)); } else { JitSpew(JitSpew_RegAlloc, " %s collides with the following", r.reg.name()); for (size_t i = 0; i < aliasedConflicting.length(); i++) { - LiveInterval* existing = aliasedConflicting[i]; - JitSpew(JitSpew_RegAlloc, " v%u[%u] %s [weight %lu]", - existing->vreg(), existing->index(), - existing->rangesToString(), computeSpillWeight(existing)); + LiveBundle* existing = aliasedConflicting[i]; + JitSpew(JitSpew_RegAlloc, " %s [weight %lu]", + existing->toString(), computeSpillWeight(existing)); } } } @@ -870,190 +1420,117 @@ BacktrackingAllocator::tryAllocateRegister(PhysicalRegister& r, LiveInterval* in JitSpew(JitSpew_RegAlloc, " allocated to %s", r.reg.name()); - for (size_t i = 0; i < interval->numRanges(); i++) { - AllocatedRange range(interval, interval->getRange(i)); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); if (!r.allocations.insert(range)) return false; } - // Set any register hint for allocating other intervals in the same group. - if (VirtualRegisterGroup* group = reg->group()) { - if (!group->allocation.isRegister()) - group->allocation = LAllocation(r.reg); - } - - interval->setAllocation(LAllocation(r.reg)); + bundle->setAllocation(LAllocation(r.reg)); *success = true; return true; } bool -BacktrackingAllocator::evictInterval(LiveInterval* interval) +BacktrackingAllocator::evictBundle(LiveBundle* bundle) { if (JitSpewEnabled(JitSpew_RegAlloc)) { JitSpew(JitSpew_RegAlloc, " Evicting %s [priority %lu] [weight %lu]", - interval->toString(), computePriority(interval), computeSpillWeight(interval)); + bundle->toString(), computePriority(bundle), computeSpillWeight(bundle)); } - MOZ_ASSERT(interval->getAllocation()->isRegister()); - - AnyRegister reg(interval->getAllocation()->toRegister()); + AnyRegister reg(bundle->allocation().toRegister()); PhysicalRegister& physical = registers[reg.code()]; MOZ_ASSERT(physical.reg == reg && physical.allocatable); - for (size_t i = 0; i < interval->numRanges(); i++) { - AllocatedRange range(interval, interval->getRange(i)); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); physical.allocations.remove(range); } - interval->setAllocation(LAllocation()); + bundle->setAllocation(LAllocation()); - size_t priority = computePriority(interval); - return allocationQueue.insert(QueueItem(interval, priority)); -} - -void -BacktrackingAllocator::distributeUses(LiveInterval* interval, - const LiveIntervalVector& newIntervals) -{ - MOZ_ASSERT(newIntervals.length() >= 2); - - // Simple redistribution of uses from an old interval to a set of new - // intervals. Intervals are permitted to overlap, in which case this will - // assign uses in the overlapping section to the interval with the latest - // start position. - for (UsePositionIterator iter(interval->usesBegin()); - iter != interval->usesEnd(); - iter++) - { - CodePosition pos = iter->pos; - LiveInterval* addInterval = nullptr; - for (size_t i = 0; i < newIntervals.length(); i++) { - LiveInterval* newInterval = newIntervals[i]; - if (newInterval->covers(pos)) { - if (!addInterval || newInterval->start() < addInterval->start()) - addInterval = newInterval; - } - } - addInterval->addUseAtEnd(new(alloc()) UsePosition(iter->use, iter->pos)); - } + size_t priority = computePriority(bundle); + return allocationQueue.insert(QueueItem(bundle, priority)); } bool -BacktrackingAllocator::split(LiveInterval* interval, - const LiveIntervalVector& newIntervals) +BacktrackingAllocator::splitAndRequeueBundles(LiveBundle* bundle, + const LiveBundleVector& newBundles) { if (JitSpewEnabled(JitSpew_RegAlloc)) { - JitSpew(JitSpew_RegAlloc, " splitting interval %s into:", interval->toString()); - for (size_t i = 0; i < newIntervals.length(); i++) { - JitSpew(JitSpew_RegAlloc, " %s", newIntervals[i]->toString()); - MOZ_ASSERT(newIntervals[i]->start() >= interval->start()); - MOZ_ASSERT(newIntervals[i]->end() <= interval->end()); + JitSpew(JitSpew_RegAlloc, " splitting bundle %s into:", bundle->toString()); + for (size_t i = 0; i < newBundles.length(); i++) + JitSpew(JitSpew_RegAlloc, " %s", newBundles[i]->toString()); + } + + // Remove all ranges in the old bundle from their register's list. + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + vregs[range->vreg()].removeRange(range); + } + + // Add all ranges in the new bundles to their register's list. + for (size_t i = 0; i < newBundles.length(); i++) { + LiveBundle* newBundle = newBundles[i]; + for (LiveRange::BundleLinkIterator iter = newBundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + vregs[range->vreg()].addRange(range); } } - MOZ_ASSERT(newIntervals.length() >= 2); - - // Find the earliest interval in the new list. - LiveInterval* first = newIntervals[0]; - for (size_t i = 1; i < newIntervals.length(); i++) { - if (newIntervals[i]->start() < first->start()) - first = newIntervals[i]; - } - - // Replace the old interval in the virtual register's state with the new intervals. - VirtualRegister* reg = &vregs[interval->vreg()]; - reg->replaceInterval(interval, first); - for (size_t i = 0; i < newIntervals.length(); i++) { - if (newIntervals[i] != first && !reg->addInterval(newIntervals[i])) + // Queue the new bundles for register assignment. + for (size_t i = 0; i < newBundles.length(); i++) { + LiveBundle* newBundle = newBundles[i]; + size_t priority = computePriority(newBundle); + if (!allocationQueue.insert(QueueItem(newBundle, priority))) return false; } return true; } -bool BacktrackingAllocator::requeueIntervals(const LiveIntervalVector& newIntervals) +bool +BacktrackingAllocator::spill(LiveBundle* bundle) { - // Queue the new intervals for register assignment. - for (size_t i = 0; i < newIntervals.length(); i++) { - LiveInterval* newInterval = newIntervals[i]; - size_t priority = computePriority(newInterval); - if (!allocationQueue.insert(QueueItem(newInterval, priority))) - return false; - } - return true; -} + JitSpew(JitSpew_RegAlloc, " Spilling bundle"); + MOZ_ASSERT(bundle->allocation().isBogus()); -void -BacktrackingAllocator::spill(LiveInterval* interval) -{ - JitSpew(JitSpew_RegAlloc, " Spilling interval"); - - MOZ_ASSERT(interval->requirement()->kind() == Requirement::NONE); - MOZ_ASSERT(!interval->getAllocation()->isStackSlot()); - - // We can't spill bogus intervals. - MOZ_ASSERT(interval->hasVreg()); - - BacktrackingVirtualRegister* reg = &vregs[interval->vreg()]; - - if (LiveInterval* spillInterval = interval->spillInterval()) { - JitSpew(JitSpew_RegAlloc, " Spilling to existing spill interval"); - while (!interval->usesEmpty()) - spillInterval->addUse(interval->popUse()); - reg->removeInterval(interval); - return; - } - - bool useCanonical = !reg->hasCanonicalSpillExclude() - || interval->start() < reg->canonicalSpillExclude(); - - if (useCanonical) { - if (reg->canonicalSpill()) { - JitSpew(JitSpew_RegAlloc, " Picked canonical spill location %s", - reg->canonicalSpill()->toString()); - interval->setAllocation(*reg->canonicalSpill()); - return; - } - - if (reg->group() && !reg->group()->spill.isUse()) { - JitSpew(JitSpew_RegAlloc, " Reusing group spill location %s", - reg->group()->spill.toString()); - interval->setAllocation(reg->group()->spill); - reg->setCanonicalSpill(reg->group()->spill); - return; + if (LiveBundle* spillParent = bundle->spillParent()) { + JitSpew(JitSpew_RegAlloc, " Using existing spill bundle"); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveRange* parentRange = spillParent->rangeFor(range->from()); + MOZ_ASSERT(parentRange->contains(range)); + MOZ_ASSERT(range->vreg() == parentRange->vreg()); + range->distributeUses(parentRange); + MOZ_ASSERT(!range->hasUses()); + vregs[range->vreg()].removeRange(range); } + return true; } - uint32_t virtualSlot = numVirtualStackSlots++; - - // Count virtual stack slots down from the maximum representable value, so - // that virtual slots are more obviously distinguished from real slots. - LStackSlot alloc(LAllocation::DATA_MASK - virtualSlot); - interval->setAllocation(alloc); - - JitSpew(JitSpew_RegAlloc, " Allocating spill location %s", alloc.toString()); - - if (useCanonical) { - reg->setCanonicalSpill(alloc); - if (reg->group()) - reg->group()->spill = alloc; - } + return bundle->spillSet()->addSpilledBundle(bundle); } bool BacktrackingAllocator::pickStackSlots() { for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { - BacktrackingVirtualRegister* reg = &vregs[i]; + VirtualRegister& reg = vregs[i]; if (mir->shouldCancel("Backtracking Pick Stack Slots")) return false; - for (size_t j = 0; j < reg->numIntervals(); j++) { - LiveInterval* interval = reg->getInterval(j); - if (!pickStackSlot(interval)) - return false; + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveBundle* bundle = range->bundle(); + + if (bundle->allocation().isBogus()) { + if (!pickStackSlot(bundle->spillSet())) + return false; + MOZ_ASSERT(!bundle->allocation().isBogus()); + } } } @@ -1061,69 +1538,31 @@ BacktrackingAllocator::pickStackSlots() } bool -BacktrackingAllocator::pickStackSlot(LiveInterval* interval) +BacktrackingAllocator::pickStackSlot(SpillSet* spillSet) { - LAllocation alloc = *interval->getAllocation(); - MOZ_ASSERT(!alloc.isUse()); - - if (!isVirtualStackSlot(alloc)) - return true; - - BacktrackingVirtualRegister& reg = vregs[interval->vreg()]; - - // Get a list of all the intervals which will share this stack slot. - LiveIntervalVector commonIntervals; - - if (!commonIntervals.append(interval)) - return false; - - if (reg.canonicalSpill() && alloc == *reg.canonicalSpill()) { - // Look for other intervals in the vreg using this spill slot. - for (size_t i = 0; i < reg.numIntervals(); i++) { - LiveInterval* ninterval = reg.getInterval(i); - if (ninterval != interval && *ninterval->getAllocation() == alloc) { - if (!commonIntervals.append(ninterval)) - return false; - } - } - - // Look for intervals in other registers with the same group using this - // spill slot. - if (reg.group() && alloc == reg.group()->spill) { - for (size_t i = 0; i < reg.group()->registers.length(); i++) { - uint32_t nvreg = reg.group()->registers[i]; - if (nvreg == interval->vreg()) - continue; - BacktrackingVirtualRegister& nreg = vregs[nvreg]; - for (size_t j = 0; j < nreg.numIntervals(); j++) { - LiveInterval* ninterval = nreg.getInterval(j); - if (*ninterval->getAllocation() == alloc) { - if (!commonIntervals.append(ninterval)) - return false; - } + // Look through all ranges that have been spilled in this set for a + // register definition which is fixed to a stack or argument slot. If we + // find one, use it for all bundles that have been spilled. tryMergeBundles + // makes sure this reuse is possible when an initial bundle contains ranges + // from multiple virtual registers. + for (size_t i = 0; i < spillSet->numSpilledBundles(); i++) { + LiveBundle* bundle = spillSet->spilledBundle(i); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + if (range->hasDefinition()) { + LDefinition* def = vregs[range->vreg()].def(); + if (def->policy() == LDefinition::FIXED) { + MOZ_ASSERT(!def->output()->isRegister()); + MOZ_ASSERT(!def->output()->isStackSlot()); + spillSet->setAllocation(*def->output()); + return true; } } } - } else { - MOZ_ASSERT_IF(reg.group(), alloc != reg.group()->spill); } - if (!reuseOrAllocateStackSlot(commonIntervals, reg.type(), &alloc)) - return false; + LDefinition::Type type = vregs[spillSet->spilledBundle(0)->firstRange()->vreg()].type(); - MOZ_ASSERT(!isVirtualStackSlot(alloc)); - - // Set the physical stack slot for each of the intervals found earlier. - for (size_t i = 0; i < commonIntervals.length(); i++) - commonIntervals[i]->setAllocation(alloc); - - return true; -} - -bool -BacktrackingAllocator::reuseOrAllocateStackSlot(const LiveIntervalVector& intervals, LDefinition::Type type, - LAllocation* palloc) -{ SpillSlotList* slotList; switch (StackSlotAllocator::width(type)) { case 4: slotList = &normalSlots; break; @@ -1137,108 +1576,125 @@ BacktrackingAllocator::reuseOrAllocateStackSlot(const LiveIntervalVector& interv // and allocating a new slot. static const size_t MAX_SEARCH_COUNT = 10; - if (!slotList->empty()) { - size_t searches = 0; - SpillSlot* stop = nullptr; - while (true) { - SpillSlot* spill = *slotList->begin(); - if (!stop) { - stop = spill; - } else if (stop == spill) { - // We looked through every slot in the list. - break; - } + size_t searches = 0; + SpillSlot* stop = nullptr; + while (!slotList->empty()) { + SpillSlot* spillSlot = *slotList->begin(); + if (!stop) { + stop = spillSlot; + } else if (stop == spillSlot) { + // We looked through every slot in the list. + break; + } - bool success = true; - for (size_t i = 0; i < intervals.length() && success; i++) { - LiveInterval* interval = intervals[i]; - for (size_t j = 0; j < interval->numRanges(); j++) { - AllocatedRange range(interval, interval->getRange(j)), existing; - if (spill->allocated.contains(range, &existing)) { - success = false; - break; - } + bool success = true; + for (size_t i = 0; i < spillSet->numSpilledBundles(); i++) { + LiveBundle* bundle = spillSet->spilledBundle(i); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveRange* existing; + if (spillSlot->allocated.contains(range, &existing)) { + success = false; + break; } } - if (success) { - // We can reuse this physical stack slot for the new intervals. - // Update the allocated ranges for the slot. - if (!insertAllRanges(spill->allocated, intervals)) - return false; - *palloc = spill->alloc; - return true; - } - - // On a miss, move the spill to the end of the list. This will cause us - // to make fewer attempts to allocate from slots with a large and - // highly contended range. - slotList->popFront(); - slotList->pushBack(spill); - - if (++searches == MAX_SEARCH_COUNT) + if (!success) break; } + if (success) { + // We can reuse this physical stack slot for the new bundles. + // Update the allocated ranges for the slot. + for (size_t i = 0; i < spillSet->numSpilledBundles(); i++) { + LiveBundle* bundle = spillSet->spilledBundle(i); + if (!insertAllRanges(spillSlot->allocated, bundle)) + return false; + } + spillSet->setAllocation(spillSlot->alloc); + return true; + } + + // On a miss, move the spill to the end of the list. This will cause us + // to make fewer attempts to allocate from slots with a large and + // highly contended range. + slotList->popFront(); + slotList->pushBack(spillSlot); + + if (++searches == MAX_SEARCH_COUNT) + break; } // We need a new physical stack slot. uint32_t stackSlot = stackSlotAllocator.allocateSlot(type); - // Make sure the virtual and physical stack slots don't start overlapping. - if (isVirtualStackSlot(LStackSlot(stackSlot))) + SpillSlot* spillSlot = new(alloc()) SpillSlot(stackSlot, alloc().lifoAlloc()); + if (!spillSlot) return false; - SpillSlot* spill = new(alloc()) SpillSlot(stackSlot, alloc().lifoAlloc()); - if (!spill) - return false; + for (size_t i = 0; i < spillSet->numSpilledBundles(); i++) { + LiveBundle* bundle = spillSet->spilledBundle(i); + if (!insertAllRanges(spillSlot->allocated, bundle)) + return false; + } - if (!insertAllRanges(spill->allocated, intervals)) - return false; + spillSet->setAllocation(spillSlot->alloc); - *palloc = spill->alloc; - - slotList->pushFront(spill); + slotList->pushFront(spillSlot); return true; } bool -BacktrackingAllocator::insertAllRanges(AllocatedRangeSet& set, const LiveIntervalVector& intervals) +BacktrackingAllocator::insertAllRanges(LiveRangeSet& set, LiveBundle* bundle) { - for (size_t i = 0; i < intervals.length(); i++) { - LiveInterval* interval = intervals[i]; - for (size_t j = 0; j < interval->numRanges(); j++) { - AllocatedRange range(interval, interval->getRange(j)); - if (!set.insert(range)) - return false; - } + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + if (!set.insert(range)) + return false; } return true; } -// Add moves to resolve conflicting assignments between a block and its -// predecessors. bool BacktrackingAllocator::resolveControlFlow() { + // Add moves to handle changing assignments for vregs over their lifetime. JitSpew(JitSpew_RegAlloc, "Resolving control flow (vreg loop)"); - // Virtual register number 0 is unused. - MOZ_ASSERT(vregs[0u].numIntervals() == 0); + // Look for places where a register's assignment changes in the middle of a + // basic block. + MOZ_ASSERT(!vregs[0u].hasRanges()); for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { - BacktrackingVirtualRegister* reg = &vregs[i]; + VirtualRegister& reg = vregs[i]; if (mir->shouldCancel("Backtracking Resolve Control Flow (vreg loop)")) return false; - for (size_t j = 1; j < reg->numIntervals(); j++) { - LiveInterval* interval = reg->getInterval(j); - MOZ_ASSERT(interval->index() == j); + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + // The range which defines the register does not have a predecessor + // to add moves from. + if (range->hasDefinition()) + continue; + + // Ignore ranges that start at block boundaries. We will handle + // these in the next phase. + CodePosition start = range->from(); + LNode* ins = insData[start]; + if (start == entryOf(ins->block())) + continue; + + // If we already saw a range which covers the start of this range + // and has the same allocation, we don't need an explicit move at + // the start of this range. bool skip = false; - for (int k = j - 1; k >= 0; k--) { - LiveInterval* prevInterval = reg->getInterval(k); - if (prevInterval->start() != interval->start()) - break; - if (*prevInterval->getAllocation() == *interval->getAllocation()) { + for (LiveRange::RegisterLinkIterator prevIter = reg.rangesBegin(); + prevIter != iter; + prevIter++) + { + LiveRange* prevRange = LiveRange::get(*prevIter); + if (prevRange->covers(start) && + prevRange->bundle()->allocation() == range->bundle()->allocation()) + { skip = true; break; } @@ -1246,19 +1702,13 @@ BacktrackingAllocator::resolveControlFlow() if (skip) continue; - CodePosition start = interval->start(); - LNode* ins = insData[start]; - if (start > entryOf(ins->block())) { - MOZ_ASSERT(start == inputOf(ins) || start == outputOf(ins)); - - LiveInterval* prevInterval = reg->intervalFor(start.previous()); - if (start.subpos() == CodePosition::INPUT) { - if (!moveInput(ins->toInstruction(), prevInterval, interval, reg->type())) - return false; - } else { - if (!moveAfter(ins->toInstruction(), prevInterval, interval, reg->type())) - return false; - } + LiveRange* predecessorRange = reg.rangeFor(start.previous()); + if (start.subpos() == CodePosition::INPUT) { + if (!moveInput(ins->toInstruction(), predecessorRange, range, reg.type())) + return false; + } else { + if (!moveAfter(ins->toInstruction(), predecessorRange, range, reg.type())) + return false; } } } @@ -1274,13 +1724,13 @@ BacktrackingAllocator::resolveControlFlow() if (mSuccessor->numPredecessors() < 1) continue; - // Resolve phis to moves + // Resolve phis to moves. for (size_t j = 0; j < successor->numPhis(); j++) { LPhi* phi = successor->getPhi(j); MOZ_ASSERT(phi->numDefs() == 1); LDefinition* def = phi->getDef(0); - VirtualRegister* vreg = &vregs[def]; - LiveInterval* to = vreg->intervalFor(entryOf(successor)); + VirtualRegister& reg = vreg(def); + LiveRange* to = reg.rangeFor(entryOf(successor)); MOZ_ASSERT(to); for (size_t k = 0; k < mSuccessor->numPredecessors(); k++) { @@ -1288,7 +1738,7 @@ BacktrackingAllocator::resolveControlFlow() MOZ_ASSERT(predecessor->mir()->numSuccessors() == 1); LAllocation* input = phi->getOperand(k); - LiveInterval* from = vregs[input].intervalFor(exitOf(predecessor)); + LiveRange* from = vreg(input).rangeFor(exitOf(predecessor)); MOZ_ASSERT(from); if (!moveAtExit(predecessor, from, to, def->type())) @@ -1296,7 +1746,8 @@ BacktrackingAllocator::resolveControlFlow() } } - // Resolve split intervals with moves + // Add moves to resolve graph edges with different allocations at their + // source and target. BitSet& live = liveIn[mSuccessor->id()]; for (BitSet::Iterator liveRegId(live); liveRegId; ++liveRegId) { @@ -1305,14 +1756,14 @@ BacktrackingAllocator::resolveControlFlow() for (size_t j = 0; j < mSuccessor->numPredecessors(); j++) { LBlock* predecessor = mSuccessor->getPredecessor(j)->lir(); - for (size_t k = 0; k < reg.numIntervals(); k++) { - LiveInterval* to = reg.getInterval(k); + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) { + LiveRange* to = LiveRange::get(*iter); if (!to->covers(entryOf(successor))) continue; if (to->covers(exitOf(predecessor))) continue; - LiveInterval* from = reg.intervalFor(exitOf(predecessor)); + LiveRange* from = reg.rangeFor(exitOf(predecessor)); if (mSuccessor->numPredecessors() > 1) { MOZ_ASSERT(predecessor->mir()->numSuccessors() == 1); @@ -1355,12 +1806,12 @@ BacktrackingAllocator::isRegisterUse(LUse* use, LNode* ins, bool considerCopy) } bool -BacktrackingAllocator::isRegisterDefinition(LiveInterval* interval) +BacktrackingAllocator::isRegisterDefinition(LiveRange* range) { - if (interval->index() != 0) + if (!range->hasDefinition()) return false; - VirtualRegister& reg = vregs[interval->vreg()]; + VirtualRegister& reg = vregs[range->vreg()]; if (reg.ins()->isPhi()) return false; @@ -1375,56 +1826,50 @@ BacktrackingAllocator::reifyAllocations() { JitSpew(JitSpew_RegAlloc, "Reifying Allocations"); - // Virtual register number 0 is unused. - MOZ_ASSERT(vregs[0u].numIntervals() == 0); + MOZ_ASSERT(!vregs[0u].hasRanges()); for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { - VirtualRegister* reg = &vregs[i]; + VirtualRegister& reg = vregs[i]; if (mir->shouldCancel("Backtracking Reify Allocations (main loop)")) return false; - for (size_t j = 0; j < reg->numIntervals(); j++) { - LiveInterval* interval = reg->getInterval(j); - MOZ_ASSERT(interval->index() == j); + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); - if (interval->index() == 0) { - reg->def()->setOutput(*interval->getAllocation()); - if (reg->ins()->recoversInput()) { - LSnapshot* snapshot = reg->ins()->toInstruction()->snapshot(); + if (range->hasDefinition()) { + reg.def()->setOutput(range->bundle()->allocation()); + if (reg.ins()->recoversInput()) { + LSnapshot* snapshot = reg.ins()->toInstruction()->snapshot(); for (size_t i = 0; i < snapshot->numEntries(); i++) { LAllocation* entry = snapshot->getEntry(i); if (entry->isUse() && entry->toUse()->policy() == LUse::RECOVERED_INPUT) - *entry = *reg->def()->output(); + *entry = *reg.def()->output(); } } } - for (UsePositionIterator iter(interval->usesBegin()); - iter != interval->usesEnd(); - iter++) - { + for (UsePositionIterator iter(range->usesBegin()); iter; iter++) { LAllocation* alloc = iter->use; - *alloc = *interval->getAllocation(); + *alloc = range->bundle()->allocation(); // For any uses which feed into MUST_REUSE_INPUT definitions, // add copies if the use and def have different allocations. LNode* ins = insData[iter->pos]; if (LDefinition* def = FindReusingDefinition(ins, alloc)) { - LiveInterval* outputInterval = - vregs[def->virtualRegister()].intervalFor(outputOf(ins)); - LAllocation* res = outputInterval->getAllocation(); - LAllocation* sourceAlloc = interval->getAllocation(); + LiveRange* outputRange = vreg(def).rangeFor(outputOf(ins)); + LAllocation res = outputRange->bundle()->allocation(); + LAllocation sourceAlloc = range->bundle()->allocation(); - if (*res != *alloc) { + if (res != *alloc) { LMoveGroup* group = getInputMoveGroup(ins->toInstruction()); - if (!group->addAfter(sourceAlloc, res, reg->type())) + if (!group->addAfter(sourceAlloc, res, reg.type())) return false; - *alloc = *res; + *alloc = res; } } } - addLiveRegistersForInterval(reg, interval); + addLiveRegistersForRange(reg, range); } } @@ -1432,6 +1877,104 @@ BacktrackingAllocator::reifyAllocations() return true; } +size_t +BacktrackingAllocator::findFirstNonCallSafepoint(CodePosition from) +{ + size_t i = 0; + for (; i < graph.numNonCallSafepoints(); i++) { + const LInstruction* ins = graph.getNonCallSafepoint(i); + if (from <= inputOf(ins)) + break; + } + return i; +} + +void +BacktrackingAllocator::addLiveRegistersForRange(VirtualRegister& reg, LiveRange* range) +{ + // Fill in the live register sets for all non-call safepoints. + LAllocation a = range->bundle()->allocation(); + if (!a.isRegister()) + return; + + // Don't add output registers to the safepoint. + CodePosition start = range->from(); + if (range->hasDefinition() && !reg.isTemp()) { +#ifdef CHECK_OSIPOINT_REGISTERS + // We don't add the output register to the safepoint, + // but it still might get added as one of the inputs. + // So eagerly add this reg to the safepoint clobbered registers. + if (reg.ins()->isInstruction()) { + if (LSafepoint* safepoint = reg.ins()->toInstruction()->safepoint()) + safepoint->addClobberedRegister(a.toRegister()); + } +#endif + start = start.next(); + } + + size_t i = findFirstNonCallSafepoint(start); + for (; i < graph.numNonCallSafepoints(); i++) { + LInstruction* ins = graph.getNonCallSafepoint(i); + CodePosition pos = inputOf(ins); + + // Safepoints are sorted, so we can shortcut out of this loop + // if we go out of range. + if (range->to() <= pos) + break; + + MOZ_ASSERT(range->covers(pos)); + + LSafepoint* safepoint = ins->safepoint(); + safepoint->addLiveRegister(a.toRegister()); + +#ifdef CHECK_OSIPOINT_REGISTERS + if (reg.isTemp()) + safepoint->addClobberedRegister(a.toRegister()); +#endif + } +} + +static inline bool +IsNunbox(VirtualRegister& reg) +{ +#ifdef JS_NUNBOX32 + return reg.type() == LDefinition::TYPE || + reg.type() == LDefinition::PAYLOAD; +#else + return false; +#endif +} + +static inline bool +IsSlotsOrElements(VirtualRegister& reg) +{ + return reg.type() == LDefinition::SLOTS; +} + +static inline bool +IsTraceable(VirtualRegister& reg) +{ + if (reg.type() == LDefinition::OBJECT) + return true; +#ifdef JS_PUNBOX64 + if (reg.type() == LDefinition::BOX) + return true; +#endif + return false; +} + +size_t +BacktrackingAllocator::findFirstSafepoint(CodePosition pos, size_t startFrom) +{ + size_t i = startFrom; + for (; i < graph.numSafepoints(); i++) { + LInstruction* ins = graph.getSafepoint(i); + if (pos <= inputOf(ins)) + break; + } + return i; +} + bool BacktrackingAllocator::populateSafepoints() { @@ -1439,73 +1982,65 @@ BacktrackingAllocator::populateSafepoints() size_t firstSafepoint = 0; - // Virtual register number 0 is unused. MOZ_ASSERT(!vregs[0u].def()); - for (uint32_t i = 1; i < vregs.numVirtualRegisters(); i++) { - BacktrackingVirtualRegister* reg = &vregs[i]; + for (uint32_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; - if (!reg->def() || (!IsTraceable(reg) && !IsSlotsOrElements(reg) && !IsNunbox(reg))) + if (!reg.def() || (!IsTraceable(reg) && !IsSlotsOrElements(reg) && !IsNunbox(reg))) continue; - firstSafepoint = findFirstSafepoint(reg->getInterval(0), firstSafepoint); + firstSafepoint = findFirstSafepoint(inputOf(reg.ins()), firstSafepoint); if (firstSafepoint >= graph.numSafepoints()) break; - // Find the furthest endpoint. Intervals are sorted, but by start - // position, and we want the greatest end position. - CodePosition end = reg->getInterval(0)->end(); - for (size_t j = 1; j < reg->numIntervals(); j++) - end = Max(end, reg->getInterval(j)->end()); + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); - for (size_t j = firstSafepoint; j < graph.numSafepoints(); j++) { - LInstruction* ins = graph.getSafepoint(j); + for (size_t j = firstSafepoint; j < graph.numSafepoints(); j++) { + LInstruction* ins = graph.getSafepoint(j); - // Stop processing safepoints if we know we're out of this virtual - // register's range. - if (end < outputOf(ins)) - break; + if (!range->covers(inputOf(ins))) { + if (inputOf(ins) >= range->to()) + break; + continue; + } - // Include temps but not instruction outputs. Also make sure MUST_REUSE_INPUT - // is not used with gcthings or nunboxes, or we would have to add the input reg - // to this safepoint. - if (ins == reg->ins() && !reg->isTemp()) { - DebugOnly def = reg->def(); - MOZ_ASSERT_IF(def->policy() == LDefinition::MUST_REUSE_INPUT, - def->type() == LDefinition::GENERAL || - def->type() == LDefinition::INT32 || - def->type() == LDefinition::FLOAT32 || - def->type() == LDefinition::DOUBLE); - continue; - } + // Include temps but not instruction outputs. Also make sure + // MUST_REUSE_INPUT is not used with gcthings or nunboxes, or + // we would have to add the input reg to this safepoint. + if (ins == reg.ins() && !reg.isTemp()) { + DebugOnly def = reg.def(); + MOZ_ASSERT_IF(def->policy() == LDefinition::MUST_REUSE_INPUT, + def->type() == LDefinition::GENERAL || + def->type() == LDefinition::INT32 || + def->type() == LDefinition::FLOAT32 || + def->type() == LDefinition::DOUBLE); + continue; + } - LSafepoint* safepoint = ins->safepoint(); + LSafepoint* safepoint = ins->safepoint(); - for (size_t k = 0; k < reg->numIntervals(); k++) { - LiveInterval* interval = reg->getInterval(k); - if (!interval->covers(inputOf(ins))) + LAllocation a = range->bundle()->allocation(); + if (a.isGeneralReg() && ins->isCall()) continue; - LAllocation* a = interval->getAllocation(); - if (a->isGeneralReg() && ins->isCall()) - continue; - - switch (reg->type()) { + switch (reg.type()) { case LDefinition::OBJECT: - safepoint->addGcPointer(*a); + safepoint->addGcPointer(a); break; case LDefinition::SLOTS: - safepoint->addSlotsOrElementsPointer(*a); + safepoint->addSlotsOrElementsPointer(a); break; #ifdef JS_NUNBOX32 case LDefinition::TYPE: - safepoint->addNunboxType(i, *a); + safepoint->addNunboxType(i, a); break; case LDefinition::PAYLOAD: - safepoint->addNunboxPayload(i, *a); + safepoint->addNunboxPayload(i, a); break; #else case LDefinition::BOX: - safepoint->addBoxedValue(*a); + safepoint->addBoxedValue(a); break; #endif default: @@ -1526,6 +2061,10 @@ BacktrackingAllocator::annotateMoveGroups() // only required for x86, as other platforms always have scratch registers // available for use. #ifdef JS_CODEGEN_X86 + LiveRange* range = LiveRange::New(alloc(), 0, CodePosition(), CodePosition().next()); + if (!range) + return false; + for (size_t i = 0; i < graph.numBlocks(); i++) { if (mir->shouldCancel("Backtracking Annotate Move Groups")) return false; @@ -1535,8 +2074,8 @@ BacktrackingAllocator::annotateMoveGroups() for (LInstructionIterator iter = block->begin(); iter != block->end(); ++iter) { if (iter->isMoveGroup()) { CodePosition from = last ? outputOf(last) : entryOf(block); - LiveInterval::Range range(from, from.next()); - AllocatedRange search(nullptr, &range), existing; + range->setTo(from.next()); + range->setFrom(from); for (size_t i = 0; i < AnyRegister::Total; i++) { PhysicalRegister& reg = registers[i]; @@ -1544,9 +2083,9 @@ BacktrackingAllocator::annotateMoveGroups() continue; // This register is unavailable for use if (a) it is in use - // by some live interval immediately before the move group, + // by some live range immediately before the move group, // or (b) it is an operand in one of the group's moves. The - // latter case handles live intervals which end immediately + // latter case handles live ranges which end immediately // before the move group or start immediately after. // For (b) we need to consider move groups immediately // preceding or following this one. @@ -1580,7 +2119,8 @@ BacktrackingAllocator::annotateMoveGroups() } while (riter != block->begin()); } - if (found || reg.allocations.contains(search, &existing)) + LiveRange* existing; + if (found || reg.allocations.contains(range, &existing)) continue; iter->toMoveGroup()->setScratchRegister(reg.reg.gpr()); @@ -1596,29 +2136,106 @@ BacktrackingAllocator::annotateMoveGroups() return true; } +///////////////////////////////////////////////////////////////////// +// Debugging methods +///////////////////////////////////////////////////////////////////// + +#ifdef DEBUG + +const char* +LiveRange::toString() const +{ + // Not reentrant! + static char buf[2000]; + + char* cursor = buf; + char* end = cursor + sizeof(buf); + + int n = JS_snprintf(cursor, end - cursor, "v%u [%u,%u)", + hasVreg() ? vreg() : 0, from().bits(), to().bits()); + if (n < 0) MOZ_CRASH(); + cursor += n; + + if (bundle() && !bundle()->allocation().isBogus()) { + n = JS_snprintf(cursor, end - cursor, " %s", bundle()->allocation().toString()); + if (n < 0) MOZ_CRASH(); + cursor += n; + } + + if (hasDefinition()) { + n = JS_snprintf(cursor, end - cursor, " (def)"); + if (n < 0) MOZ_CRASH(); + cursor += n; + } + + for (UsePositionIterator iter = usesBegin(); iter; iter++) { + n = JS_snprintf(cursor, end - cursor, " %s@%u", iter->use->toString(), iter->pos.bits()); + if (n < 0) MOZ_CRASH(); + cursor += n; + } + + return buf; +} + +const char* +LiveBundle::toString() const +{ + // Not reentrant! + static char buf[2000]; + + char* cursor = buf; + char* end = cursor + sizeof(buf); + + for (LiveRange::BundleLinkIterator iter = rangesBegin(); iter; iter++) { + int n = JS_snprintf(cursor, end - cursor, "%s %s", + (iter == rangesBegin()) ? "" : " ##", + LiveRange::get(*iter)->toString()); + if (n < 0) MOZ_CRASH(); + cursor += n; + } + + return buf; +} + +#endif // DEBUG + void -BacktrackingAllocator::dumpRegisterGroups() +BacktrackingAllocator::dumpVregs() { #ifdef DEBUG - bool any = false; + MOZ_ASSERT(!vregs[0u].hasRanges()); - // Virtual register number 0 is unused. - MOZ_ASSERT(!vregs[0u].group()); - for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { - VirtualRegisterGroup* group = vregs[i].group(); - if (group && i == group->canonicalReg()) { - if (!any) { - fprintf(stderr, "Register groups:\n"); - any = true; + fprintf(stderr, "Live ranges by virtual register:\n"); + + for (uint32_t i = 1; i < graph.numVirtualRegisters(); i++) { + fprintf(stderr, " "); + VirtualRegister& reg = vregs[i]; + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) { + if (iter != reg.rangesBegin()) + fprintf(stderr, " ## "); + fprintf(stderr, "%s", LiveRange::get(*iter)->toString()); + } + fprintf(stderr, "\n"); + } + + fprintf(stderr, "\nLive ranges by bundle:\n"); + + for (uint32_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + for (LiveRange::RegisterLinkIterator baseIter = reg.rangesBegin(); baseIter; baseIter++) { + LiveRange* range = LiveRange::get(*baseIter); + LiveBundle* bundle = range->bundle(); + if (range == bundle->firstRange()) { + fprintf(stderr, " "); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + if (iter != bundle->rangesBegin()) + fprintf(stderr, " ## "); + fprintf(stderr, "%s", LiveRange::get(*iter)->toString()); + } + fprintf(stderr, "\n"); } - fprintf(stderr, " "); - for (size_t j = 0; j < group->registers.length(); j++) - fprintf(stderr, " v%u", group->registers[j]); - fprintf(stderr, "\n"); } } - if (any) - fprintf(stderr, "\n"); #endif } @@ -1626,41 +2243,24 @@ void BacktrackingAllocator::dumpFixedRanges() { #ifdef DEBUG - bool any = false; - - for (size_t i = 0; i < AnyRegister::Total; i++) { - if (registers[i].allocatable && fixedIntervals[i]->numRanges() != 0) { - if (!any) { - fprintf(stderr, "Live ranges by physical register:\n"); - any = true; - } - fprintf(stderr, " %s: %s\n", AnyRegister::FromCode(i).name(), fixedIntervals[i]->toString()); - } - } - - if (any) - fprintf(stderr, "\n"); + fprintf(stderr, "Live ranges by physical register: %s\n", callRanges->toString()); #endif // DEBUG } #ifdef DEBUG -struct BacktrackingAllocator::PrintLiveIntervalRange +struct BacktrackingAllocator::PrintLiveRange { bool& first_; - explicit PrintLiveIntervalRange(bool& first) : first_(first) {} + explicit PrintLiveRange(bool& first) : first_(first) {} - void operator()(const AllocatedRange& item) + void operator()(const LiveRange* range) { - if (item.range == item.interval->getRange(0)) { - if (first_) - first_ = false; - else - fprintf(stderr, " /"); - if (item.interval->hasVreg()) - fprintf(stderr, " v%u[%u]", item.interval->vreg(), item.interval->index()); - fprintf(stderr, "%s", item.interval->rangesToString()); - } + if (first_) + first_ = false; + else + fprintf(stderr, " /"); + fprintf(stderr, " %s", range->toString()); } }; #endif @@ -1669,7 +2269,7 @@ void BacktrackingAllocator::dumpAllocations() { #ifdef DEBUG - fprintf(stderr, "Allocations by virtual register:\n"); + fprintf(stderr, "Allocations:\n"); dumpVregs(); @@ -1679,7 +2279,7 @@ BacktrackingAllocator::dumpAllocations() if (registers[i].allocatable && !registers[i].allocations.empty()) { fprintf(stderr, " %s:", AnyRegister::FromCode(i).name()); bool first = true; - registers[i].allocations.forEach(PrintLiveIntervalRange(first)); + registers[i].allocations.forEach(PrintLiveRange(first)); fprintf(stderr, "\n"); } } @@ -1688,82 +2288,69 @@ BacktrackingAllocator::dumpAllocations() #endif // DEBUG } -bool -BacktrackingAllocator::addLiveInterval(LiveIntervalVector& intervals, uint32_t vreg, - LiveInterval* spillInterval, - CodePosition from, CodePosition to) -{ - LiveInterval* interval = LiveInterval::New(alloc(), vreg, 0); - interval->setSpillInterval(spillInterval); - return interval->addRange(from, to) && intervals.append(interval); -} - /////////////////////////////////////////////////////////////////////////////// // Heuristic Methods /////////////////////////////////////////////////////////////////////////////// size_t -BacktrackingAllocator::computePriority(const LiveInterval* interval) +BacktrackingAllocator::computePriority(LiveBundle* bundle) { - // The priority of an interval is its total length, so that longer lived - // intervals will be processed before shorter ones (even if the longer ones - // have a low spill weight). See processInterval(). + // The priority of a bundle is its total length, so that longer lived + // bundles will be processed before shorter ones (even if the longer ones + // have a low spill weight). See processBundle(). size_t lifetimeTotal = 0; - for (size_t i = 0; i < interval->numRanges(); i++) { - const LiveInterval::Range* range = interval->getRange(i); - lifetimeTotal += range->to - range->from; + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + lifetimeTotal += range->to() - range->from(); } return lifetimeTotal; } -size_t -BacktrackingAllocator::computePriority(const VirtualRegisterGroup* group) +bool +BacktrackingAllocator::minimalDef(LiveRange* range, LNode* ins) { - size_t priority = 0; - for (size_t j = 0; j < group->registers.length(); j++) { - uint32_t vreg = group->registers[j]; - priority += computePriority(vregs[vreg].getInterval(0)); - } - return priority; + // Whether this is a minimal range capturing a definition at ins. + return (range->to() <= minimalDefEnd(ins).next()) && + ((!ins->isPhi() && range->from() == inputOf(ins)) || range->from() == outputOf(ins)); } bool -BacktrackingAllocator::minimalDef(const LiveInterval* interval, LNode* ins) +BacktrackingAllocator::minimalUse(LiveRange* range, LNode* ins) { - // Whether interval is a minimal interval capturing a definition at ins. - return (interval->end() <= minimalDefEnd(ins).next()) && - ((!ins->isPhi() && interval->start() == inputOf(ins)) || interval->start() == outputOf(ins)); + // Whether this is a minimal range capturing a use at ins. + return (range->from() == inputOf(ins)) && + (range->to() == outputOf(ins) || range->to() == outputOf(ins).next()); } bool -BacktrackingAllocator::minimalUse(const LiveInterval* interval, LNode* ins) +BacktrackingAllocator::minimalBundle(LiveBundle* bundle, bool* pfixed) { - // Whether interval is a minimal interval capturing a use at ins. - return (interval->start() == inputOf(ins)) && - (interval->end() == outputOf(ins) || interval->end() == outputOf(ins).next()); -} + LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); + LiveRange* range = LiveRange::get(*iter); -bool -BacktrackingAllocator::minimalInterval(const LiveInterval* interval, bool* pfixed) -{ - if (!interval->hasVreg()) { + if (!range->hasVreg()) { *pfixed = true; return true; } - if (interval->index() == 0) { - VirtualRegister& reg = vregs[interval->vreg()]; + // If a bundle contains multiple ranges, splitAtAllRegisterUses will split + // each range into a separate bundle. + if (++iter) + return false; + + if (range->hasDefinition()) { + VirtualRegister& reg = vregs[range->vreg()]; if (pfixed) *pfixed = reg.def()->policy() == LDefinition::FIXED && reg.def()->output()->isRegister(); - return minimalDef(interval, reg.ins()); + return minimalDef(range, reg.ins()); } bool fixed = false, minimal = false, multiple = false; - for (UsePositionIterator iter = interval->usesBegin(); iter != interval->usesEnd(); iter++) { - if (iter != interval->usesBegin()) + for (UsePositionIterator iter = range->usesBegin(); iter; iter++) { + if (iter != range->usesBegin()) multiple = true; LUse* use = iter->use; @@ -1772,12 +2359,12 @@ BacktrackingAllocator::minimalInterval(const LiveInterval* interval, bool* pfixe if (fixed) return false; fixed = true; - if (minimalUse(interval, insData[iter->pos])) + if (minimalUse(range, insData[iter->pos])) minimal = true; break; case LUse::REGISTER: - if (minimalUse(interval, insData[iter->pos])) + if (minimalUse(range, insData[iter->pos])) minimal = true; break; @@ -1786,8 +2373,8 @@ BacktrackingAllocator::minimalInterval(const LiveInterval* interval, bool* pfixe } } - // If an interval contains a fixed use and at least one other use, - // splitAtAllRegisterUses will split each use into a different interval. + // If a range contains a fixed use and at least one other use, + // splitAtAllRegisterUses will split each use into a different bundle. if (multiple && fixed) minimal = false; @@ -1797,108 +2384,96 @@ BacktrackingAllocator::minimalInterval(const LiveInterval* interval, bool* pfixe } size_t -BacktrackingAllocator::computeSpillWeight(const LiveInterval* interval) +BacktrackingAllocator::computeSpillWeight(LiveBundle* bundle) { - // Minimal intervals have an extremely high spill weight, to ensure they - // can evict any other intervals and be allocated to a register. + // Minimal bundles have an extremely high spill weight, to ensure they + // can evict any other bundles and be allocated to a register. bool fixed; - if (minimalInterval(interval, &fixed)) + if (minimalBundle(bundle, &fixed)) return fixed ? 2000000 : 1000000; size_t usesTotal = 0; - if (interval->index() == 0) { - VirtualRegister* reg = &vregs[interval->vreg()]; - if (reg->def()->policy() == LDefinition::FIXED && reg->def()->output()->isRegister()) - usesTotal += 2000; - else if (!reg->ins()->isPhi()) - usesTotal += 2000; - } + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); - for (UsePositionIterator iter = interval->usesBegin(); iter != interval->usesEnd(); iter++) { - LUse* use = iter->use; + if (range->hasDefinition()) { + VirtualRegister& reg = vregs[range->vreg()]; + if (reg.def()->policy() == LDefinition::FIXED && reg.def()->output()->isRegister()) + usesTotal += 2000; + else if (!reg.ins()->isPhi()) + usesTotal += 2000; + } - switch (use->policy()) { - case LUse::ANY: - usesTotal += 1000; - break; + for (UsePositionIterator iter = range->usesBegin(); iter; iter++) { + LUse* use = iter->use; - case LUse::REGISTER: - case LUse::FIXED: - usesTotal += 2000; - break; + switch (use->policy()) { + case LUse::ANY: + usesTotal += 1000; + break; - case LUse::KEEPALIVE: - break; + case LUse::REGISTER: + case LUse::FIXED: + usesTotal += 2000; + break; - default: - // Note: RECOVERED_INPUT will not appear in UsePositionIterator. - MOZ_CRASH("Bad use"); + case LUse::KEEPALIVE: + break; + + default: + // Note: RECOVERED_INPUT will not appear in UsePositionIterator. + MOZ_CRASH("Bad use"); + } } } - // Intervals for registers in groups get higher weights. - if (interval->hint()->kind() != Requirement::NONE) - usesTotal += 2000; - // Compute spill weight as a use density, lowering the weight for long - // lived intervals with relatively few uses. - size_t lifetimeTotal = computePriority(interval); + // lived bundles with relatively few uses. + size_t lifetimeTotal = computePriority(bundle); return lifetimeTotal ? usesTotal / lifetimeTotal : 0; } size_t -BacktrackingAllocator::computeSpillWeight(const VirtualRegisterGroup* group) +BacktrackingAllocator::maximumSpillWeight(const LiveBundleVector& bundles) { size_t maxWeight = 0; - for (size_t j = 0; j < group->registers.length(); j++) { - uint32_t vreg = group->registers[j]; - maxWeight = Max(maxWeight, computeSpillWeight(vregs[vreg].getInterval(0))); - } - return maxWeight; -} - -size_t -BacktrackingAllocator::maximumSpillWeight(const LiveIntervalVector& intervals) -{ - size_t maxWeight = 0; - for (size_t i = 0; i < intervals.length(); i++) - maxWeight = Max(maxWeight, computeSpillWeight(intervals[i])); + for (size_t i = 0; i < bundles.length(); i++) + maxWeight = Max(maxWeight, computeSpillWeight(bundles[i])); return maxWeight; } bool -BacktrackingAllocator::trySplitAcrossHotcode(LiveInterval* interval, bool* success) +BacktrackingAllocator::trySplitAcrossHotcode(LiveBundle* bundle, bool* success) { - // If this interval has portions that are hot and portions that are cold, + // If this bundle has portions that are hot and portions that are cold, // split it at the boundaries between hot and cold code. - const LiveInterval::Range* hotRange = nullptr; + LiveRange* hotRange = nullptr; - for (size_t i = 0; i < interval->numRanges(); i++) { - AllocatedRange range(interval, interval->getRange(i)), existing; - if (hotcode.contains(range, &existing)) { - hotRange = existing.range; + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + if (hotcode.contains(range, &hotRange)) break; - } } - // Don't split if there is no hot code in the interval. + // Don't split if there is no hot code in the bundle. if (!hotRange) { - JitSpew(JitSpew_RegAlloc, " interval does not contain hot code"); + JitSpew(JitSpew_RegAlloc, " bundle does not contain hot code"); return true; } - // Don't split if there is no cold code in the interval. + // Don't split if there is no cold code in the bundle. bool coldCode = false; - for (size_t i = 0; i < interval->numRanges(); i++) { - if (!hotRange->contains(interval->getRange(i))) { + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + if (!hotRange->contains(range)) { coldCode = true; break; } } if (!coldCode) { - JitSpew(JitSpew_RegAlloc, " interval does not contain cold code"); + JitSpew(JitSpew_RegAlloc, " bundle does not contain cold code"); return true; } @@ -1910,104 +2485,111 @@ BacktrackingAllocator::trySplitAcrossHotcode(LiveInterval* interval, bool* succe // soon and this special case removed. See bug 948838. if (compilingAsmJS()) { SplitPositionVector splitPositions; - if (!splitPositions.append(hotRange->from) || !splitPositions.append(hotRange->to)) + if (!splitPositions.append(hotRange->from()) || !splitPositions.append(hotRange->to())) return false; *success = true; - return splitAt(interval, splitPositions); + return splitAt(bundle, splitPositions); } - LiveInterval* hotInterval = LiveInterval::New(alloc(), interval->vreg(), 0); - LiveInterval* preInterval = nullptr; - LiveInterval* postInterval = nullptr; + LiveBundle* hotBundle = LiveBundle::New(alloc(), bundle->spillSet(), bundle->spillParent()); + if (!hotBundle) + return false; + LiveBundle* preBundle = nullptr; + LiveBundle* postBundle = nullptr; - // Accumulate the ranges of hot and cold code in the interval. Note that + // Accumulate the ranges of hot and cold code in the bundle. Note that // we are only comparing with the single hot range found, so the cold code // may contain separate hot ranges. - Vector hotList, coldList; - for (size_t i = 0; i < interval->numRanges(); i++) { - LiveInterval::Range hot, coldPre, coldPost; - interval->getRange(i)->intersect(hotRange, &coldPre, &hot, &coldPost); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveRange::Range hot, coldPre, coldPost; + range->intersect(hotRange, &coldPre, &hot, &coldPost); - if (!hot.empty() && !hotInterval->addRange(hot.from, hot.to)) - return false; + if (!hot.empty()) { + if (!hotBundle->addRangeAndDistributeUses(alloc(), range, hot.from, hot.to)) + return false; + } if (!coldPre.empty()) { - if (!preInterval) - preInterval = LiveInterval::New(alloc(), interval->vreg(), 0); - if (!preInterval->addRange(coldPre.from, coldPre.to)) + if (!preBundle) { + preBundle = LiveBundle::New(alloc(), bundle->spillSet(), bundle->spillParent()); + if (!preBundle) + return false; + } + if (!preBundle->addRangeAndDistributeUses(alloc(), range, coldPre.from, coldPre.to)) return false; } if (!coldPost.empty()) { - if (!postInterval) - postInterval = LiveInterval::New(alloc(), interval->vreg(), 0); - if (!postInterval->addRange(coldPost.from, coldPost.to)) + if (!postBundle) + postBundle = LiveBundle::New(alloc(), bundle->spillSet(), bundle->spillParent()); + if (!postBundle->addRangeAndDistributeUses(alloc(), range, coldPost.from, coldPost.to)) return false; } } - MOZ_ASSERT(preInterval || postInterval); - MOZ_ASSERT(hotInterval->numRanges()); + MOZ_ASSERT(preBundle || postBundle); + MOZ_ASSERT(hotBundle->numRanges() != 0); - LiveIntervalVector newIntervals; - if (!newIntervals.append(hotInterval)) + LiveBundleVector newBundles; + if (!newBundles.append(hotBundle)) return false; - if (preInterval && !newIntervals.append(preInterval)) + if (preBundle && !newBundles.append(preBundle)) return false; - if (postInterval && !newIntervals.append(postInterval)) + if (postBundle && !newBundles.append(postBundle)) return false; - distributeUses(interval, newIntervals); - *success = true; - return split(interval, newIntervals) && requeueIntervals(newIntervals); + return splitAndRequeueBundles(bundle, newBundles); } bool -BacktrackingAllocator::trySplitAfterLastRegisterUse(LiveInterval* interval, LiveInterval* conflict, bool* success) +BacktrackingAllocator::trySplitAfterLastRegisterUse(LiveBundle* bundle, LiveBundle* conflict, + bool* success) { - // If this interval's later uses do not require it to be in a register, + // If this bundle's later uses do not require it to be in a register, // split it after the last use which does require a register. If conflict // is specified, only consider register uses before the conflict starts. CodePosition lastRegisterFrom, lastRegisterTo, lastUse; - // If the definition of the interval is in a register, consider that a - // register use too for our purposes here. - if (isRegisterDefinition(interval)) { - CodePosition spillStart = minimalDefEnd(insData[interval->start()]).next(); - if (!conflict || spillStart < conflict->start()) { - lastUse = lastRegisterFrom = interval->start(); - lastRegisterTo = spillStart; + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + + // If the range defines a register, consider that a register use for + // our purposes here. + if (isRegisterDefinition(range)) { + CodePosition spillStart = minimalDefEnd(insData[range->from()]).next(); + if (!conflict || spillStart < conflict->firstRange()->from()) { + lastUse = lastRegisterFrom = range->from(); + lastRegisterTo = spillStart; + } } - } - for (UsePositionIterator iter(interval->usesBegin()); - iter != interval->usesEnd(); - iter++) - { - LUse* use = iter->use; - LNode* ins = insData[iter->pos]; + for (UsePositionIterator iter(range->usesBegin()); iter; iter++) { + LUse* use = iter->use; + LNode* ins = insData[iter->pos]; - // Uses in the interval should be sorted. - MOZ_ASSERT(iter->pos >= lastUse); - lastUse = inputOf(ins); + // Uses in the bundle should be sorted. + MOZ_ASSERT(iter->pos >= lastUse); + lastUse = inputOf(ins); - if (!conflict || outputOf(ins) < conflict->start()) { - if (isRegisterUse(use, ins, /* considerCopy = */ true)) { - lastRegisterFrom = inputOf(ins); - lastRegisterTo = iter->pos.next(); + if (!conflict || outputOf(ins) < conflict->firstRange()->from()) { + if (isRegisterUse(use, ins, /* considerCopy = */ true)) { + lastRegisterFrom = inputOf(ins); + lastRegisterTo = iter->pos.next(); + } } } } // Can't trim non-register uses off the end by splitting. if (!lastRegisterFrom.bits()) { - JitSpew(JitSpew_RegAlloc, " interval has no register uses"); + JitSpew(JitSpew_RegAlloc, " bundle has no register uses"); return true; } - if (lastRegisterFrom == lastUse) { - JitSpew(JitSpew_RegAlloc, " interval's last use is a register use"); + if (lastUse < lastRegisterTo) { + JitSpew(JitSpew_RegAlloc, " bundle's last use is a register use"); return true; } @@ -2018,45 +2600,55 @@ BacktrackingAllocator::trySplitAfterLastRegisterUse(LiveInterval* interval, Live if (!splitPositions.append(lastRegisterTo)) return false; *success = true; - return splitAt(interval, splitPositions); + return splitAt(bundle, splitPositions); } bool -BacktrackingAllocator::trySplitBeforeFirstRegisterUse(LiveInterval* interval, LiveInterval* conflict, bool* success) +BacktrackingAllocator::trySplitBeforeFirstRegisterUse(LiveBundle* bundle, LiveBundle* conflict, bool* success) { - // If this interval's earlier uses do not require it to be in a register, + // If this bundle's earlier uses do not require it to be in a register, // split it before the first use which does require a register. If conflict // is specified, only consider register uses after the conflict ends. - if (isRegisterDefinition(interval)) { - JitSpew(JitSpew_RegAlloc, " interval is defined by a register"); + if (isRegisterDefinition(bundle->firstRange())) { + JitSpew(JitSpew_RegAlloc, " bundle is defined by a register"); return true; } - if (interval->index() != 0) { - JitSpew(JitSpew_RegAlloc, " interval is not defined in memory"); + if (!bundle->firstRange()->hasDefinition()) { + JitSpew(JitSpew_RegAlloc, " bundle does not have definition"); return true; } CodePosition firstRegisterFrom; - for (UsePositionIterator iter(interval->usesBegin()); - iter != interval->usesEnd(); - iter++) - { - LUse* use = iter->use; - LNode* ins = insData[iter->pos]; + CodePosition conflictEnd; + if (conflict) { + for (LiveRange::BundleLinkIterator iter = conflict->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + if (range->to() > conflictEnd) + conflictEnd = range->to(); + } + } - if (!conflict || outputOf(ins) >= conflict->end()) { - if (isRegisterUse(use, ins, /* considerCopy = */ true)) { - firstRegisterFrom = inputOf(ins); - break; + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + + for (UsePositionIterator iter(range->usesBegin()); iter; iter++) { + LUse* use = iter->use; + LNode* ins = insData[iter->pos]; + + if (!conflict || outputOf(ins) >= conflictEnd) { + if (isRegisterUse(use, ins, /* considerCopy = */ true)) { + firstRegisterFrom = inputOf(ins); + break; + } } } } if (!firstRegisterFrom.bits()) { // Can't trim non-register uses off the beginning by splitting. - JitSpew(JitSpew_RegAlloc, " interval has no register uses"); + JitSpew(JitSpew_RegAlloc, " bundle has no register uses"); return true; } @@ -2067,236 +2659,240 @@ BacktrackingAllocator::trySplitBeforeFirstRegisterUse(LiveInterval* interval, Li if (!splitPositions.append(firstRegisterFrom)) return false; *success = true; - return splitAt(interval, splitPositions); + return splitAt(bundle, splitPositions); } -bool -BacktrackingAllocator::splitAtAllRegisterUses(LiveInterval* interval) +// When splitting a bundle according to a list of split positions, return +// whether a use or range at |pos| should use a different bundle than the last +// position this was called for. +static bool +UseNewBundle(const SplitPositionVector& splitPositions, CodePosition pos, + size_t* activeSplitPosition) { - // Split this interval so that all its register uses become minimal - // intervals and allow the vreg to be spilled throughout its range. - - LiveIntervalVector newIntervals; - uint32_t vreg = interval->vreg(); - - JitSpew(JitSpew_RegAlloc, " split at all register uses"); - - // If this LiveInterval is the result of an earlier split which created a - // spill interval, that spill interval covers the whole range, so we don't - // need to create a new one. - bool spillIntervalIsNew = false; - LiveInterval* spillInterval = interval->spillInterval(); - if (!spillInterval) { - spillInterval = LiveInterval::New(alloc(), vreg, 0); - spillIntervalIsNew = true; + if (splitPositions.empty()) { + // When the split positions are empty we are splitting at all uses. + return true; } - CodePosition spillStart = interval->start(); - if (isRegisterDefinition(interval)) { - // Treat the definition of the interval as a register use so that it - // can be split and spilled ASAP. - CodePosition from = interval->start(); - CodePosition to = minimalDefEnd(insData[from]).next(); - if (!addLiveInterval(newIntervals, vreg, spillInterval, from, to)) - return false; - spillStart = to; - } - - if (spillIntervalIsNew) { - for (size_t i = 0; i < interval->numRanges(); i++) { - const LiveInterval::Range* range = interval->getRange(i); - CodePosition from = Max(range->from, spillStart); - if (!spillInterval->addRange(from, range->to)) - return false; - } - } - - for (UsePositionIterator iter(interval->usesBegin()); - iter != interval->usesEnd(); - iter++) - { - LNode* ins = insData[iter->pos]; - if (iter->pos < spillStart) { - newIntervals.back()->addUseAtEnd(new(alloc()) UsePosition(iter->use, iter->pos)); - } else if (isRegisterUse(iter->use, ins)) { - // For register uses which are not useRegisterAtStart, pick an - // interval that covers both the instruction's input and output, so - // that the register is not reused for an output. - CodePosition from = inputOf(ins); - CodePosition to = iter->use->usedAtStart() ? outputOf(ins) : iter->pos.next(); - - // Use the same interval for duplicate use positions, except when - // the uses are fixed (they may require incompatible registers). - if (newIntervals.empty() || - newIntervals.back()->end() != to || - newIntervals.back()->usesBegin()->use->policy() == LUse::FIXED || - iter->use->policy() == LUse::FIXED) - { - if (!addLiveInterval(newIntervals, vreg, spillInterval, from, to)) - return false; - } - - newIntervals.back()->addUseAtEnd(new(alloc()) UsePosition(iter->use, iter->pos)); - } else { - MOZ_ASSERT(spillIntervalIsNew); - spillInterval->addUseAtEnd(new(alloc()) UsePosition(iter->use, iter->pos)); - } - } - - if (spillIntervalIsNew && !newIntervals.append(spillInterval)) + if (*activeSplitPosition == splitPositions.length()) { + // We've advanced past all split positions. return false; - - return split(interval, newIntervals) && requeueIntervals(newIntervals); -} - -// Find the next split position after the current position. -static size_t NextSplitPosition(size_t activeSplitPosition, - const SplitPositionVector& splitPositions, - CodePosition currentPos) -{ - while (activeSplitPosition < splitPositions.length() && - splitPositions[activeSplitPosition] <= currentPos) - { - ++activeSplitPosition; } - return activeSplitPosition; + + if (splitPositions[*activeSplitPosition] > pos) { + // We haven't gotten to the next split position yet. + return false; + } + + // We've advanced past the next split position, find the next one which we + // should split at. + while (*activeSplitPosition < splitPositions.length() && + splitPositions[*activeSplitPosition] <= pos) + { + (*activeSplitPosition)++; + } + return true; } -// Test whether the current position has just crossed a split point. -static bool SplitHere(size_t activeSplitPosition, - const SplitPositionVector& splitPositions, - CodePosition currentPos) +static bool +HasPrecedingRangeSharingVreg(LiveBundle* bundle, LiveRange* range) { - return activeSplitPosition < splitPositions.length() && - currentPos >= splitPositions[activeSplitPosition]; + MOZ_ASSERT(range->bundle() == bundle); + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* prevRange = LiveRange::get(*iter); + if (prevRange == range) + return false; + if (prevRange->vreg() == range->vreg()) + return true; + } + + MOZ_CRASH(); +} + +static bool +HasFollowingRangeSharingVreg(LiveBundle* bundle, LiveRange* range) +{ + MOZ_ASSERT(range->bundle() == bundle); + + bool foundRange = false; + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* prevRange = LiveRange::get(*iter); + if (foundRange && prevRange->vreg() == range->vreg()) + return true; + if (prevRange == range) + foundRange = true; + } + + MOZ_ASSERT(foundRange); + return false; } bool -BacktrackingAllocator::splitAt(LiveInterval* interval, - const SplitPositionVector& splitPositions) +BacktrackingAllocator::splitAt(LiveBundle* bundle, const SplitPositionVector& splitPositions) { - // Split the interval at the given split points. Unlike splitAtAllRegisterUses, - // consolidate any register uses which have no intervening split points into the - // same resulting interval. + // Split the bundle at the given split points. Register uses which have no + // intervening split points are consolidated into the same bundle. If the + // list of split points is empty, then all register uses are placed in + // minimal bundles. - // splitPositions should be non-empty and sorted. - MOZ_ASSERT(!splitPositions.empty()); + // splitPositions should be sorted. for (size_t i = 1; i < splitPositions.length(); ++i) MOZ_ASSERT(splitPositions[i-1] < splitPositions[i]); - // Don't spill the interval until after the end of its definition. - CodePosition spillStart = interval->start(); - if (isRegisterDefinition(interval)) - spillStart = minimalDefEnd(insData[interval->start()]).next(); - - uint32_t vreg = interval->vreg(); - - // If this LiveInterval is the result of an earlier split which created a - // spill interval, that spill interval covers the whole range, so we don't - // need to create a new one. - bool spillIntervalIsNew = false; - LiveInterval* spillInterval = interval->spillInterval(); - if (!spillInterval) { - spillInterval = LiveInterval::New(alloc(), vreg, 0); - spillIntervalIsNew = true; - - for (size_t i = 0; i < interval->numRanges(); i++) { - const LiveInterval::Range* range = interval->getRange(i); - CodePosition from = Max(range->from, spillStart); - if (!spillInterval->addRange(from, range->to)) - return false; - } - } - - LiveIntervalVector newIntervals; - - CodePosition lastRegisterUse; - if (spillStart != interval->start()) { - LiveInterval* newInterval = LiveInterval::New(alloc(), vreg, 0); - newInterval->setSpillInterval(spillInterval); - if (!newIntervals.append(newInterval)) + // We don't need to create a new spill bundle if there already is one. + bool spillBundleIsNew = false; + LiveBundle* spillBundle = bundle->spillParent(); + if (!spillBundle) { + spillBundle = LiveBundle::New(alloc(), bundle->spillSet(), nullptr); + if (!spillBundle) return false; - lastRegisterUse = interval->start(); - } + spillBundleIsNew = true; - size_t activeSplitPosition = NextSplitPosition(0, splitPositions, interval->start()); - for (UsePositionIterator iter(interval->usesBegin()); iter != interval->usesEnd(); iter++) { - LNode* ins = insData[iter->pos]; - if (iter->pos < spillStart) { - newIntervals.back()->addUseAtEnd(new(alloc()) UsePosition(iter->use, iter->pos)); - activeSplitPosition = NextSplitPosition(activeSplitPosition, splitPositions, iter->pos); - } else if (isRegisterUse(iter->use, ins)) { - if (lastRegisterUse.bits() == 0 || - SplitHere(activeSplitPosition, splitPositions, iter->pos)) - { - // Place this register use into a different interval from the - // last one if there are any split points between the two uses. - LiveInterval* newInterval = LiveInterval::New(alloc(), vreg, 0); - newInterval->setSpillInterval(spillInterval); - if (!newIntervals.append(newInterval)) + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + + CodePosition from = range->from(); + if (isRegisterDefinition(range)) + from = minimalDefEnd(insData[from]).next(); + + if (from < range->to()) { + if (!spillBundle->addRange(alloc(), range->vreg(), from, range->to())) return false; - activeSplitPosition = NextSplitPosition(activeSplitPosition, - splitPositions, - iter->pos); + + if (range->hasDefinition() && !isRegisterDefinition(range)) + spillBundle->lastRange()->setHasDefinition(); } - newIntervals.back()->addUseAtEnd(new(alloc()) UsePosition(iter->use, iter->pos)); - lastRegisterUse = iter->pos; - } else { - MOZ_ASSERT(spillIntervalIsNew); - spillInterval->addUseAtEnd(new(alloc()) UsePosition(iter->use, iter->pos)); } } - // Compute ranges for each new interval that cover all its uses. - size_t activeRange = interval->numRanges(); - for (size_t i = 0; i < newIntervals.length(); i++) { - LiveInterval* newInterval = newIntervals[i]; - CodePosition start, end; - if (i == 0 && spillStart != interval->start()) { - start = interval->start(); - if (newInterval->usesEmpty()) - end = spillStart; - else - end = newInterval->usesBack()->pos.next(); - } else { - start = inputOf(insData[newInterval->usesBegin()->pos]); - end = newInterval->usesBack()->pos.next(); - } - for (; activeRange > 0; --activeRange) { - const LiveInterval::Range* range = interval->getRange(activeRange - 1); - if (range->to <= start) - continue; - if (range->from >= end) - break; - if (!newInterval->addRange(Max(range->from, start), - Min(range->to, end))) - return false; - if (range->to >= end) - break; - } - } + LiveBundleVector newBundles; - if (spillIntervalIsNew && !newIntervals.append(spillInterval)) + // The bundle which ranges are currently being added to. + LiveBundle* activeBundle = LiveBundle::New(alloc(), bundle->spillSet(), spillBundle); + if (!newBundles.append(activeBundle)) return false; - return split(interval, newIntervals) && requeueIntervals(newIntervals); + // State for use by UseNewBundle. + size_t activeSplitPosition = 0; + + // Make new bundles according to the split positions, and distribute ranges + // and uses to them. + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + + if (UseNewBundle(splitPositions, range->from(), &activeSplitPosition)) { + activeBundle = LiveBundle::New(alloc(), bundle->spillSet(), spillBundle); + if (!newBundles.append(activeBundle)) + return false; + } + + LiveRange* activeRange = LiveRange::New(alloc(), range->vreg(), range->from(), range->to()); + if (!activeRange) + return false; + activeBundle->addRange(activeRange); + + if (isRegisterDefinition(range)) + activeRange->setHasDefinition(); + + while (range->hasUses()) { + UsePosition* use = range->popUse(); + LNode* ins = insData[use->pos]; + + // Any uses of a register that appear before its definition has + // finished must be associated with the range for that definition. + if (isRegisterDefinition(range) && use->pos <= minimalDefEnd(insData[range->from()])) { + activeRange->addUse(use); + } else if (isRegisterUse(use->use, ins)) { + // Place this register use into a different bundle from the + // last one if there are any split points between the two uses. + // UseNewBundle always returns true if we are splitting at all + // register uses, but we can still reuse the last range and + // bundle if they have uses at the same position, except when + // either use is fixed (the two uses might require incompatible + // registers.) + if (UseNewBundle(splitPositions, use->pos, &activeSplitPosition) && + (!activeRange->hasUses() || + activeRange->usesBegin()->pos != use->pos || + activeRange->usesBegin()->use->policy() == LUse::FIXED || + use->use->policy() == LUse::FIXED)) + { + activeBundle = LiveBundle::New(alloc(), bundle->spillSet(), spillBundle); + if (!newBundles.append(activeBundle)) + return false; + activeRange = LiveRange::New(alloc(), range->vreg(), range->from(), range->to()); + if (!activeRange) + return false; + activeBundle->addRange(activeRange); + } + + activeRange->addUse(use); + } else { + MOZ_ASSERT(spillBundleIsNew); + spillBundle->rangeFor(use->pos)->addUse(use); + } + } + } + + LiveBundleVector filteredBundles; + + // Trim the ends of ranges in each new bundle when there are no other + // earlier or later ranges in the same bundle with the same vreg. + for (size_t i = 0; i < newBundles.length(); i++) { + LiveBundle* bundle = newBundles[i]; + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; ) { + LiveRange* range = LiveRange::get(*iter); + + if (!range->hasDefinition()) { + if (!HasPrecedingRangeSharingVreg(bundle, range)) { + if (range->hasUses()) { + UsePosition* use = *range->usesBegin(); + range->setFrom(inputOf(insData[use->pos])); + } else { + bundle->removeRangeAndIncrementIterator(iter); + continue; + } + } + } + + if (!HasFollowingRangeSharingVreg(bundle, range)) { + if (range->hasUses()) { + UsePosition* use = range->lastUse(); + range->setTo(use->pos.next()); + } else if (range->hasDefinition()) { + range->setTo(minimalDefEnd(insData[range->from()]).next()); + } else { + bundle->removeRangeAndIncrementIterator(iter); + continue; + } + } + + iter++; + } + + if (bundle->hasRanges() && !filteredBundles.append(bundle)) + return false; + } + + if (spillBundleIsNew && !filteredBundles.append(spillBundle)) + return false; + + return splitAndRequeueBundles(bundle, filteredBundles); } bool -BacktrackingAllocator::splitAcrossCalls(LiveInterval* interval) +BacktrackingAllocator::splitAcrossCalls(LiveBundle* bundle) { - // Split the interval to separate register uses and non-register uses and + // Split the bundle to separate register uses and non-register uses and // allow the vreg to be spilled across its range. - // Find the locations of all calls in the interval's range. Fixed intervals - // are introduced by buildLivenessInfo only for calls when allocating for - // the backtracking allocator. fixedIntervalsUnion is sorted backwards, so - // iterate through it backwards. + // Find the locations of all calls in the bundle's range. SplitPositionVector callPositions; - for (size_t i = fixedIntervalsUnion->numRanges(); i > 0; i--) { - const LiveInterval::Range* range = fixedIntervalsUnion->getRange(i - 1); - if (interval->covers(range->from) && interval->covers(range->from.previous())) { - if (!callPositions.append(range->from)) + for (LiveRange::BundleLinkIterator iter = callRanges->rangesBegin(); iter; iter++) { + LiveRange* callRange = LiveRange::get(*iter); + if (bundle->rangeFor(callRange->from()) && bundle->rangeFor(callRange->from().previous())) { + if (!callPositions.append(callRange->from())) return false; } } @@ -2304,37 +2900,38 @@ BacktrackingAllocator::splitAcrossCalls(LiveInterval* interval) #ifdef DEBUG JitSpewStart(JitSpew_RegAlloc, " split across calls at "); - for (size_t i = 0; i < callPositions.length(); ++i) { + for (size_t i = 0; i < callPositions.length(); ++i) JitSpewCont(JitSpew_RegAlloc, "%s%u", i != 0 ? ", " : "", callPositions[i].bits()); - } JitSpewFin(JitSpew_RegAlloc); #endif - return splitAt(interval, callPositions); + return splitAt(bundle, callPositions); } bool -BacktrackingAllocator::chooseIntervalSplit(LiveInterval* interval, bool fixed, LiveInterval* conflict) +BacktrackingAllocator::chooseBundleSplit(LiveBundle* bundle, bool fixed, LiveBundle* conflict) { bool success = false; - if (!trySplitAcrossHotcode(interval, &success)) + if (!trySplitAcrossHotcode(bundle, &success)) return false; if (success) return true; if (fixed) - return splitAcrossCalls(interval); + return splitAcrossCalls(bundle); - if (!trySplitBeforeFirstRegisterUse(interval, conflict, &success)) + if (!trySplitBeforeFirstRegisterUse(bundle, conflict, &success)) return false; if (success) return true; - if (!trySplitAfterLastRegisterUse(interval, conflict, &success)) + if (!trySplitAfterLastRegisterUse(bundle, conflict, &success)) return false; if (success) return true; - return splitAtAllRegisterUses(interval); + // Split at all register uses. + SplitPositionVector emptyPositions; + return splitAt(bundle, emptyPositions); } diff --git a/js/src/jit/BacktrackingAllocator.h b/js/src/jit/BacktrackingAllocator.h index 5b4948c38e..abc7988483 100644 --- a/js/src/jit/BacktrackingAllocator.h +++ b/js/src/jit/BacktrackingAllocator.h @@ -11,7 +11,8 @@ #include "ds/PriorityQueue.h" #include "ds/SplayTree.h" -#include "jit/LiveRangeAllocator.h" +#include "jit/RegisterAllocator.h" +#include "jit/StackSlotAllocator.h" // Backtracking priority queue based register allocator based on that described // in the following blog post: @@ -21,55 +22,489 @@ namespace js { namespace jit { -// Information about a group of registers. Registers may be grouped together -// when (a) all of their lifetimes are disjoint, (b) they are of the same type -// (double / non-double) and (c) it is desirable that they have the same -// allocation. -struct VirtualRegisterGroup : public TempObject +class Requirement { - // All virtual registers in the group. - Vector registers; + public: + enum Kind { + NONE, + REGISTER, + FIXED, + MUST_REUSE_INPUT + }; - // Desired physical register to use for registers in the group. - LAllocation allocation; + Requirement() + : kind_(NONE) + { } - // Spill location to be shared by registers in the group. - LAllocation spill; + explicit Requirement(Kind kind) + : kind_(kind) + { + // These have dedicated constructors. + MOZ_ASSERT(kind != FIXED && kind != MUST_REUSE_INPUT); + } - explicit VirtualRegisterGroup(TempAllocator& alloc) - : registers(alloc), allocation(LUse(0, LUse::ANY)), spill(LUse(0, LUse::ANY)) - {} + Requirement(Kind kind, CodePosition at) + : kind_(kind), + position_(at) + { + // These have dedicated constructors. + MOZ_ASSERT(kind != FIXED && kind != MUST_REUSE_INPUT); + } - uint32_t canonicalReg() { - uint32_t minimum = registers[0]; - for (size_t i = 1; i < registers.length(); i++) - minimum = Min(minimum, registers[i]); - return minimum; + explicit Requirement(LAllocation fixed) + : kind_(FIXED), + allocation_(fixed) + { + MOZ_ASSERT(!fixed.isBogus() && !fixed.isUse()); + } + + // Only useful as a hint, encodes where the fixed requirement is used to + // avoid allocating a fixed register too early. + Requirement(LAllocation fixed, CodePosition at) + : kind_(FIXED), + allocation_(fixed), + position_(at) + { + MOZ_ASSERT(!fixed.isBogus() && !fixed.isUse()); + } + + Requirement(uint32_t vreg, CodePosition at) + : kind_(MUST_REUSE_INPUT), + allocation_(LUse(vreg, LUse::ANY)), + position_(at) + { } + + Kind kind() const { + return kind_; + } + + LAllocation allocation() const { + MOZ_ASSERT(!allocation_.isBogus() && !allocation_.isUse()); + return allocation_; + } + + uint32_t virtualRegister() const { + MOZ_ASSERT(allocation_.isUse()); + MOZ_ASSERT(kind() == MUST_REUSE_INPUT); + return allocation_.toUse()->virtualRegister(); + } + + CodePosition pos() const { + return position_; + } + + int priority() const; + + bool merge(const Requirement& newRequirement) { + // Merge newRequirement with any existing requirement, returning false + // if the new and old requirements conflict. + MOZ_ASSERT(newRequirement.kind() != Requirement::MUST_REUSE_INPUT); + + if (newRequirement.kind() == Requirement::FIXED) { + if (kind() == Requirement::FIXED) + return newRequirement.allocation() == allocation(); + *this = newRequirement; + return true; + } + + MOZ_ASSERT(newRequirement.kind() == Requirement::REGISTER); + if (kind() == Requirement::FIXED) + return allocation().isRegister(); + + *this = newRequirement; + return true; + } + + void dump() const; + + private: + Kind kind_; + LAllocation allocation_; + CodePosition position_; +}; + +struct UsePosition : public TempObject, + public InlineForwardListNode +{ + LUse* use; + CodePosition pos; + + UsePosition(LUse* use, CodePosition pos) : + use(use), + pos(pos) + { + // Verify that the usedAtStart() flag is consistent with the + // subposition. For now ignore fixed registers, because they + // are handled specially around calls. + MOZ_ASSERT_IF(!use->isFixedRegister(), + pos.subpos() == (use->usedAtStart() + ? CodePosition::INPUT + : CodePosition::OUTPUT)); } }; -class BacktrackingVirtualRegister : public VirtualRegister +typedef InlineForwardListIterator UsePositionIterator; + +// Backtracking allocator data structures overview. +// +// LiveRange: A continuous range of positions where a virtual register is live. +// LiveBundle: A set of LiveRanges which do not overlap. +// VirtualRegister: A set of all LiveRanges used for some LDefinition. +// +// The allocator first performs a liveness ananlysis on the LIR graph which +// constructs LiveRanges for each VirtualRegister, determining where the +// registers are live. +// +// The ranges are then bundled together according to heuristics, and placed on +// the allocation queue. +// +// As bundles are removed from the allocation queue, we attempt to find a +// physical register or stack slot allocation for all ranges in the removed +// bundle, possibly evicting already-allocated bundles. See processBundle() +// for details. +// +// If we are not able to allocate a bundle, it is split according to heuristics +// into two or more smaller bundles which cover all the ranges of the original. +// These smaller bundles are then allocated independently. + +class LiveBundle; + +class LiveRange : public TempObject { + public: + // Linked lists are used to keep track of the ranges in each LiveBundle and + // VirtualRegister. Since a LiveRange may be in two lists simultaneously, use + // these auxiliary classes to keep things straight. + class BundleLink : public InlineForwardListNode {}; + class RegisterLink : public InlineForwardListNode {}; + + typedef InlineForwardListIterator BundleLinkIterator; + typedef InlineForwardListIterator RegisterLinkIterator; + + // Links in the lists in LiveBundle and VirtualRegister. + BundleLink bundleLink; + RegisterLink registerLink; + + static LiveRange* get(BundleLink* link) { + return reinterpret_cast(reinterpret_cast(link) - + offsetof(LiveRange, bundleLink)); + } + static LiveRange* get(RegisterLink* link) { + return reinterpret_cast(reinterpret_cast(link) - + offsetof(LiveRange, registerLink)); + } + + struct Range + { + // The beginning of this range, inclusive. + CodePosition from; + + // The end of this range, exclusive. + CodePosition to; + + Range() {} + + Range(CodePosition from, CodePosition to) + : from(from), to(to) + { + MOZ_ASSERT(!empty()); + } + + bool empty() { + MOZ_ASSERT(from <= to); + return from == to; + } + }; + + private: + // The virtual register this range is for, or zero if this does not have a + // virtual register (for example, it is in the callRanges bundle). + uint32_t vreg_; + + // The bundle containing this range, null if liveness information is being + // constructed and we haven't started allocating bundles yet. + LiveBundle* bundle_; + + // The code positions in this range. + Range range_; + + // All uses of the virtual register in this range, ordered by location. + InlineForwardList uses_; + + // Whether this range contains the virtual register's definition. + bool hasDefinition_; + + LiveRange(uint32_t vreg, Range range) + : vreg_(vreg), bundle_(nullptr), range_(range), hasDefinition_(false) + { + MOZ_ASSERT(!range.empty()); + } + + public: + static LiveRange* New(TempAllocator& alloc, uint32_t vreg, + CodePosition from, CodePosition to) { + return new(alloc) LiveRange(vreg, Range(from, to)); + } + + uint32_t vreg() const { + MOZ_ASSERT(hasVreg()); + return vreg_; + } + bool hasVreg() const { + return vreg_ != 0; + } + + LiveBundle* bundle() const { + return bundle_; + } + + CodePosition from() const { + return range_.from; + } + CodePosition to() const { + return range_.to; + } + bool covers(CodePosition pos) const { + return pos >= from() && pos < to(); + } + + // Whether this range wholly contains other. + bool contains(LiveRange* other) const; + + // Intersect this range with other, returning the subranges of this + // that are before, inside, or after other. + void intersect(LiveRange* other, Range* pre, Range* inside, Range* post) const; + + // Whether this range has any intersection with other. + bool intersects(LiveRange* other) const; + + UsePositionIterator usesBegin() const { + return uses_.begin(); + } + UsePosition* lastUse() const { + return uses_.back(); + } + bool hasUses() const { + return !!usesBegin(); + } + UsePosition* popUse() { + return uses_.popFront(); + } + + bool hasDefinition() const { + return hasDefinition_; + } + + void setFrom(CodePosition from) { + range_.from = from; + MOZ_ASSERT(!range_.empty()); + } + void setTo(CodePosition to) { + range_.to = to; + MOZ_ASSERT(!range_.empty()); + } + + void setBundle(LiveBundle* bundle) { + bundle_ = bundle; + } + + void addUse(UsePosition* use); + void distributeUses(LiveRange* other); + + void setHasDefinition() { + MOZ_ASSERT(!hasDefinition_); + hasDefinition_ = true; + } + + // Return a string describing this range. This is not re-entrant! +#ifdef DEBUG + const char* toString() const; +#else + const char* toString() const { return "???"; } +#endif + + // Comparator for use in range splay trees. + static int compare(LiveRange* v0, LiveRange* v1) { + // LiveRange includes 'from' but excludes 'to'. + if (v0->to() <= v1->from()) + return -1; + if (v0->from() >= v1->to()) + return 1; + return 0; + } +}; + +// Tracks information about bundles that should all be spilled to the same +// physical location. At the beginning of allocation, each bundle has its own +// spill set. As bundles are split, the new smaller bundles continue to use the +// same spill set. +class SpillSet : public TempObject +{ + // All bundles with this spill set which have been spilled. All bundles in + // this list will be given the same physical slot. + Vector list_; + + explicit SpillSet(TempAllocator& alloc) + : list_(alloc) + { } + + public: + static SpillSet* New(TempAllocator& alloc) { + return new(alloc) SpillSet(alloc); + } + + bool addSpilledBundle(LiveBundle* bundle) { + return list_.append(bundle); + } + size_t numSpilledBundles() const { + return list_.length(); + } + LiveBundle* spilledBundle(size_t i) const { + return list_[i]; + } + + void setAllocation(LAllocation alloc); +}; + +// A set of live ranges which are all pairwise disjoint. The register allocator +// attempts to find allocations for an entire bundle, and if it fails the +// bundle will be broken into smaller ones which are allocated independently. +class LiveBundle : public TempObject +{ + // Set to use if this bundle or one it is split into is spilled. + SpillSet* spill_; + + // All the ranges in this set, ordered by location. + InlineForwardList ranges_; + + // Allocation to use for ranges in this set, bogus if unallocated or spilled + // and not yet given a physical stack slot. + LAllocation alloc_; + + // Bundle which entirely contains this one and has no register uses. This + // may or may not be spilled by the allocator, but it can be spilled and + // will not be split. + LiveBundle* spillParent_; + + LiveBundle(SpillSet* spill, LiveBundle* spillParent) + : spill_(spill), spillParent_(spillParent) + { } + + public: + static LiveBundle* New(TempAllocator& alloc, SpillSet* spill, LiveBundle* spillParent) { + return new(alloc) LiveBundle(spill, spillParent); + } + + SpillSet* spillSet() const { + return spill_; + } + void setSpillSet(SpillSet* spill) { + spill_ = spill; + } + + LiveRange::BundleLinkIterator rangesBegin() const { + return ranges_.begin(); + } + bool hasRanges() const { + return !!rangesBegin(); + } + LiveRange* firstRange() const { + return LiveRange::get(*rangesBegin()); + } + LiveRange* lastRange() const { + return LiveRange::get(ranges_.back()); + } + LiveRange* rangeFor(CodePosition pos) const; + void removeRange(LiveRange* range); + void removeRangeAndIncrementIterator(LiveRange::BundleLinkIterator& iter) { + ranges_.removeAndIncrement(iter); + } + void addRange(LiveRange* range); + bool addRange(TempAllocator& alloc, uint32_t vreg, CodePosition from, CodePosition to); + bool addRangeAndDistributeUses(TempAllocator& alloc, LiveRange* oldRange, + CodePosition from, CodePosition to); + LiveRange* popFirstRange(); +#ifdef DEBUG + size_t numRanges() const; +#endif + + LAllocation allocation() const { + return alloc_; + } + void setAllocation(LAllocation alloc) { + alloc_ = alloc; + } + + LiveBundle* spillParent() const { + return spillParent_; + } + + // Return a string describing this bundle. This is not re-entrant! +#ifdef DEBUG + const char* toString() const; +#else + const char* toString() const { return "???"; } +#endif +}; + +// Information about the allocation for a virtual register. +class VirtualRegister +{ + // Instruction which defines this register. + LNode* ins_; + + // Definition in the instruction for this register. + LDefinition* def_; + + // All live ranges for this register. These may overlap each other, and are + // ordered by their start position. + InlineForwardList ranges_; + + // Whether def_ is a temp or an output. + bool isTemp_; + // If this register's definition is MUST_REUSE_INPUT, whether a copy must // be introduced before the definition that relaxes the policy. bool mustCopyInput_; - // Spill location to use for this register. - LAllocation canonicalSpill_; - - // Code position above which the canonical spill cannot be used; such - // intervals may overlap other registers in the same group. - CodePosition canonicalSpillExclude_; - - // If this register is associated with a group of other registers, - // information about the group. This structure is shared between all - // registers in the group. - VirtualRegisterGroup* group_; + void operator=(const VirtualRegister&) = delete; + VirtualRegister(const VirtualRegister&) = delete; public: - explicit BacktrackingVirtualRegister(TempAllocator& alloc) - : VirtualRegister(alloc) - {} + explicit VirtualRegister() + { + // Note: This class is zeroed before it is constructed. + } + + void init(LNode* ins, LDefinition* def, bool isTemp) { + MOZ_ASSERT(!ins_); + ins_ = ins; + def_ = def; + isTemp_ = isTemp; + } + + LNode* ins() const { + return ins_; + } + LDefinition* def() const { + return def_; + } + LDefinition::Type type() const { + return def()->type(); + } + uint32_t vreg() const { + return def()->virtualRegister(); + } + bool isCompatible(const AnyRegister& r) const { + return def_->isCompatibleReg(r); + } + bool isCompatible(const VirtualRegister& vr) const { + return def_->isCompatibleDef(*vr.def_); + } + bool isTemp() const { + return isTemp_; + } + void setMustCopyInput() { mustCopyInput_ = true; } @@ -77,56 +512,56 @@ class BacktrackingVirtualRegister : public VirtualRegister return mustCopyInput_; } - void setCanonicalSpill(LAllocation alloc) { - MOZ_ASSERT(!alloc.isUse()); - canonicalSpill_ = alloc; + LiveRange::RegisterLinkIterator rangesBegin() const { + return ranges_.begin(); } - const LAllocation* canonicalSpill() const { - return canonicalSpill_.isBogus() ? nullptr : &canonicalSpill_; + bool hasRanges() const { + return !!rangesBegin(); + } + LiveRange* firstRange() const { + return LiveRange::get(*rangesBegin()); + } + LiveRange* lastRange() const { + return LiveRange::get(ranges_.back()); + } + LiveRange* rangeFor(CodePosition pos) const; + void removeRange(LiveRange* range); + void addRange(LiveRange* range); + + LiveBundle* firstBundle() const { + return firstRange()->bundle(); } - void setCanonicalSpillExclude(CodePosition pos) { - canonicalSpillExclude_ = pos; - } - bool hasCanonicalSpillExclude() const { - return canonicalSpillExclude_.bits() != 0; - } - CodePosition canonicalSpillExclude() const { - MOZ_ASSERT(hasCanonicalSpillExclude()); - return canonicalSpillExclude_; - } - - void setGroup(VirtualRegisterGroup* group) { - group_ = group; - } - VirtualRegisterGroup* group() { - return group_; - } + bool addInitialRange(TempAllocator& alloc, CodePosition from, CodePosition to); + void addInitialUse(UsePosition* use); + void setInitialDefinition(CodePosition from); }; // A sequence of code positions, for tellings BacktrackingAllocator::splitAt // where to split. typedef js::Vector SplitPositionVector; -class BacktrackingAllocator - : private LiveRangeAllocator +class BacktrackingAllocator : protected RegisterAllocator { friend class C1Spewer; friend class JSONSpewer; - // Priority queue element: either an interval or group of intervals and the - // associated priority. + BitSet* liveIn; + FixedList vregs; + + // Ranges where all registers must be spilled due to call instructions. + LiveBundle* callRanges; + + // Allocation state. + StackSlotAllocator stackSlotAllocator; + + // Priority queue element: a bundle and the associated priority. struct QueueItem { - LiveInterval* interval; - VirtualRegisterGroup* group; + LiveBundle* bundle; - QueueItem(LiveInterval* interval, size_t priority) - : interval(interval), group(nullptr), priority_(priority) - {} - - QueueItem(VirtualRegisterGroup* group, size_t priority) - : interval(nullptr), group(group), priority_(priority) + QueueItem(LiveBundle* bundle, size_t priority) + : bundle(bundle), priority_(priority) {} static size_t priority(const QueueItem& v) { @@ -139,37 +574,14 @@ class BacktrackingAllocator PriorityQueue allocationQueue; - // A subrange over which a physical register is allocated. - struct AllocatedRange { - LiveInterval* interval; - const LiveInterval::Range* range; - - AllocatedRange() - : interval(nullptr), range(nullptr) - {} - - AllocatedRange(LiveInterval* interval, const LiveInterval::Range* range) - : interval(interval), range(range) - {} - - static int compare(const AllocatedRange& v0, const AllocatedRange& v1) { - // LiveInterval::Range includes 'from' but excludes 'to'. - if (v0.range->to <= v1.range->from) - return -1; - if (v0.range->from >= v1.range->to) - return 1; - return 0; - } - }; - - typedef SplayTree AllocatedRangeSet; + typedef SplayTree LiveRangeSet; // Each physical register is associated with the set of ranges over which // that register is currently allocated. struct PhysicalRegister { bool allocatable; AnyRegister reg; - AllocatedRangeSet allocations; + LiveRangeSet allocations; PhysicalRegister() : allocatable(false) {} }; @@ -177,16 +589,12 @@ class BacktrackingAllocator // Ranges of code which are considered to be hot, for which good allocation // should be prioritized. - AllocatedRangeSet hotcode; - - // During register allocation, virtual stack slots are used for spills. - // These are converted to actual spill locations - size_t numVirtualStackSlots; + LiveRangeSet hotcode; // Information about an allocated stack slot. struct SpillSlot : public TempObject, public InlineForwardListNode { LStackSlot alloc; - AllocatedRangeSet allocated; + LiveRangeSet allocated; SpillSlot(uint32_t slot, LifoAlloc* alloc) : alloc(slot), allocated(alloc) @@ -199,93 +607,130 @@ class BacktrackingAllocator public: BacktrackingAllocator(MIRGenerator* mir, LIRGenerator* lir, LIRGraph& graph) - : LiveRangeAllocator(mir, lir, graph), - numVirtualStackSlots(0) + : RegisterAllocator(mir, lir, graph), + liveIn(nullptr), + callRanges(nullptr) { } bool go(); private: - typedef Vector LiveIntervalVector; + typedef Vector LiveRangeVector; + typedef Vector LiveBundleVector; + // Liveness methods. bool init(); - bool canAddToGroup(VirtualRegisterGroup* group, BacktrackingVirtualRegister* reg); - bool tryGroupRegisters(uint32_t vreg0, uint32_t vreg1); - bool tryGroupReusedRegister(uint32_t def, uint32_t use); - bool groupAndQueueRegisters(); - bool tryAllocateFixed(LiveInterval* interval, bool* success, bool* pfixed, - LiveIntervalVector& conflicting); - bool tryAllocateNonFixed(LiveInterval* interval, bool* success, bool* pfixed, - LiveIntervalVector& conflicting); - bool processInterval(LiveInterval* interval); - bool processGroup(VirtualRegisterGroup* group); - bool setIntervalRequirement(LiveInterval* interval); - bool tryAllocateRegister(PhysicalRegister& r, LiveInterval* interval, - bool* success, bool* pfixed, LiveIntervalVector& conflicting); - bool tryAllocateGroupRegister(PhysicalRegister& r, VirtualRegisterGroup* group, - bool* psuccess, bool* pfixed, LiveInterval** pconflicting); - bool evictInterval(LiveInterval* interval); - void distributeUses(LiveInterval* interval, const LiveIntervalVector& newIntervals); - bool split(LiveInterval* interval, const LiveIntervalVector& newIntervals); - bool requeueIntervals(const LiveIntervalVector& newIntervals); - void spill(LiveInterval* interval); + bool buildLivenessInfo(); + + bool addInitialFixedRange(AnyRegister reg, CodePosition from, CodePosition to); + + VirtualRegister& vreg(const LDefinition* def) { + return vregs[def->virtualRegister()]; + } + VirtualRegister& vreg(const LAllocation* alloc) { + MOZ_ASSERT(alloc->isUse()); + return vregs[alloc->toUse()->virtualRegister()]; + } + + // Allocation methods. + bool tryMergeBundles(LiveBundle* bundle0, LiveBundle* bundle1); + bool tryMergeReusedRegister(VirtualRegister& def, VirtualRegister& input); + bool mergeAndQueueRegisters(); + bool tryAllocateFixed(LiveBundle* bundle, Requirement requirement, + bool* success, bool* pfixed, LiveBundleVector& conflicting); + bool tryAllocateNonFixed(LiveBundle* bundle, Requirement requirement, Requirement hint, + bool* success, bool* pfixed, LiveBundleVector& conflicting); + bool processBundle(LiveBundle* bundle); + bool computeRequirement(LiveBundle* bundle, Requirement *prequirement, Requirement *phint); + bool tryAllocateRegister(PhysicalRegister& r, LiveBundle* bundle, + bool* success, bool* pfixed, LiveBundleVector& conflicting); + bool evictBundle(LiveBundle* bundle); + bool splitAndRequeueBundles(LiveBundle* bundle, const LiveBundleVector& newBundles); + bool spill(LiveBundle* bundle); bool isReusedInput(LUse* use, LNode* ins, bool considerCopy); bool isRegisterUse(LUse* use, LNode* ins, bool considerCopy = false); - bool isRegisterDefinition(LiveInterval* interval); - bool addLiveInterval(LiveIntervalVector& intervals, uint32_t vreg, - LiveInterval* spillInterval, - CodePosition from, CodePosition to); - bool pickStackSlot(LiveInterval* interval); - bool reuseOrAllocateStackSlot(const LiveIntervalVector& intervals, LDefinition::Type type, - LAllocation* palloc); - bool insertAllRanges(AllocatedRangeSet& set, const LiveIntervalVector& intervals); + bool isRegisterDefinition(LiveRange* range); + bool pickStackSlot(SpillSet* spill); + bool insertAllRanges(LiveRangeSet& set, LiveBundle* bundle); + // Reification methods. bool pickStackSlots(); bool resolveControlFlow(); bool reifyAllocations(); bool populateSafepoints(); bool annotateMoveGroups(); + size_t findFirstNonCallSafepoint(CodePosition from); + size_t findFirstSafepoint(CodePosition pos, size_t startFrom); + void addLiveRegistersForRange(VirtualRegister& reg, LiveRange* range); - void dumpRegisterGroups(); + bool addMove(LMoveGroup* moves, LiveRange* from, LiveRange* to, LDefinition::Type type) { + LAllocation fromAlloc = from->bundle()->allocation(); + LAllocation toAlloc = to->bundle()->allocation(); + MOZ_ASSERT(fromAlloc != toAlloc); + return moves->add(fromAlloc, toAlloc, type); + } + + bool moveInput(LInstruction* ins, LiveRange* from, LiveRange* to, LDefinition::Type type) { + if (from->bundle()->allocation() == to->bundle()->allocation()) + return true; + LMoveGroup* moves = getInputMoveGroup(ins); + return addMove(moves, from, to, type); + } + + bool moveAfter(LInstruction* ins, LiveRange* from, LiveRange* to, LDefinition::Type type) { + if (from->bundle()->allocation() == to->bundle()->allocation()) + return true; + LMoveGroup* moves = getMoveGroupAfter(ins); + return addMove(moves, from, to, type); + } + + bool moveAtExit(LBlock* block, LiveRange* from, LiveRange* to, LDefinition::Type type) { + if (from->bundle()->allocation() == to->bundle()->allocation()) + return true; + LMoveGroup* moves = block->getExitMoveGroup(alloc()); + return addMove(moves, from, to, type); + } + + bool moveAtEntry(LBlock* block, LiveRange* from, LiveRange* to, LDefinition::Type type) { + if (from->bundle()->allocation() == to->bundle()->allocation()) + return true; + LMoveGroup* moves = block->getEntryMoveGroup(alloc()); + return addMove(moves, from, to, type); + } + + // Debugging methods. void dumpFixedRanges(); void dumpAllocations(); - struct PrintLiveIntervalRange; + struct PrintLiveRange; - bool minimalDef(const LiveInterval* interval, LNode* ins); - bool minimalUse(const LiveInterval* interval, LNode* ins); - bool minimalInterval(const LiveInterval* interval, bool* pfixed = nullptr); + bool minimalDef(LiveRange* range, LNode* ins); + bool minimalUse(LiveRange* range, LNode* ins); + bool minimalBundle(LiveBundle* bundle, bool* pfixed = nullptr); // Heuristic methods. - size_t computePriority(const LiveInterval* interval); - size_t computeSpillWeight(const LiveInterval* interval); + size_t computePriority(LiveBundle* bundle); + size_t computeSpillWeight(LiveBundle* bundle); - size_t computePriority(const VirtualRegisterGroup* group); - size_t computeSpillWeight(const VirtualRegisterGroup* group); + size_t maximumSpillWeight(const LiveBundleVector& bundles); - size_t maximumSpillWeight(const LiveIntervalVector& intervals); + bool chooseBundleSplit(LiveBundle* bundle, bool fixed, LiveBundle* conflict); - bool chooseIntervalSplit(LiveInterval* interval, bool fixed, LiveInterval* conflict); - - bool splitAt(LiveInterval* interval, + bool splitAt(LiveBundle* bundle, const SplitPositionVector& splitPositions); - bool trySplitAcrossHotcode(LiveInterval* interval, bool* success); - bool trySplitAfterLastRegisterUse(LiveInterval* interval, LiveInterval* conflict, bool* success); - bool trySplitBeforeFirstRegisterUse(LiveInterval* interval, LiveInterval* conflict, bool* success); - bool splitAtAllRegisterUses(LiveInterval* interval); - bool splitAcrossCalls(LiveInterval* interval); + bool trySplitAcrossHotcode(LiveBundle* bundle, bool* success); + bool trySplitAfterLastRegisterUse(LiveBundle* bundle, LiveBundle* conflict, bool* success); + bool trySplitBeforeFirstRegisterUse(LiveBundle* bundle, LiveBundle* conflict, bool* success); + bool splitAcrossCalls(LiveBundle* bundle); bool compilingAsmJS() { return mir->info().compilingAsmJS(); } - bool isVirtualStackSlot(LAllocation alloc) { - return alloc.isStackSlot() && - LAllocation::DATA_MASK - alloc.toStackSlot()->slot() < numVirtualStackSlots; - } + void dumpVregs(); }; } // namespace jit diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 43e98d0e6c..e71ed56b3c 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -454,7 +454,7 @@ GetStubReturnAddress(JSContext* cx, jsbytecode* pc) return cx->compartment()->jitCompartment()->baselineSetPropReturnAddr(); // This should be a call op of some kind, now. MOZ_ASSERT(IsCallPC(pc)); - return cx->compartment()->jitCompartment()->baselineCallReturnAddr(); + return cx->compartment()->jitCompartment()->baselineCallReturnAddr(JSOp(*pc) == JSOP_NEW); } static inline jsbytecode* diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 8f69c59b98..b8948fc791 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -8,7 +8,6 @@ #include "mozilla/UniquePtr.h" -#include "jit/BaselineHelpers.h" #include "jit/BaselineIC.h" #include "jit/BaselineJIT.h" #include "jit/FixedList.h" @@ -19,6 +18,7 @@ #ifdef JS_ION_PERF # include "jit/PerfSpewer.h" #endif +#include "jit/SharedICHelpers.h" #include "jit/VMFunctions.h" #include "vm/TraceLogging.h" diff --git a/js/src/jit/BaselineDebugModeOSR.cpp b/js/src/jit/BaselineDebugModeOSR.cpp index 599856315f..a7fba842e9 100644 --- a/js/src/jit/BaselineDebugModeOSR.cpp +++ b/js/src/jit/BaselineDebugModeOSR.cpp @@ -759,7 +759,7 @@ CloneOldBaselineStub(JSContext* cx, DebugModeOSREntryVector& entries, size_t ent switch (oldStub->kind()) { #define CASE_KIND(kindName) \ case ICStub::kindName: \ - entry.newStub = IC##kindName::Clone(stubSpace, firstMonitorStub, \ + entry.newStub = IC##kindName::Clone(cx, stubSpace, firstMonitorStub, \ *oldStub->to##kindName()); \ break; PATCHABLE_ICSTUB_KIND_LIST(CASE_KIND) diff --git a/js/src/jit/BaselineFrameInfo.h b/js/src/jit/BaselineFrameInfo.h index d50b32cdd9..6c4acac499 100644 --- a/js/src/jit/BaselineFrameInfo.h +++ b/js/src/jit/BaselineFrameInfo.h @@ -10,9 +10,9 @@ #include "mozilla/Alignment.h" #include "jit/BaselineFrame.h" -#include "jit/BaselineRegisters.h" #include "jit/FixedList.h" #include "jit/MacroAssembler.h" +#include "jit/SharedICRegisters.h" namespace js { namespace jit { diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index e643183477..a4ba9a1001 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -17,7 +17,6 @@ #include "builtin/Eval.h" #include "builtin/SIMD.h" #include "jit/BaselineDebugModeOSR.h" -#include "jit/BaselineHelpers.h" #include "jit/BaselineJIT.h" #include "jit/JitSpewer.h" #include "jit/Linker.h" @@ -25,6 +24,7 @@ #ifdef JS_ION_PERF # include "jit/PerfSpewer.h" #endif +#include "jit/SharedICHelpers.h" #include "jit/VMFunctions.h" #include "js/Conversions.h" #include "vm/Opcodes.h" @@ -493,7 +493,7 @@ ICStub::trace(JSTracer* trc) } case ICStub::SetProp_CallScripted: { ICSetProp_CallScripted* callStub = toSetProp_CallScripted(); - callStub->guard().trace(trc); + callStub->receiverGuard().trace(trc); TraceEdge(trc, &callStub->holder(), "baseline-setpropcallscripted-stub-holder"); TraceEdge(trc, &callStub->holderShape(), "baseline-setpropcallscripted-stub-holdershape"); TraceEdge(trc, &callStub->setter(), "baseline-setpropcallscripted-stub-setter"); @@ -501,7 +501,7 @@ ICStub::trace(JSTracer* trc) } case ICStub::SetProp_CallNative: { ICSetProp_CallNative* callStub = toSetProp_CallNative(); - callStub->guard().trace(trc); + callStub->receiverGuard().trace(trc); TraceEdge(trc, &callStub->holder(), "baseline-setpropcallnative-stub-holder"); TraceEdge(trc, &callStub->holderShape(), "baseline-setpropcallnative-stub-holdershape"); TraceEdge(trc, &callStub->setter(), "baseline-setpropcallnative-stub-setter"); @@ -1775,7 +1775,7 @@ DoNewObject(JSContext* cx, BaselineFrame* frame, ICNewObject_Fallback* stub, Mut ICStubSpace* space = ICStubCompiler::StubSpaceForKind(ICStub::NewObject_WithTemplate, script); - ICStub* templateStub = ICStub::New(space, code); + ICStub* templateStub = ICStub::New(cx, space, code); if (!templateStub) return false; @@ -3589,10 +3589,6 @@ IsCacheableSetPropCall(JSContext* cx, JSObject* obj, JSObject* holder, Shape* sh { MOZ_ASSERT(isScripted); - // Currently we only optimize setter calls for setters bound on prototypes. - if (obj == holder) - return false; - if (!shape || !IsCacheableProtoChain(obj, holder)) return false; @@ -6438,26 +6434,40 @@ static bool UpdateExistingSetPropCallStubs(ICSetProp_Fallback* fallbackStub, ICStub::Kind kind, NativeObject* holder, - ReceiverGuard receiverGuard, + JSObject* receiver, JSFunction* setter) { MOZ_ASSERT(kind == ICStub::SetProp_CallScripted || kind == ICStub::SetProp_CallNative); + MOZ_ASSERT(holder); + MOZ_ASSERT(receiver); + + bool isOwnSetter = (holder == receiver); bool foundMatchingStub = false; + ReceiverGuard receiverGuard(receiver); for (ICStubConstIterator iter = fallbackStub->beginChainConst(); !iter.atEnd(); iter++) { if (iter->kind() == kind) { ICSetPropCallSetter* setPropStub = static_cast(*iter); - if (setPropStub->holder() == holder) { + if (setPropStub->holder() == holder && setPropStub->isOwnSetter() == isOwnSetter) { + // If this is an own setter, update the receiver guard as well, + // since that's the shape we'll be guarding on. Furthermore, + // isOwnSetter() relies on holderShape_ and receiverGuard_ being + // the same shape. + if (isOwnSetter) + setPropStub->receiverGuard().update(receiverGuard); + + MOZ_ASSERT(setPropStub->holderShape() != holder->lastProperty() || + !setPropStub->receiverGuard().matches(receiverGuard), + "Why didn't we end up using this stub?"); + // We want to update the holder shape to match the new one no // matter what, even if the receiver shape is different. - MOZ_ASSERT(setPropStub->holderShape() != holder->lastProperty() || - !setPropStub->guard().matches(receiverGuard), - "Why didn't we end up using this stub?"); setPropStub->holderShape() = holder->lastProperty(); + // Make sure to update the setter, since a shape change might // have changed which setter we want to use. setPropStub->setter() = setter; - if (setPropStub->guard().matches(receiverGuard)) + if (setPropStub->receiverGuard().matches(receiverGuard)) foundMatchingStub = true; } } @@ -7853,14 +7863,14 @@ ICGetPropNativeCompiler::getStub(ICStubSpace* space) switch (kind) { case ICStub::GetProp_Native: { MOZ_ASSERT(obj_ == holder_); - return ICStub::New(space, getStubCode(), firstMonitorStub_, guard, offset_); + return newStub(space, getStubCode(), firstMonitorStub_, guard, offset_); } case ICStub::GetProp_NativePrototype: { MOZ_ASSERT(obj_ != holder_); Shape* holderShape = holder_->as().lastProperty(); - return ICStub::New(space, getStubCode(), firstMonitorStub_, guard, - offset_, holder_, holderShape); + return newStub(space, getStubCode(), firstMonitorStub_, guard, + offset_, holder_, holderShape); } default: @@ -8373,12 +8383,12 @@ ICGetPropCallDOMProxyNativeCompiler::getStub(ICStubSpace* space) expandoShape = expandoVal.toObject().as().lastProperty(); if (kind == ICStub::GetProp_CallDOMProxyNative) { - return ICStub::New( + return newStub( space, getStubCode(), firstMonitorStub_, shape, expandoShape, holder_, holderShape, getter_, pcOffset_); } - return ICStub::New( + return newStub( space, getStubCode(), firstMonitorStub_, shape, expandoAndGeneration, generation, expandoShape, holder_, holderShape, getter_, pcOffset_); @@ -8388,7 +8398,7 @@ ICStub* ICGetProp_DOMProxyShadowed::Compiler::getStub(ICStubSpace* space) { RootedShape shape(cx, proxy_->maybeShape()); - return New(space, getStubCode(), firstMonitorStub_, shape, + return New(cx, space, getStubCode(), firstMonitorStub_, shape, proxy_->handler(), name_, pcOffset_); } @@ -8539,10 +8549,10 @@ ICGetProp_ArgumentsCallee::Compiler::generateStubCode(MacroAssembler& masm) } /* static */ ICGetProp_Generic* -ICGetProp_Generic::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetProp_Generic::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_Generic& other) { - return New(space, other.jitCode(), firstMonitorStub); + return New(cx, space, other.jitCode(), firstMonitorStub); } static bool @@ -8840,11 +8850,10 @@ TryAttachSetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecode* pc, // Try handling scripted setters. if (cacheableCall && isScripted) { RootedFunction callee(cx, &shape->setterObject()->as()); - MOZ_ASSERT(obj != holder); MOZ_ASSERT(callee->hasScript()); if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallScripted, - &holder->as(), receiverGuard, callee)) { + &holder->as(), obj, callee)) { *attached = true; return true; } @@ -8865,11 +8874,10 @@ TryAttachSetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecode* pc, // Try handling JSNative setters. if (cacheableCall && !isScripted) { RootedFunction callee(cx, &shape->setterObject()->as()); - MOZ_ASSERT(obj != holder); MOZ_ASSERT(callee->isNative()); if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallNative, - &holder->as(), receiverGuard, callee)) { + &holder->as(), obj, callee)) { *attached = true; return true; } @@ -9609,14 +9617,16 @@ ICSetProp_CallScripted::Compiler::generateStubCode(MacroAssembler& masm) // Unbox and shape guard. Register objReg = masm.extractObject(R0, ExtractTemp0); - GuardReceiverObject(masm, ReceiverGuard(obj_), objReg, scratch, - ICSetProp_CallScripted::offsetOfGuard(), &failureUnstow); + GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch, + ICSetProp_CallScripted::offsetOfReceiverGuard(), &failureUnstow); - Register holderReg = regs.takeAny(); - masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfHolder()), holderReg); - masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfHolderShape()), scratch); - masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failureUnstow); - regs.add(holderReg); + if (receiver_ != holder_) { + Register holderReg = regs.takeAny(); + masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfHolder()), holderReg); + masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfHolderShape()), scratch); + masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failureUnstow); + regs.add(holderReg); + } // Push a stub frame so that we can perform a non-tail call. enterStubFrame(masm, scratch); @@ -9728,14 +9738,16 @@ ICSetProp_CallNative::Compiler::generateStubCode(MacroAssembler& masm) // Unbox and shape guard. Register objReg = masm.extractObject(R0, ExtractTemp0); - GuardReceiverObject(masm, ReceiverGuard(obj_), objReg, scratch, - ICSetProp_CallNative::offsetOfGuard(), &failureUnstow); + GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch, + ICSetProp_CallNative::offsetOfReceiverGuard(), &failureUnstow); - Register holderReg = regs.takeAny(); - masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallNative::offsetOfHolder()), holderReg); - masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallNative::offsetOfHolderShape()), scratch); - masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failureUnstow); - regs.add(holderReg); + if (receiver_ != holder_) { + Register holderReg = regs.takeAny(); + masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallNative::offsetOfHolder()), holderReg); + masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallNative::offsetOfHolderShape()), scratch); + masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failureUnstow); + regs.add(holderReg); + } // Push a stub frame so that we can perform a non-tail call. enterStubFrame(masm, scratch); @@ -10795,24 +10807,20 @@ ICCall_Fallback::Compiler::generateStubCode(MacroAssembler& masm) leaveStubFrame(masm, true); - // R1 and R0 are taken. - regs = availableGeneralRegs(2); - Register scratch = regs.takeAny(); - // If this is a |constructing| call, if the callee returns a non-object, we replace it with // the |this| object passed in. - MOZ_ASSERT(JSReturnOperand == R0); - Label skipThisReplace; - masm.load16ZeroExtend(Address(BaselineStubReg, ICStub::offsetOfExtra()), scratch); - masm.branchTest32(Assembler::Zero, scratch, Imm32(ICCall_Fallback::CONSTRUCTING_FLAG), - &skipThisReplace); - masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); - masm.moveValue(R1, R0); + if (isConstructing_) { + MOZ_ASSERT(JSReturnOperand == R0); + Label skipThisReplace; + + masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); + masm.moveValue(R1, R0); #ifdef DEBUG - masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); - masm.assumeUnreachable("Failed to return object in constructing call."); + masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); + masm.assumeUnreachable("Failed to return object in constructing call."); #endif - masm.bind(&skipThisReplace); + masm.bind(&skipThisReplace); + } // At this point, BaselineStubReg points to the ICCall_Fallback stub, which is NOT // a MonitoredStub, but rather a MonitoredFallbackStub. To use EmitEnterTypeMonitorIC, @@ -10833,7 +10841,8 @@ ICCall_Fallback::Compiler::postGenerateStubCode(MacroAssembler& masm, Handlecompartment()->jitCompartment()->initBaselineCallReturnAddr(code->raw() + offset.offset()); + cx->compartment()->jitCompartment()->initBaselineCallReturnAddr(code->raw() + offset.offset(), + isConstructing_); return true; } @@ -11852,7 +11861,7 @@ ICTableSwitch::Compiler::getStub(ICStubSpace* space) pc += JUMP_OFFSET_LEN; } - return ICStub::New(space, code, table, low, length, defaultpc); + return newStub(space, code, table, low, length, defaultpc); } void @@ -12475,22 +12484,24 @@ ICGetElemNativePrototypeCallStub::ICGetElemNativePrototypeCallStub( {} /* static */ ICGetElem_NativePrototypeCallNative* -ICGetElem_NativePrototypeCallNative::Clone(ICStubSpace* space, +ICGetElem_NativePrototypeCallNative::Clone(JSContext* cx, + ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_NativePrototypeCallNative& other) { - return New(space, other.jitCode(), firstMonitorStub, + return New(cx, space, other.jitCode(), firstMonitorStub, other.shape(), other.name(), other.accessType(), other.needsAtomize(), other.getter(), other.pcOffset_, other.holder(), other.holderShape()); } /* static */ ICGetElem_NativePrototypeCallScripted* -ICGetElem_NativePrototypeCallScripted::Clone(ICStubSpace* space, +ICGetElem_NativePrototypeCallScripted::Clone(JSContext* cx, + ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_NativePrototypeCallScripted& other) { - return New(space, other.jitCode(), firstMonitorStub, + return New(cx, space, other.jitCode(), firstMonitorStub, other.shape(), other.name(), other.accessType(), other.needsAtomize(), other.getter(), other.pcOffset_, other.holder(), other.holderShape()); @@ -12502,10 +12513,10 @@ ICGetElem_Dense::ICGetElem_Dense(JitCode* stubCode, ICStub* firstMonitorStub, Sh { } /* static */ ICGetElem_Dense* -ICGetElem_Dense::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetElem_Dense::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_Dense& other) { - return New(space, other.jitCode(), firstMonitorStub, other.shape_); + return New(cx, space, other.jitCode(), firstMonitorStub, other.shape_); } ICGetElem_UnboxedArray::ICGetElem_UnboxedArray(JitCode* stubCode, ICStub* firstMonitorStub, @@ -12515,10 +12526,10 @@ ICGetElem_UnboxedArray::ICGetElem_UnboxedArray(JitCode* stubCode, ICStub* firstM { } /* static */ ICGetElem_UnboxedArray* -ICGetElem_UnboxedArray::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetElem_UnboxedArray::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_UnboxedArray& other) { - return New(space, other.jitCode(), firstMonitorStub, other.group_); + return New(cx, space, other.jitCode(), firstMonitorStub, other.group_); } ICGetElem_TypedArray::ICGetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type) @@ -12530,10 +12541,10 @@ ICGetElem_TypedArray::ICGetElem_TypedArray(JitCode* stubCode, Shape* shape, Scal } /* static */ ICGetElem_Arguments* -ICGetElem_Arguments::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetElem_Arguments::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_Arguments& other) { - return New(space, other.jitCode(), firstMonitorStub, other.which()); + return New(cx, space, other.jitCode(), firstMonitorStub, other.which()); } ICSetElem_DenseOrUnboxedArray::ICSetElem_DenseOrUnboxedArray(JitCode* stubCode, Shape* shape, ObjectGroup* group) @@ -12559,7 +12570,7 @@ ICSetElemDenseOrUnboxedArrayAddCompiler::getStubSpecific(ICStubSpace* space, con if (!group) return nullptr; Rooted stubCode(cx, getStubCode()); - return ICStub::New>(space, stubCode, group, shapes); + return newStub>(space, stubCode, group, shapes); } ICSetElem_TypedArray::ICSetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type, @@ -12673,11 +12684,11 @@ ICGetPropNativeStub::ICGetPropNativeStub(ICStub::Kind kind, JitCode* stubCode, { } /* static */ ICGetProp_Native* -ICGetProp_Native::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetProp_Native::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_Native& other) { - return New(space, other.jitCode(), firstMonitorStub, other.receiverGuard(), - other.offset()); + return New(cx, space, other.jitCode(), firstMonitorStub, + other.receiverGuard(), other.offset()); } ICGetProp_NativePrototype::ICGetProp_NativePrototype(JitCode* stubCode, ICStub* firstMonitorStub, @@ -12689,10 +12700,10 @@ ICGetProp_NativePrototype::ICGetProp_NativePrototype(JitCode* stubCode, ICStub* { } /* static */ ICGetProp_NativePrototype* -ICGetProp_NativePrototype::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetProp_NativePrototype::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_NativePrototype& other) { - return New(space, other.jitCode(), firstMonitorStub, + return New(cx, space, other.jitCode(), firstMonitorStub, other.receiverGuard(), other.offset(), other.holder_, other.holderShape_); } @@ -12765,20 +12776,20 @@ ICInstanceOf_Function::ICInstanceOf_Function(JitCode* stubCode, Shape* shape, { } /* static */ ICGetProp_CallScripted* -ICGetProp_CallScripted::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetProp_CallScripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_CallScripted& other) { - return New(space, other.jitCode(), firstMonitorStub, + return New(cx, space, other.jitCode(), firstMonitorStub, other.receiverGuard(), other.holder_, other.holderShape_, other.getter_, other.pcOffset_); } /* static */ ICGetProp_CallNative* -ICGetProp_CallNative::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetProp_CallNative::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_CallNative& other) { - return New(space, other.jitCode(), firstMonitorStub, + return New(cx, space, other.jitCode(), firstMonitorStub, other.receiverGuard(), other.holder_, other.holderShape_, other.getter_, other.pcOffset_); } @@ -12799,7 +12810,7 @@ ICSetProp_Native::Compiler::getStub(ICStubSpace* space) return nullptr; RootedShape shape(cx, LastPropertyForSetProp(obj_)); - ICSetProp_Native* stub = ICStub::New(space, getStubCode(), group, shape, offset_); + ICSetProp_Native* stub = newStub(space, getStubCode(), group, shape, offset_); if (!stub || !stub->initUpdatingChain(cx, space)) return nullptr; return stub; @@ -12851,11 +12862,11 @@ ICSetPropNativeAddCompiler::ICSetPropNativeAddCompiler(JSContext* cx, HandleObje MOZ_ASSERT(protoChainDepth_ <= ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH); } -ICSetPropCallSetter::ICSetPropCallSetter(Kind kind, JitCode* stubCode, ReceiverGuard guard, +ICSetPropCallSetter::ICSetPropCallSetter(Kind kind, JitCode* stubCode, ReceiverGuard receiverGuard, JSObject* holder, Shape* holderShape, JSFunction* setter, uint32_t pcOffset) : ICStub(kind, stubCode), - guard_(guard), + receiverGuard_(receiverGuard), holder_(holder), holderShape_(holderShape), setter_(setter), @@ -12865,17 +12876,20 @@ ICSetPropCallSetter::ICSetPropCallSetter(Kind kind, JitCode* stubCode, ReceiverG } /* static */ ICSetProp_CallScripted* -ICSetProp_CallScripted::Clone(ICStubSpace* space, ICStub*, ICSetProp_CallScripted& other) +ICSetProp_CallScripted::Clone(JSContext* cx, ICStubSpace* space, ICStub*, + ICSetProp_CallScripted& other) { - return New(space, other.jitCode(), other.guard(), other.holder_, - other.holderShape_, other.setter_, other.pcOffset_); + return New(cx, space, other.jitCode(), other.receiverGuard(), + other.holder_, other.holderShape_, other.setter_, + other.pcOffset_); } /* static */ ICSetProp_CallNative* -ICSetProp_CallNative::Clone(ICStubSpace* space, ICStub*, ICSetProp_CallNative& other) +ICSetProp_CallNative::Clone(JSContext* cx, ICStubSpace* space, ICStub*, ICSetProp_CallNative& other) { - return New(space, other.jitCode(), other.guard(), other.holder_, - other.holderShape_, other.setter_, other.pcOffset_); + return New(cx, space, other.jitCode(), other.receiverGuard(), + other.holder_, other.holderShape_, other.setter_, + other.pcOffset_); } ICCall_Scripted::ICCall_Scripted(JitCode* stubCode, ICStub* firstMonitorStub, @@ -12888,16 +12902,18 @@ ICCall_Scripted::ICCall_Scripted(JitCode* stubCode, ICStub* firstMonitorStub, { } /* static */ ICCall_Scripted* -ICCall_Scripted::Clone(ICStubSpace* space, ICStub* firstMonitorStub, ICCall_Scripted& other) +ICCall_Scripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICCall_Scripted& other) { - return New(space, other.jitCode(), firstMonitorStub, other.callee_, + return New(cx, space, other.jitCode(), firstMonitorStub, other.callee_, other.templateObject_, other.pcOffset_); } /* static */ ICCall_AnyScripted* -ICCall_AnyScripted::Clone(ICStubSpace* space, ICStub* firstMonitorStub, ICCall_AnyScripted& other) +ICCall_AnyScripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICCall_AnyScripted& other) { - return New(space, other.jitCode(), firstMonitorStub, other.pcOffset_); + return New(cx, space, other.jitCode(), firstMonitorStub, other.pcOffset_); } ICCall_Native::ICCall_Native(JitCode* stubCode, ICStub* firstMonitorStub, @@ -12918,9 +12934,10 @@ ICCall_Native::ICCall_Native(JitCode* stubCode, ICStub* firstMonitorStub, } /* static */ ICCall_Native* -ICCall_Native::Clone(ICStubSpace* space, ICStub* firstMonitorStub, ICCall_Native& other) +ICCall_Native::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICCall_Native& other) { - return New(space, other.jitCode(), firstMonitorStub, other.callee_, + return New(cx, space, other.jitCode(), firstMonitorStub, other.callee_, other.templateObject_, other.pcOffset_); } @@ -12942,9 +12959,10 @@ ICCall_ClassHook::ICCall_ClassHook(JitCode* stubCode, ICStub* firstMonitorStub, } /* static */ ICCall_ClassHook* -ICCall_ClassHook::Clone(ICStubSpace* space, ICStub* firstMonitorStub, ICCall_ClassHook& other) +ICCall_ClassHook::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICCall_ClassHook& other) { - ICCall_ClassHook* res = New(space, other.jitCode(), firstMonitorStub, + ICCall_ClassHook* res = New(cx, space, other.jitCode(), firstMonitorStub, other.clasp(), nullptr, other.templateObject_, other.pcOffset_); if (res) @@ -12953,27 +12971,29 @@ ICCall_ClassHook::Clone(ICStubSpace* space, ICStub* firstMonitorStub, ICCall_Cla } /* static */ ICCall_ScriptedApplyArray* -ICCall_ScriptedApplyArray::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICCall_ScriptedApplyArray::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICCall_ScriptedApplyArray& other) { - return New(space, other.jitCode(), firstMonitorStub, + return New(cx, space, other.jitCode(), firstMonitorStub, other.pcOffset_); } /* static */ ICCall_ScriptedApplyArguments* -ICCall_ScriptedApplyArguments::Clone(ICStubSpace* space, +ICCall_ScriptedApplyArguments::Clone(JSContext* cx, + ICStubSpace* space, ICStub* firstMonitorStub, ICCall_ScriptedApplyArguments& other) { - return New(space, other.jitCode(), firstMonitorStub, + return New(cx, space, other.jitCode(), firstMonitorStub, other.pcOffset_); } /* static */ ICCall_ScriptedFunCall* -ICCall_ScriptedFunCall::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICCall_ScriptedFunCall::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICCall_ScriptedFunCall& other) { - return New(space, other.jitCode(), firstMonitorStub, other.pcOffset_); + return New(cx, space, other.jitCode(), firstMonitorStub, + other.pcOffset_); } ICGetPropCallDOMProxyNativeStub::ICGetPropCallDOMProxyNativeStub(Kind kind, JitCode* stubCode, @@ -13009,21 +13029,23 @@ ICGetPropCallDOMProxyNativeCompiler::ICGetPropCallDOMProxyNativeCompiler(JSConte } /* static */ ICGetProp_CallDOMProxyNative* -ICGetProp_CallDOMProxyNative::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetProp_CallDOMProxyNative::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_CallDOMProxyNative& other) { - return New(space, other.jitCode(), firstMonitorStub, + return New(cx, space, other.jitCode(), firstMonitorStub, other.receiverGuard_.shape(), other.expandoShape_, other.holder_, other.holderShape_, other.getter_, other.pcOffset_); } /* static */ ICGetProp_CallDOMProxyWithGenerationNative* -ICGetProp_CallDOMProxyWithGenerationNative::Clone(ICStubSpace* space, +ICGetProp_CallDOMProxyWithGenerationNative::Clone(JSContext* cx, + ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_CallDOMProxyWithGenerationNative& other) { - return New(space, other.jitCode(), firstMonitorStub, + return New(cx, space, other.jitCode(), + firstMonitorStub, other.receiverGuard_.shape(), other.expandoAndGeneration_, other.generation_, @@ -13046,11 +13068,12 @@ ICGetProp_DOMProxyShadowed::ICGetProp_DOMProxyShadowed(JitCode* stubCode, { } /* static */ ICGetProp_DOMProxyShadowed* -ICGetProp_DOMProxyShadowed::Clone(ICStubSpace* space, ICStub* firstMonitorStub, +ICGetProp_DOMProxyShadowed::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_DOMProxyShadowed& other) { - return New(space, other.jitCode(), firstMonitorStub, other.shape_, - other.proxyHandler_, other.name_, other.pcOffset_); + return New(cx, space, other.jitCode(), firstMonitorStub, + other.shape_, other.proxyHandler_, other.name_, + other.pcOffset_); } // diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index 04e4885bd3..8c7005969e 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -16,7 +16,7 @@ #include "builtin/TypedObject.h" #include "jit/BaselineJIT.h" -#include "jit/BaselineRegisters.h" +#include "jit/SharedICRegisters.h" #include "vm/ArrayObject.h" #include "vm/ReceiverGuard.h" #include "vm/TypedArrayCommon.h" @@ -637,10 +637,13 @@ class ICStub void trace(JSTracer* trc); template - static T* New(ICStubSpace* space, JitCode* code, Args&&... args) { + static T* New(JSContext* cx, ICStubSpace* space, JitCode* code, Args&&... args) { if (!code) return nullptr; - return space->allocate(code, mozilla::Forward(args)...); + T* result = space->allocate(code, mozilla::Forward(args)...); + if (!result) + ReportOutOfMemory(cx); + return result; } protected: @@ -1084,7 +1087,6 @@ class ICStubCompiler // Prevent GC in the middle of stub compilation. js::gc::AutoSuppressGC suppressGC; - protected: JSContext* cx; ICStub::Kind kind; @@ -1168,6 +1170,11 @@ class ICStubCompiler inline bool emitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val, Register scratch, LiveGeneralRegisterSet saveRegs); + template + T* newStub(Args&&... args) { + return ICStub::New(cx, mozilla::Forward(args)...); + } + public: virtual ICStub* getStub(ICStubSpace* space) = 0; @@ -1222,7 +1229,7 @@ class ICWarmUpCounter_Fallback : public ICFallbackStub { } ICWarmUpCounter_Fallback* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -1469,7 +1476,7 @@ class ICTypeMonitor_Fallback : public ICStub { } ICTypeMonitor_Fallback* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), mainFallbackStub_, + return newStub(space, getStubCode(), mainFallbackStub_, argumentIndex_); } }; @@ -1503,7 +1510,7 @@ class ICTypeMonitor_PrimitiveSet : public TypeCheckPrimitiveSetStub ICTypeMonitor_PrimitiveSet* getStub(ICStubSpace* space) { MOZ_ASSERT(!existingStub_); - return ICStub::New(space, getStubCode(), flags_); + return newStub(space, getStubCode(), flags_); } }; }; @@ -1537,7 +1544,7 @@ class ICTypeMonitor_SingleObject : public ICStub { } ICTypeMonitor_SingleObject* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), obj_); + return newStub(space, getStubCode(), obj_); } }; }; @@ -1571,7 +1578,7 @@ class ICTypeMonitor_ObjectGroup : public ICStub { } ICTypeMonitor_ObjectGroup* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), group_); + return newStub(space, getStubCode(), group_); } }; }; @@ -1602,7 +1609,7 @@ class ICTypeUpdate_Fallback : public ICStub { } ICTypeUpdate_Fallback* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -1635,7 +1642,7 @@ class ICTypeUpdate_PrimitiveSet : public TypeCheckPrimitiveSetStub ICTypeUpdate_PrimitiveSet* getStub(ICStubSpace* space) { MOZ_ASSERT(!existingStub_); - return ICStub::New(space, getStubCode(), flags_); + return newStub(space, getStubCode(), flags_); } }; }; @@ -1670,7 +1677,7 @@ class ICTypeUpdate_SingleObject : public ICStub { } ICTypeUpdate_SingleObject* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), obj_); + return newStub(space, getStubCode(), obj_); } }; }; @@ -1705,7 +1712,7 @@ class ICTypeUpdate_ObjectGroup : public ICStub { } ICTypeUpdate_ObjectGroup* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), group_); + return newStub(space, getStubCode(), group_); } }; }; @@ -1731,7 +1738,7 @@ class ICThis_Fallback : public ICFallbackStub : ICStubCompiler(cx, ICStub::This_Fallback) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -1763,7 +1770,7 @@ class ICNewArray_Fallback : public ICFallbackStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), templateGroup); + return newStub(space, getStubCode(), templateGroup); } }; @@ -1801,7 +1808,7 @@ class ICNewObject_Fallback : public ICFallbackStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; @@ -1855,7 +1862,7 @@ class ICCompare_Fallback : public ICFallbackStub : ICStubCompiler(cx, ICStub::Compare_Fallback) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -1878,7 +1885,7 @@ class ICCompare_Int32 : public ICStub : ICMultiStubCompiler(cx, ICStub::Compare_Int32, op) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -1902,7 +1909,7 @@ class ICCompare_Double : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -1941,7 +1948,8 @@ class ICCompare_NumberWithUndefined : public ICStub } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), lhsIsUndefined); + return newStub(space, getStubCode(), + lhsIsUndefined); } }; }; @@ -1965,7 +1973,7 @@ class ICCompare_String : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -1989,7 +1997,7 @@ class ICCompare_Boolean : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2013,7 +2021,7 @@ class ICCompare_Object : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2049,7 +2057,7 @@ class ICCompare_ObjectWithUndefined : public ICStub } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2089,7 +2097,7 @@ class ICCompare_Int32WithBoolean : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), lhsIsInt32_); + return newStub(space, getStubCode(), lhsIsInt32_); } }; }; @@ -2117,7 +2125,7 @@ class ICToBool_Fallback : public ICFallbackStub : ICStubCompiler(cx, ICStub::ToBool_Fallback) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2140,7 +2148,7 @@ class ICToBool_Int32 : public ICStub : ICStubCompiler(cx, ICStub::ToBool_Int32) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2163,7 +2171,7 @@ class ICToBool_String : public ICStub : ICStubCompiler(cx, ICStub::ToBool_String) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2186,7 +2194,7 @@ class ICToBool_NullUndefined : public ICStub : ICStubCompiler(cx, ICStub::ToBool_NullUndefined) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2209,7 +2217,7 @@ class ICToBool_Double : public ICStub : ICStubCompiler(cx, ICStub::ToBool_Double) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2232,7 +2240,7 @@ class ICToBool_Object : public ICStub : ICStubCompiler(cx, ICStub::ToBool_Object) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2258,7 +2266,7 @@ class ICToNumber_Fallback : public ICFallbackStub : ICStubCompiler(cx, ICStub::ToNumber_Fallback) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2307,7 +2315,7 @@ class ICBinaryArith_Fallback : public ICFallbackStub : ICStubCompiler(cx, ICStub::BinaryArith_Fallback) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2347,7 +2355,7 @@ class ICBinaryArith_Int32 : public ICStub op_(op), allowDouble_(allowDouble) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), allowDouble_); + return newStub(space, getStubCode(), allowDouble_); } }; }; @@ -2371,7 +2379,7 @@ class ICBinaryArith_StringConcat : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2407,7 +2415,8 @@ class ICBinaryArith_StringObjectConcat : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), lhsIsString_); + return newStub(space, getStubCode(), + lhsIsString_); } }; }; @@ -2431,7 +2440,7 @@ class ICBinaryArith_Double : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2484,7 +2493,7 @@ class ICBinaryArith_BooleanWithInt32 : public ICStub } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), + return newStub(space, getStubCode(), lhsIsBool_, rhsIsBool_); } }; @@ -2522,7 +2531,8 @@ class ICBinaryArith_DoubleWithInt32 : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), lhsIsDouble_); + return newStub(space, getStubCode(), + lhsIsDouble_); } }; }; @@ -2562,7 +2572,7 @@ class ICUnaryArith_Fallback : public ICFallbackStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2586,7 +2596,7 @@ class ICUnaryArith_Int32 : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2610,7 +2620,7 @@ class ICUnaryArith_Double : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -2657,7 +2667,7 @@ class ICGetElem_Fallback : public ICMonitoredFallbackStub { } ICStub* getStub(ICStubSpace* space) { - ICGetElem_Fallback* stub = ICStub::New(space, getStubCode()); + ICGetElem_Fallback* stub = newStub(space, getStubCode()); if (!stub) return nullptr; if (!stub->initMonitoringChain(cx, space)) @@ -2843,7 +2853,7 @@ class ICGetElem_NativePrototypeCallNative : public ICGetElemNativePrototypeCallS {} public: - static ICGetElem_NativePrototypeCallNative* Clone(ICStubSpace* space, + static ICGetElem_NativePrototypeCallNative* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_NativePrototypeCallNative& other); }; @@ -2865,7 +2875,7 @@ class ICGetElem_NativePrototypeCallScripted : public ICGetElemNativePrototypeCal public: static ICGetElem_NativePrototypeCallScripted* - Clone(ICStubSpace* space, + Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_NativePrototypeCallScripted& other); }; @@ -2940,7 +2950,7 @@ class ICGetElemNativeCompiler : public ICStubCompiler RootedShape shape(cx, obj_->as().lastProperty()); if (kind == ICStub::GetElem_NativeSlot) { MOZ_ASSERT(obj_ == holder_); - return ICStub::New( + return newStub( space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_, offset_); } @@ -2948,20 +2958,20 @@ class ICGetElemNativeCompiler : public ICStubCompiler MOZ_ASSERT(obj_ != holder_); RootedShape holderShape(cx, holder_->as().lastProperty()); if (kind == ICStub::GetElem_NativePrototypeSlot) { - return ICStub::New( + return newStub( space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_, offset_, holder_, holderShape); } if (kind == ICStub::GetElem_NativePrototypeCallNative) { - return ICStub::New( + return newStub( space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_, getter_, pcOffset_, holder_, holderShape); } MOZ_ASSERT(kind == ICStub::GetElem_NativePrototypeCallScripted); if (kind == ICStub::GetElem_NativePrototypeCallScripted) { - return ICStub::New( + return newStub( space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_, getter_, pcOffset_, holder_, holderShape); } @@ -2988,7 +2998,7 @@ class ICGetElem_String : public ICStub : ICStubCompiler(cx, ICStub::GetElem_String) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -3002,7 +3012,7 @@ class ICGetElem_Dense : public ICMonitoredStub ICGetElem_Dense(JitCode* stubCode, ICStub* firstMonitorStub, Shape* shape); public: - static ICGetElem_Dense* Clone(ICStubSpace* space, ICStub* firstMonitorStub, + static ICGetElem_Dense* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_Dense& other); static size_t offsetOfShape() { @@ -3038,7 +3048,7 @@ class ICGetElem_Dense : public ICMonitoredStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, shape_); + return newStub(space, getStubCode(), firstMonitorStub_, shape_); } }; }; @@ -3052,8 +3062,8 @@ class ICGetElem_UnboxedArray : public ICMonitoredStub ICGetElem_UnboxedArray(JitCode* stubCode, ICStub* firstMonitorStub, ObjectGroup* group); public: - static ICGetElem_UnboxedArray* Clone(ICStubSpace* space, ICStub* firstMonitorStub, - ICGetElem_UnboxedArray& other); + static ICGetElem_UnboxedArray* Clone(JSContext* cx, ICStubSpace* space, + ICStub* firstMonitorStub, ICGetElem_UnboxedArray& other); static size_t offsetOfGroup() { return offsetof(ICGetElem_UnboxedArray, group_); @@ -3085,8 +3095,7 @@ class ICGetElem_UnboxedArray : public ICMonitoredStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - group_); + return newStub(space, getStubCode(), firstMonitorStub_, group_); } }; }; @@ -3152,7 +3161,7 @@ class ICGetElem_TypedArray : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), shape_, type_); + return newStub(space, getStubCode(), shape_, type_); } }; }; @@ -3171,7 +3180,7 @@ class ICGetElem_Arguments : public ICMonitoredStub } public: - static ICGetElem_Arguments* Clone(ICStubSpace* space, ICStub* firstMonitorStub, + static ICGetElem_Arguments* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_Arguments& other); Which which() const { @@ -3205,7 +3214,7 @@ class ICGetElem_Arguments : public ICMonitoredStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, which_); + return newStub(space, getStubCode(), firstMonitorStub_, which_); } }; }; @@ -3243,7 +3252,7 @@ class ICSetElem_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -3294,7 +3303,7 @@ class ICSetElem_DenseOrUnboxedArray : public ICUpdatedStub ICUpdatedStub* getStub(ICStubSpace* space) { ICSetElem_DenseOrUnboxedArray* stub = - ICStub::New(space, getStubCode(), shape_, group_); + newStub(space, getStubCode(), shape_, group_); if (!stub || !stub->initUpdatingChain(cx, space)) return nullptr; return stub; @@ -3467,8 +3476,8 @@ class ICSetElem_TypedArray : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), shape_, type_, - expectOutOfBounds_); + return newStub(space, getStubCode(), shape_, type_, + expectOutOfBounds_); } }; }; @@ -3496,7 +3505,7 @@ class ICIn_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -3587,14 +3596,14 @@ class ICInNativeCompiler : public ICStubCompiler RootedShape shape(cx, obj_->as().lastProperty()); if (kind == ICStub::In_Native) { MOZ_ASSERT(obj_ == holder_); - return ICStub::New(space, getStubCode(), shape, name_); + return newStub(space, getStubCode(), shape, name_); } MOZ_ASSERT(obj_ != holder_); MOZ_ASSERT(kind == ICStub::In_NativePrototype); RootedShape holderShape(cx, holder_->as().lastProperty()); - return ICStub::New(space, getStubCode(), shape, name_, holder_, - holderShape); + return newStub(space, getStubCode(), shape, name_, holder_, + holderShape); } }; @@ -3679,9 +3688,8 @@ class ICInNativeDoesNotExistCompiler : public ICStubCompiler template ICStub* getStubSpecific(ICStubSpace* space, const AutoShapeVector* shapes) { - return ICStub::New>(space, getStubCode(), - shapes, name_); - } + return newStub>(space, getStubCode(), shapes, + name_);} ICStub* getStub(ICStubSpace* space); }; @@ -3715,7 +3723,7 @@ class ICIn_Dense : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), shape_); + return newStub(space, getStubCode(), shape_); } }; }; @@ -3752,7 +3760,7 @@ class ICGetName_Fallback : public ICMonitoredFallbackStub { } ICStub* getStub(ICStubSpace* space) { - ICGetName_Fallback* stub = ICStub::New(space, getStubCode()); + ICGetName_Fallback* stub = newStub(space, getStubCode()); if (!stub || !stub->initMonitoringChain(cx, space)) return nullptr; return stub; @@ -3799,8 +3807,8 @@ class ICGetName_Global : public ICMonitoredStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, shape_, - slot_); + return newStub(space, getStubCode(), firstMonitorStub_, shape_, + slot_); } }; }; @@ -3866,7 +3874,8 @@ class ICGetName_Scope : public ICMonitoredStub } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, shapes_, offset_); + return newStub(space, getStubCode(), firstMonitorStub_, shapes_, + offset_); } }; }; @@ -3892,7 +3901,7 @@ class ICBindName_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -3919,7 +3928,7 @@ class ICGetIntrinsic_Fallback : public ICMonitoredFallbackStub ICStub* getStub(ICStubSpace* space) { ICGetIntrinsic_Fallback* stub = - ICStub::New(space, getStubCode()); + newStub(space, getStubCode()); if (!stub || !stub->initMonitoringChain(cx, space)) return nullptr; return stub; @@ -3957,7 +3966,7 @@ class ICGetIntrinsic_Constant : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), value_); + return newStub(space, getStubCode(), value_); } }; }; @@ -4001,7 +4010,7 @@ class ICGetProp_Fallback : public ICMonitoredFallbackStub { } ICStub* getStub(ICStubSpace* space) { - ICGetProp_Fallback* stub = ICStub::New(space, getStubCode()); + ICGetProp_Fallback* stub = newStub(space, getStubCode()); if (!stub || !stub->initMonitoringChain(cx, space)) return nullptr; return stub; @@ -4019,7 +4028,7 @@ class ICGetProp_Generic : public ICMonitoredStub : ICMonitoredStub(ICStub::GetProp_Generic, stubCode, firstMonitorStub) {} public: - static ICGetProp_Generic* Clone(ICStubSpace* space, ICStub* firstMonitorStub, + static ICGetProp_Generic* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_Generic& other); class Compiler : public ICStubCompiler { @@ -4033,7 +4042,7 @@ class ICGetProp_Generic : public ICMonitoredStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_); + return newStub(space, getStubCode(), firstMonitorStub_); } }; }; @@ -4057,7 +4066,7 @@ class ICGetProp_ArrayLength : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -4081,7 +4090,7 @@ class ICGetProp_UnboxedArrayLength : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -4143,8 +4152,8 @@ class ICGetProp_Primitive : public ICMonitoredStub ICStub* getStub(ICStubSpace* space) { RootedShape protoShape(cx, prototype_->as().lastProperty()); - return ICStub::New(space, getStubCode(), firstMonitorStub_, - protoShape, offset_); + return newStub(space, getStubCode(), firstMonitorStub_, + protoShape, offset_); } }; }; @@ -4168,7 +4177,7 @@ class ICGetProp_StringLength : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -4220,7 +4229,7 @@ class ICGetProp_Native : public ICGetPropNativeStub {} public: - static ICGetProp_Native* Clone(ICStubSpace* space, ICStub* firstMonitorStub, + static ICGetProp_Native* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_Native& other); }; @@ -4241,7 +4250,8 @@ class ICGetProp_NativePrototype : public ICGetPropNativeStub uint32_t offset, JSObject* holder, Shape* holderShape); public: - static ICGetProp_NativePrototype* Clone(ICStubSpace* space, + static ICGetProp_NativePrototype* Clone(JSContext* cx, + ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_NativePrototype& other); @@ -4389,7 +4399,7 @@ class ICGetPropNativeDoesNotExistCompiler : public ICStubCompiler template ICStub* getStubSpecific(ICStubSpace* space, const AutoShapeVector* shapes) { ReceiverGuard guard(obj_); - return ICStub::New > + return newStub> (space, getStubCode(), firstMonitorStub_, guard, shapes); } @@ -4447,8 +4457,8 @@ class ICGetProp_Unboxed : public ICMonitoredStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - group_, fieldOffset_); + return newStub(space, getStubCode(), firstMonitorStub_, group_, + fieldOffset_); } }; }; @@ -4516,8 +4526,8 @@ class ICGetProp_TypedObject : public ICMonitoredStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - shape_, fieldOffset_); + return newStub(space, getStubCode(), firstMonitorStub_, shape_, + fieldOffset_); } }; }; @@ -4564,6 +4574,12 @@ class ICGetPropCallGetter : public ICMonitoredStub return receiverGuard_; } + bool isOwnGetter() const { + MOZ_ASSERT(holder_->isNative()); + MOZ_ASSERT(holderShape_); + return receiverGuard_.shape() == holderShape_; + } + static size_t offsetOfHolder() { return offsetof(ICGetPropCallGetter, holder_); } @@ -4580,12 +4596,6 @@ class ICGetPropCallGetter : public ICMonitoredStub return offsetof(ICGetPropCallGetter, receiverGuard_); } - bool isOwnGetter() const { - MOZ_ASSERT(holder_->isNative()); - MOZ_ASSERT(holderShape_); - return receiverGuard_.shape() == holderShape_; - } - class Compiler : public ICStubCompiler { protected: ICStub* firstMonitorStub_; @@ -4638,7 +4648,7 @@ class ICGetProp_CallScripted : public ICGetPropCallGetter {} public: - static ICGetProp_CallScripted* Clone(ICStubSpace* space, + static ICGetProp_CallScripted* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_CallScripted& other); class Compiler : public ICGetPropCallGetter::Compiler { @@ -4656,7 +4666,7 @@ class ICGetProp_CallScripted : public ICGetPropCallGetter ICStub* getStub(ICStubSpace* space) { ReceiverGuard guard(receiver_); Shape* holderShape = holder_->as().lastProperty(); - return ICStub::New(space, getStubCode(), firstMonitorStub_, + return newStub(space, getStubCode(), firstMonitorStub_, guard, holder_, holderShape, getter_, pcOffset_); } @@ -4679,7 +4689,7 @@ class ICGetProp_CallNative : public ICGetPropCallGetter {} public: - static ICGetProp_CallNative* Clone(ICStubSpace* space, ICStub* firstMonitorStub, + static ICGetProp_CallNative* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_CallNative& other); class Compiler : public ICGetPropCallGetter::Compiler @@ -4707,7 +4717,7 @@ class ICGetProp_CallNative : public ICGetPropCallGetter ICStub* getStub(ICStubSpace* space) { ReceiverGuard guard(receiver_); Shape* holderShape = holder_->as().lastProperty(); - return ICStub::New(space, getStubCode(), firstMonitorStub_, + return newStub(space, getStubCode(), firstMonitorStub_, guard, holder_, holderShape, getter_, pcOffset_); } @@ -4749,7 +4759,8 @@ class ICGetProp_CallDOMProxyNative : public ICGetPropCallDOMProxyNativeStub {} public: - static ICGetProp_CallDOMProxyNative* Clone(ICStubSpace* space, + static ICGetProp_CallDOMProxyNative* Clone(JSContext* cx, + ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_CallDOMProxyNative& other); }; @@ -4776,7 +4787,7 @@ class ICGetProp_CallDOMProxyWithGenerationNative : public ICGetPropCallDOMProxyN } static ICGetProp_CallDOMProxyWithGenerationNative* - Clone(ICStubSpace* space, ICStub* firstMonitorStub, + Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_CallDOMProxyWithGenerationNative& other); void* expandoAndGeneration() const { @@ -4832,7 +4843,7 @@ class ICGetProp_DOMProxyShadowed : public ICMonitoredStub uint32_t pcOffset); public: - static ICGetProp_DOMProxyShadowed* Clone(ICStubSpace* space, + static ICGetProp_DOMProxyShadowed* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetProp_DOMProxyShadowed& other); @@ -4907,7 +4918,7 @@ class ICGetProp_ArgumentsLength : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -4932,7 +4943,7 @@ class ICGetProp_ArgumentsCallee : public ICMonitoredStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_); + return newStub(space, getStubCode(), firstMonitorStub_); } }; }; @@ -4974,7 +4985,7 @@ class ICSetProp_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -5157,8 +5168,8 @@ class ICSetPropNativeAddCompiler : public ICStubCompiler else newShape = obj_->as().maybeExpando()->lastProperty(); - return ICStub::New>( - space, getStubCode(), oldGroup_, shapes, newShape, newGroup, offset_); + return newStub>( + space, getStubCode(), oldGroup_, shapes, newShape, newGroup, offset_); } ICUpdatedStub* getStub(ICStubSpace* space); @@ -5214,8 +5225,8 @@ class ICSetProp_Unboxed : public ICUpdatedStub {} ICUpdatedStub* getStub(ICStubSpace* space) { - ICUpdatedStub* stub = ICStub::New(space, getStubCode(), - group_, fieldOffset_); + ICUpdatedStub* stub = newStub(space, getStubCode(), group_, + fieldOffset_); if (!stub || !stub->initUpdatingChain(cx, space)) return nullptr; return stub; @@ -5299,9 +5310,9 @@ class ICSetProp_TypedObject : public ICUpdatedStub bool isObjectReference = fieldDescr_->is() && fieldDescr_->as().type() == ReferenceTypeDescr::TYPE_OBJECT; - ICUpdatedStub* stub = ICStub::New(space, getStubCode(), shape_, - group_, fieldOffset_, - isObjectReference); + ICUpdatedStub* stub = newStub(space, getStubCode(), shape_, + group_, fieldOffset_, + isObjectReference); if (!stub || !stub->initUpdatingChain(cx, space)) return nullptr; return stub; @@ -5320,10 +5331,13 @@ class ICSetPropCallSetter : public ICStub friend class ICStubSpace; protected: - // Object shape/group. - HeapReceiverGuard guard_; + // Shape/group of receiver object. Used for both own and proto setters. + HeapReceiverGuard receiverGuard_; - // Holder and shape. + // Holder and holder shape. For own setters, guarding on receiverGuard_ is + // sufficient, although Ion may use holder_ and holderShape_ even for own + // setters. In this case holderShape_ == receiverGuard_.shape_ (isOwnSetter + // below relies on this). HeapPtrObject holder_; HeapPtrShape holderShape_; @@ -5333,13 +5347,13 @@ class ICSetPropCallSetter : public ICStub // PC of call, for profiler uint32_t pcOffset_; - ICSetPropCallSetter(Kind kind, JitCode* stubCode, ReceiverGuard guard, + ICSetPropCallSetter(Kind kind, JitCode* stubCode, ReceiverGuard receiverGuard, JSObject* holder, Shape* holderShape, JSFunction* setter, uint32_t pcOffset); public: - HeapReceiverGuard& guard() { - return guard_; + HeapReceiverGuard& receiverGuard() { + return receiverGuard_; } HeapPtrObject& holder() { return holder_; @@ -5351,8 +5365,14 @@ class ICSetPropCallSetter : public ICStub return setter_; } - static size_t offsetOfGuard() { - return offsetof(ICSetPropCallSetter, guard_); + bool isOwnSetter() const { + MOZ_ASSERT(holder_->isNative()); + MOZ_ASSERT(holderShape_); + return receiverGuard_.shape() == holderShape_; + } + + static size_t offsetOfReceiverGuard() { + return offsetof(ICSetPropCallSetter, receiverGuard_); } static size_t offsetOfHolder() { return offsetof(ICSetPropCallSetter, holder_); @@ -5369,21 +5389,22 @@ class ICSetPropCallSetter : public ICStub class Compiler : public ICStubCompiler { protected: - RootedObject obj_; + RootedObject receiver_; RootedObject holder_; RootedFunction setter_; uint32_t pcOffset_; virtual int32_t getKey() const { return static_cast(kind) | - (HeapReceiverGuard::keyBits(obj_) << 16); + (HeapReceiverGuard::keyBits(receiver_) << 16) | + (static_cast(receiver_ != holder_) << 19); } public: - Compiler(JSContext* cx, ICStub::Kind kind, HandleObject obj, HandleObject holder, + Compiler(JSContext* cx, ICStub::Kind kind, HandleObject receiver, HandleObject holder, HandleFunction setter, uint32_t pcOffset) : ICStubCompiler(cx, kind), - obj_(cx, obj), + receiver_(cx, receiver), holder_(cx, holder), setter_(cx, setter), pcOffset_(pcOffset) @@ -5406,7 +5427,7 @@ class ICSetProp_CallScripted : public ICSetPropCallSetter {} public: - static ICSetProp_CallScripted* Clone(ICStubSpace* space, ICStub* , + static ICSetProp_CallScripted* Clone(JSContext* cx, ICStubSpace* space, ICStub*, ICSetProp_CallScripted& other); class Compiler : public ICSetPropCallSetter::Compiler { @@ -5421,9 +5442,9 @@ class ICSetProp_CallScripted : public ICSetPropCallSetter {} ICStub* getStub(ICStubSpace* space) { - ReceiverGuard guard(obj_); + ReceiverGuard guard(receiver_); Shape* holderShape = holder_->as().lastProperty(); - return ICStub::New(space, getStubCode(), guard, holder_, + return newStub(space, getStubCode(), guard, holder_, holderShape, setter_, pcOffset_); } }; @@ -5442,7 +5463,8 @@ class ICSetProp_CallNative : public ICSetPropCallSetter {} public: - static ICSetProp_CallNative* Clone(ICStubSpace* space, ICStub* , + static ICSetProp_CallNative* Clone(JSContext* cx, + ICStubSpace* space, ICStub*, ICSetProp_CallNative& other); class Compiler : public ICSetPropCallSetter::Compiler { @@ -5457,10 +5479,10 @@ class ICSetProp_CallNative : public ICSetPropCallSetter {} ICStub* getStub(ICStubSpace* space) { - ReceiverGuard guard(obj_); + ReceiverGuard guard(receiver_); Shape* holderShape = holder_->as().lastProperty(); - return ICStub::New(space, getStubCode(), guard, holder_, - holderShape, setter_, pcOffset_); + return newStub(space, getStubCode(), guard, holder_, holderShape, + setter_, pcOffset_); } }; }; @@ -5503,27 +5525,18 @@ class ICCall_Fallback : public ICMonitoredFallbackStub { friend class ICStubSpace; public: - static const unsigned CONSTRUCTING_FLAG = 0x1; - static const unsigned UNOPTIMIZABLE_CALL_FLAG = 0x2; + static const unsigned UNOPTIMIZABLE_CALL_FLAG = 0x1; static const uint32_t MAX_OPTIMIZED_STUBS = 16; static const uint32_t MAX_SCRIPTED_STUBS = 7; static const uint32_t MAX_NATIVE_STUBS = 7; private: - ICCall_Fallback(JitCode* stubCode, bool isConstructing) + explicit ICCall_Fallback(JitCode* stubCode) : ICMonitoredFallbackStub(ICStub::Call_Fallback, stubCode) - { - extra_ = 0; - if (isConstructing) - extra_ |= CONSTRUCTING_FLAG; - } + { } public: - bool isConstructing() const { - return extra_ & CONSTRUCTING_FLAG; - } - void noteUnoptimizableCall() { extra_ |= UNOPTIMIZABLE_CALL_FLAG; } @@ -5548,6 +5561,9 @@ class ICCall_Fallback : public ICMonitoredFallbackStub // Compiler for this stub kind. class Compiler : public ICCallStubCompiler { + public: + static const int32_t CALL_KEY = static_cast(ICStub::Call_Fallback); + static const int32_t CONSTRUCT_KEY = static_cast(ICStub::Call_Fallback) | (1 << 17); protected: bool isConstructing_; bool isSpread_; @@ -5556,7 +5572,8 @@ class ICCall_Fallback : public ICMonitoredFallbackStub bool postGenerateStubCode(MacroAssembler& masm, Handle code); virtual int32_t getKey() const { - return static_cast(kind) | (static_cast(isSpread_) << 16); + return static_cast(kind) | (static_cast(isSpread_) << 16) | + (static_cast(isConstructing_) << 17); } public: @@ -5567,7 +5584,7 @@ class ICCall_Fallback : public ICMonitoredFallbackStub { } ICStub* getStub(ICStubSpace* space) { - ICCall_Fallback* stub = ICStub::New(space, getStubCode(), isConstructing_); + ICCall_Fallback* stub = newStub(space, getStubCode()); if (!stub || !stub->initMonitoringChain(cx, space)) return nullptr; return stub; @@ -5594,7 +5611,7 @@ class ICCall_Scripted : public ICMonitoredStub uint32_t pcOffset); public: - static ICCall_Scripted* Clone(ICStubSpace* space, ICStub* firstMonitorStub, + static ICCall_Scripted* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICCall_Scripted& other); HeapPtrFunction& callee() { @@ -5625,7 +5642,7 @@ class ICCall_AnyScripted : public ICMonitoredStub { } public: - static ICCall_AnyScripted* Clone(ICStubSpace* space, ICStub* firstMonitorStub, + static ICCall_AnyScripted* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICCall_AnyScripted& other); static size_t offsetOfPCOffset() { @@ -5675,11 +5692,10 @@ class ICCallScriptedCompiler : public ICCallStubCompiler { ICStub* getStub(ICStubSpace* space) { if (callee_) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - callee_, templateObject_, - pcOffset_); + return newStub(space, getStubCode(), firstMonitorStub_, callee_, + templateObject_, pcOffset_); } - return ICStub::New(space, getStubCode(), firstMonitorStub_, pcOffset_); + return newStub(space, getStubCode(), firstMonitorStub_, pcOffset_); } }; @@ -5701,7 +5717,7 @@ class ICCall_Native : public ICMonitoredStub uint32_t pcOffset); public: - static ICCall_Native* Clone(ICStubSpace* space, ICStub* firstMonitorStub, + static ICCall_Native* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICCall_Native& other); HeapPtrFunction& callee() { @@ -5754,8 +5770,8 @@ class ICCall_Native : public ICMonitoredStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - callee_, templateObject_, pcOffset_); + return newStub(space, getStubCode(), firstMonitorStub_, callee_, + templateObject_, pcOffset_); } }; }; @@ -5775,7 +5791,7 @@ class ICCall_ClassHook : public ICMonitoredStub uint32_t pcOffset); public: - static ICCall_ClassHook* Clone(ICStubSpace* space, ICStub* firstMonitorStub, + static ICCall_ClassHook* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICCall_ClassHook& other); const Class* clasp() { @@ -5828,8 +5844,8 @@ class ICCall_ClassHook : public ICMonitoredStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - clasp_, native_, templateObject_, pcOffset_); + return newStub(space, getStubCode(), firstMonitorStub_, clasp_, + native_, templateObject_, pcOffset_); } }; }; @@ -5852,7 +5868,8 @@ class ICCall_ScriptedApplyArray : public ICMonitoredStub {} public: - static ICCall_ScriptedApplyArray* Clone(ICStubSpace* space, + static ICCall_ScriptedApplyArray* Clone(JSContext* cx, + ICStubSpace* space, ICStub* firstMonitorStub, ICCall_ScriptedApplyArray& other); @@ -5879,8 +5896,8 @@ class ICCall_ScriptedApplyArray : public ICMonitoredStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - pcOffset_); + return newStub(space, getStubCode(), firstMonitorStub_, + pcOffset_); } }; }; @@ -5898,7 +5915,8 @@ class ICCall_ScriptedApplyArguments : public ICMonitoredStub {} public: - static ICCall_ScriptedApplyArguments* Clone(ICStubSpace* space, + static ICCall_ScriptedApplyArguments* Clone(JSContext* cx, + ICStubSpace* space, ICStub* firstMonitorStub, ICCall_ScriptedApplyArguments& other); @@ -5925,8 +5943,8 @@ class ICCall_ScriptedApplyArguments : public ICMonitoredStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - pcOffset_); + return newStub(space, getStubCode(), firstMonitorStub_, + pcOffset_); } }; }; @@ -5945,8 +5963,8 @@ class ICCall_ScriptedFunCall : public ICMonitoredStub {} public: - static ICCall_ScriptedFunCall* Clone(ICStubSpace* space, ICStub* firstMonitorStub, - ICCall_ScriptedFunCall& other); + static ICCall_ScriptedFunCall* Clone(JSContext* cx, ICStubSpace* space, + ICStub* firstMonitorStub, ICCall_ScriptedFunCall& other); static size_t offsetOfPCOffset() { return offsetof(ICCall_ScriptedFunCall, pcOffset_); @@ -5971,8 +5989,8 @@ class ICCall_ScriptedFunCall : public ICMonitoredStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - pcOffset_); + return newStub(space, getStubCode(), firstMonitorStub_, + pcOffset_); } }; }; @@ -6045,9 +6063,8 @@ class ICCall_StringSplit : public ICMonitoredStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), firstMonitorStub_, - pcOffset_, expectedThis_, expectedArg_, - templateObject_); + return newStub(space, getStubCode(), firstMonitorStub_, pcOffset_, + expectedThis_, expectedArg_, templateObject_); } }; }; @@ -6071,7 +6088,7 @@ class ICCall_IsSuspendedStarGenerator : public ICStub : ICStubCompiler(cx, ICStub::Call_IsSuspendedStarGenerator) {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -6131,7 +6148,7 @@ class ICIteratorNew_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -6164,7 +6181,7 @@ class ICIteratorMore_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -6189,7 +6206,7 @@ class ICIteratorMore_Native : public ICStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -6214,7 +6231,7 @@ class ICIteratorClose_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -6251,7 +6268,7 @@ class ICInstanceOf_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -6303,7 +6320,8 @@ class ICInstanceOf_Function : public ICStub {} ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), shape_, prototypeObj_, slot_); + return newStub(space, getStubCode(), shape_, prototypeObj_, + slot_); } }; }; @@ -6330,7 +6348,7 @@ class ICTypeOf_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -6369,7 +6387,7 @@ class ICTypeOf_Typed : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), type_); + return newStub(space, getStubCode(), type_); } }; }; @@ -6403,7 +6421,7 @@ class ICRest_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), templateObject); + return newStub(space, getStubCode(), templateObject); } }; }; @@ -6430,7 +6448,7 @@ class ICRetSub_Fallback : public ICFallbackStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode()); + return newStub(space, getStubCode()); } }; }; @@ -6473,7 +6491,7 @@ class ICRetSub_Resume : public ICStub { } ICStub* getStub(ICStubSpace* space) { - return ICStub::New(space, getStubCode(), pcOffset_, addr_); + return newStub(space, getStubCode(), pcOffset_, addr_); } }; }; diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp index 39db33d04b..ecca85685e 100644 --- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -672,15 +672,16 @@ BaselineInspector::commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shap for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) { if (stub->isSetProp_CallScripted() || stub->isSetProp_CallNative()) { ICSetPropCallSetter* nstub = static_cast(stub); - if (!AddReceiver(nstub->guard(), receivers, convertUnboxedGroups)) + bool isOwn = nstub->isOwnSetter(); + if (!isOwn && !AddReceiver(nstub->receiverGuard(), receivers, convertUnboxedGroups)) return false; if (!*holder) { *holder = nstub->holder(); *holderShape = nstub->holderShape(); *commonSetter = nstub->setter(); - *isOwnProperty = false; - } else if (nstub->holderShape() != *holderShape) { + *isOwnProperty = isOwn; + } else if (nstub->holderShape() != *holderShape || isOwn != *isOwnProperty) { return false; } else { MOZ_ASSERT(*commonSetter == nstub->setter()); diff --git a/js/src/jit/C1Spewer.cpp b/js/src/jit/C1Spewer.cpp index 8a6d9807c6..dfe9a893ae 100644 --- a/js/src/jit/C1Spewer.cpp +++ b/js/src/jit/C1Spewer.cpp @@ -16,67 +16,67 @@ #include "jit/LIR.h" #include "jit/MIRGraph.h" +#include "vm/Printer.h" + using namespace js; using namespace js::jit; bool C1Spewer::init(const char* path) { - spewout_ = fopen(path, "w"); - return spewout_ != nullptr; + return out_.init(path); } void C1Spewer::beginFunction(MIRGraph* graph, HandleScript script) { - if (!spewout_) + if (!out_.isInitialized()) return; this->graph = graph; - fprintf(spewout_, "begin_compilation\n"); + out_.printf("begin_compilation\n"); if (script) { - fprintf(spewout_, " name \"%s:%" PRIuSIZE "\"\n", script->filename(), script->lineno()); - fprintf(spewout_, " method \"%s:%" PRIuSIZE "\"\n", script->filename(), script->lineno()); + out_.printf(" name \"%s:%" PRIuSIZE "\"\n", script->filename(), script->lineno()); + out_.printf(" method \"%s:%" PRIuSIZE "\"\n", script->filename(), script->lineno()); } else { - fprintf(spewout_, " name \"asm.js compilation\"\n"); - fprintf(spewout_, " method \"asm.js compilation\"\n"); + out_.printf(" name \"asm.js compilation\"\n"); + out_.printf(" method \"asm.js compilation\"\n"); } - fprintf(spewout_, " date %d\n", (int)time(nullptr)); - fprintf(spewout_, "end_compilation\n"); + out_.printf(" date %d\n", (int)time(nullptr)); + out_.printf("end_compilation\n"); } void C1Spewer::spewPass(const char* pass) { - if (!spewout_) + if (!out_.isInitialized()) return; - fprintf(spewout_, "begin_cfg\n"); - fprintf(spewout_, " name \"%s\"\n", pass); + out_.printf("begin_cfg\n"); + out_.printf(" name \"%s\"\n", pass); for (MBasicBlockIterator block(graph->begin()); block != graph->end(); block++) - spewPass(spewout_, *block); + spewPass(out_, *block); - fprintf(spewout_, "end_cfg\n"); - fflush(spewout_); + out_.printf("end_cfg\n"); + out_.flush(); } void -C1Spewer::spewIntervals(const char* pass, BacktrackingAllocator* regalloc) +C1Spewer::spewRanges(const char* pass, BacktrackingAllocator* regalloc) { - if (!spewout_) + if (!out_.isInitialized()) return; - fprintf(spewout_, "begin_intervals\n"); - fprintf(spewout_, " name \"%s\"\n", pass); + out_.printf("begin_ranges\n"); + out_.printf(" name \"%s\"\n", pass); - size_t nextId = 0x4000; for (MBasicBlockIterator block(graph->begin()); block != graph->end(); block++) - spewIntervals(spewout_, *block, regalloc, nextId); + spewRanges(out_, *block, regalloc); - fprintf(spewout_, "end_intervals\n"); - fflush(spewout_); + out_.printf("end_ranges\n"); + out_.flush(); } void @@ -87,136 +87,131 @@ C1Spewer::endFunction() void C1Spewer::finish() { - if (spewout_) - fclose(spewout_); + if (out_.isInitialized()) + out_.finish(); } static void -DumpDefinition(FILE* fp, MDefinition* def) +DumpDefinition(GenericPrinter& out, MDefinition* def) { - fprintf(fp, " "); - fprintf(fp, "%u %u ", def->id(), unsigned(def->useCount())); - def->printName(fp); - fprintf(fp, " "); - def->printOpcode(fp); - fprintf(fp, " <|@\n"); + out.printf(" "); + out.printf("%u %u ", def->id(), unsigned(def->useCount())); + def->printName(out); + out.printf(" "); + def->printOpcode(out); + out.printf(" <|@\n"); } static void -DumpLIR(FILE* fp, LNode* ins) +DumpLIR(GenericPrinter& out, LNode* ins) { - fprintf(fp, " "); - fprintf(fp, "%d ", ins->id()); - ins->dump(fp); - fprintf(fp, " <|@\n"); + out.printf(" "); + out.printf("%d ", ins->id()); + ins->dump(out); + out.printf(" <|@\n"); } void -C1Spewer::spewIntervals(FILE* fp, BacktrackingAllocator* regalloc, LNode* ins, size_t& nextId) +C1Spewer::spewRanges(GenericPrinter& out, BacktrackingAllocator* regalloc, LNode* ins) { for (size_t k = 0; k < ins->numDefs(); k++) { uint32_t id = ins->getDef(k)->virtualRegister(); VirtualRegister* vreg = ®alloc->vregs[id]; - for (size_t i = 0; i < vreg->numIntervals(); i++) { - LiveInterval* live = vreg->getInterval(i); - if (live->numRanges()) { - fprintf(fp, "%d object \"", (i == 0) ? id : int32_t(nextId++)); - fprintf(fp, "%s", live->getAllocation()->toString()); - fprintf(fp, "\" %d -1", id); - for (size_t j = 0; j < live->numRanges(); j++) { - fprintf(fp, " [%u, %u[", live->getRange(j)->from.bits(), - live->getRange(j)->to.bits()); - } - for (UsePositionIterator usePos(live->usesBegin()); usePos != live->usesEnd(); usePos++) - fprintf(fp, " %u M", usePos->pos.bits()); - fprintf(fp, " \"\"\n"); - } + for (LiveRange::RegisterLinkIterator iter = vreg->rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + out.printf("%d object \"", id); + out.printf("%s", range->bundle()->allocation().toString()); + out.printf("\" %d -1", id); + out.printf(" [%u, %u[", range->from().bits(), range->to().bits()); + for (UsePositionIterator usePos(range->usesBegin()); usePos; usePos++) + out.printf(" %u M", usePos->pos.bits()); + out.printf(" \"\"\n"); } } } void -C1Spewer::spewIntervals(FILE* fp, MBasicBlock* block, BacktrackingAllocator* regalloc, size_t& nextId) +C1Spewer::spewRanges(GenericPrinter& out, MBasicBlock* block, BacktrackingAllocator* regalloc) { LBlock* lir = block->lir(); if (!lir) return; for (size_t i = 0; i < lir->numPhis(); i++) - spewIntervals(fp, regalloc, lir->getPhi(i), nextId); + spewRanges(out, regalloc, lir->getPhi(i)); for (LInstructionIterator ins = lir->begin(); ins != lir->end(); ins++) - spewIntervals(fp, regalloc, *ins, nextId); + spewRanges(out, regalloc, *ins); } void -C1Spewer::spewPass(FILE* fp, MBasicBlock* block) +C1Spewer::spewPass(GenericPrinter& out, MBasicBlock* block) { - fprintf(fp, " begin_block\n"); - fprintf(fp, " name \"B%d\"\n", block->id()); - fprintf(fp, " from_bci -1\n"); - fprintf(fp, " to_bci -1\n"); + out.printf(" begin_block\n"); + out.printf(" name \"B%d\"\n", block->id()); + out.printf(" from_bci -1\n"); + out.printf(" to_bci -1\n"); - fprintf(fp, " predecessors"); + out.printf(" predecessors"); for (uint32_t i = 0; i < block->numPredecessors(); i++) { MBasicBlock* pred = block->getPredecessor(i); - fprintf(fp, " \"B%d\"", pred->id()); + out.printf(" \"B%d\"", pred->id()); } - fprintf(fp, "\n"); + out.printf("\n"); - fprintf(fp, " successors"); + out.printf(" successors"); for (uint32_t i = 0; i < block->numSuccessors(); i++) { MBasicBlock* successor = block->getSuccessor(i); - fprintf(fp, " \"B%d\"", successor->id()); + out.printf(" \"B%d\"", successor->id()); } - fprintf(fp, "\n"); + out.printf("\n"); - fprintf(fp, " xhandlers\n"); - fprintf(fp, " flags\n"); + out.printf(" xhandlers\n"); + out.printf(" flags\n"); if (block->lir() && block->lir()->begin() != block->lir()->end()) { - fprintf(fp, " first_lir_id %d\n", block->lir()->firstId()); - fprintf(fp, " last_lir_id %d\n", block->lir()->lastId()); + out.printf(" first_lir_id %d\n", block->lir()->firstId()); + out.printf(" last_lir_id %d\n", block->lir()->lastId()); } - fprintf(fp, " begin_states\n"); + out.printf(" begin_states\n"); if (block->entryResumePoint()) { - fprintf(fp, " begin_locals\n"); - fprintf(fp, " size %d\n", (int)block->numEntrySlots()); - fprintf(fp, " method \"None\"\n"); + out.printf(" begin_locals\n"); + out.printf(" size %d\n", (int)block->numEntrySlots()); + out.printf(" method \"None\"\n"); for (uint32_t i = 0; i < block->numEntrySlots(); i++) { MDefinition* ins = block->getEntrySlot(i); - fprintf(fp, " "); - fprintf(fp, "%d ", i); + out.printf(" "); + out.printf("%d ", i); if (ins->isUnused()) - fprintf(fp, "unused"); + out.printf("unused"); else - ins->printName(fp); - fprintf(fp, "\n"); + ins->printName(out); + out.printf("\n"); } - fprintf(fp, " end_locals\n"); + out.printf(" end_locals\n"); } - fprintf(fp, " end_states\n"); + out.printf(" end_states\n"); - fprintf(fp, " begin_HIR\n"); + out.printf(" begin_HIR\n"); for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); phi++) - DumpDefinition(fp, *phi); + DumpDefinition(out, *phi); for (MInstructionIterator i(block->begin()); i != block->end(); i++) - DumpDefinition(fp, *i); - fprintf(fp, " end_HIR\n"); + DumpDefinition(out, *i); + out.printf(" end_HIR\n"); if (block->lir()) { - fprintf(fp, " begin_LIR\n"); + out.printf(" begin_LIR\n"); for (size_t i = 0; i < block->lir()->numPhis(); i++) - DumpLIR(fp, block->lir()->getPhi(i)); + DumpLIR(out, block->lir()->getPhi(i)); for (LInstructionIterator i(block->lir()->begin()); i != block->lir()->end(); i++) - DumpLIR(fp, *i); - fprintf(fp, " end_LIR\n"); + DumpLIR(out, *i); + out.printf(" end_LIR\n"); } - fprintf(fp, " end_block\n"); + out.printf(" end_block\n"); } #endif /* DEBUG */ diff --git a/js/src/jit/C1Spewer.h b/js/src/jit/C1Spewer.h index 9caa6f11b6..ded457e254 100644 --- a/js/src/jit/C1Spewer.h +++ b/js/src/jit/C1Spewer.h @@ -12,6 +12,7 @@ #include "NamespaceImports.h" #include "js/RootingAPI.h" +#include "vm/Printer.h" namespace js { namespace jit { @@ -24,24 +25,24 @@ class LNode; class C1Spewer { MIRGraph* graph; - FILE* spewout_; + Fprinter out_; public: C1Spewer() - : graph(nullptr), spewout_(nullptr) + : graph(nullptr), out_() { } bool init(const char* path); void beginFunction(MIRGraph* graph, HandleScript script); void spewPass(const char* pass); - void spewIntervals(const char* pass, BacktrackingAllocator* regalloc); + void spewRanges(const char* pass, BacktrackingAllocator* regalloc); void endFunction(); void finish(); private: - void spewPass(FILE* fp, MBasicBlock* block); - void spewIntervals(FILE* fp, BacktrackingAllocator* regalloc, LNode* ins, size_t& nextId); - void spewIntervals(FILE* fp, MBasicBlock* block, BacktrackingAllocator* regalloc, size_t& nextId); + void spewPass(GenericPrinter& out, MBasicBlock* block); + void spewRanges(GenericPrinter& out, BacktrackingAllocator* regalloc, LNode* ins); + void spewRanges(GenericPrinter& out, MBasicBlock* block, BacktrackingAllocator* regalloc); }; } // namespace jit diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index cd2922cf00..4429b5f9ef 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -39,6 +39,7 @@ #include "jsboolinlines.h" +#include "jit/MacroAssembler-inl.h" #include "jit/shared/CodeGenerator-shared-inl.h" #include "vm/Interpreter-inl.h" @@ -656,7 +657,7 @@ CodeGenerator::getJumpLabelForBranch(MBasicBlock* block) // important here as these tests are extremely unlikely to be used in loop // backedges, so emit inline code for the patchable jump. Heap allocating // the label allows it to be used by out of line blocks. - Label *res = alloc().lifoAlloc()->new_