2828import static org .junit .Assert .assertNotNull ;
2929import static org .junit .Assert .assertTrue ;
3030
31+ import java .io .IOException ;
32+ import java .util .Map ;
3133import java .util .concurrent .atomic .AtomicReference ;
3234import java .util .function .Consumer ;
3335import java .util .function .Supplier ;
3436
3537import org .graalvm .polyglot .Context ;
38+ import org .graalvm .polyglot .Engine ;
39+ import org .graalvm .polyglot .Source ;
3640import org .junit .After ;
3741import org .junit .Assert ;
3842import org .junit .Assume ;
4448import com .oracle .truffle .api .CompilerDirectives ;
4549import com .oracle .truffle .api .CompilerDirectives .CompilationFinal ;
4650import com .oracle .truffle .api .Truffle ;
51+ import com .oracle .truffle .api .TruffleLanguage ;
4752import com .oracle .truffle .api .frame .VirtualFrame ;
53+ import com .oracle .truffle .api .nodes .IndirectCallNode ;
4854import com .oracle .truffle .api .nodes .RootNode ;
55+ import com .oracle .truffle .api .test .common .NullObject ;
4956import com .oracle .truffle .compiler .TruffleCompilerListener ;
5057import com .oracle .truffle .runtime .AbstractCompilationTask ;
5158import com .oracle .truffle .runtime .OptimizedCallTarget ;
5259import com .oracle .truffle .runtime .OptimizedTruffleRuntime ;
5360import com .oracle .truffle .runtime .OptimizedTruffleRuntimeListener ;
5461
5562import jdk .graal .compiler .truffle .TruffleCompilerOptions ;
63+ import jdk .graal .compiler .util .CollectionsUtil ;
5664
5765public class DeoptLoopDetectionTest {
5866
67+ private static final Map <String , String > ENGINE_OPTIONS = CollectionsUtil .mapOf (
68+ "engine.CompilationFailureAction" , "Silent" , //
69+ "engine.BackgroundCompilation" , "false" , //
70+ "engine.CompileImmediately" , "true" );
5971 private final AtomicReference <CallTarget > callTargetFilter = new AtomicReference <>();
6072 private final AtomicReference <Boolean > compilationResult = new AtomicReference <>();
6173 private final AtomicReference <String > compilationFailedReason = new AtomicReference <>();
@@ -80,10 +92,7 @@ public void onCompilationFailed(OptimizedCallTarget target, String reason, boole
8092
8193 @ Before
8294 public void setup () {
83- context = Context .newBuilder ().//
84- option ("engine.CompilationFailureAction" , "Silent" ).//
85- option ("engine.BackgroundCompilation" , "false" ).//
86- option ("engine.CompileImmediately" , "true" ).build ();
95+ context = Context .newBuilder ().options (ENGINE_OPTIONS ).build ();
8796 context .enter ();
8897 Assume .assumeTrue (Truffle .getRuntime () instanceof OptimizedTruffleRuntime );
8998 ((OptimizedTruffleRuntime ) Truffle .getRuntime ()).addListener (listener );
@@ -104,7 +113,7 @@ public Object execute(VirtualFrame frame) {
104113 CompilerDirectives .transferToInterpreterAndInvalidate ();
105114 return null ;
106115 }
107- }, "alwaysDeopt" , CallTarget ::call , 1 );
116+ }, "alwaysDeopt" , CallTarget ::call , 0 , 1 );
108117 }
109118
110119 @ Test
@@ -145,19 +154,19 @@ public void accept(CallTarget callTarget) {
145154 ((OptimizedCallTarget ) callTarget ).invalidate ("Force one more recompile" );
146155 }
147156 }
148- }, 1 ));
157+ }, 0 , 1 ));
149158 Assert .assertEquals ("No deopt loop detected after " + MAX_EXECUTIONS + " executions" , expectedError .getMessage ());
150159 }
151160
152161 @ Test
153162 public void testLocalDeopt () {
154163 assertDeoptLoop (new BaseRootNode () {
155164
156- @ CompilationFinal boolean cachedValue ;
165+ @ CompilationFinal int cachedValue ;
157166
158167 @ Override
159168 public Object execute (VirtualFrame frame ) {
160- boolean arg = (boolean ) frame .getArguments ()[0 ];
169+ int arg = (int ) frame .getArguments ()[0 ];
161170 if (this .cachedValue != arg ) {
162171 CompilerDirectives .transferToInterpreterAndInvalidate ();
163172 this .cachedValue = arg ;
@@ -166,9 +175,9 @@ public Object execute(VirtualFrame frame) {
166175 }
167176
168177 }, "localDeopt" , (target ) -> {
169- target .call (true );
170- target .call (false );
171- }, 2 );
178+ target .call (0 );
179+ target .call (1 );
180+ }, 0 , 2 );
172181 }
173182
174183 @ Test
@@ -190,7 +199,7 @@ public Object execute(VirtualFrame frame) {
190199
191200 }, "globalDeopt" , (target ) -> {
192201 target .call (true );
193- }, 1 );
202+ }, 0 , 1 );
194203 }
195204
196205 static class StaticAssumptionRootNode extends BaseRootNode {
@@ -215,7 +224,7 @@ public void testStaticAssumptionNoDeopt() {
215224 StaticAssumptionRootNode .assumption = Assumption .create ();
216225 firstExecution [0 ] = false ;
217226 }
218- }, 1 ));
227+ }, 0 , 1 ));
219228 Assert .assertEquals ("No deopt loop detected after " + MAX_EXECUTIONS + " executions" , expectedError .getMessage ());
220229 }
221230
@@ -243,7 +252,7 @@ public Object execute(VirtualFrame frame) {
243252
244253 }, "localLoopDeoptwithChangedCode" , (target ) -> {
245254 target .call (1 );
246- }, 1 );
255+ }, 0 , 1 );
247256 }
248257
249258 @ Test
@@ -289,12 +298,166 @@ public Object execute(VirtualFrame frame) {
289298 public void accept (CallTarget target ) {
290299 target .call (input ++);
291300 }
292- }, 1 );
301+ }, 0 , 1 );
302+ }
303+
304+ @ TruffleLanguage .Registration (id = NonConstantLanguageContextTestLanguage .ID , contextPolicy = TruffleLanguage .ContextPolicy .SHARED )
305+ static class NonConstantLanguageContextTestLanguage extends TruffleLanguage <NonConstantLanguageContextTestLanguage .LanguageContext > {
306+ static final String ID = "NonConstantLanguageContextTestLanguage" ;
307+ static final String ROOT_NODE_NAME = "languageContextFallacy" ;
308+
309+ static class LanguageContext {
310+ private final Env env ;
311+
312+ @ CompilationFinal boolean initialized ;
313+
314+ LanguageContext (Env env ) {
315+ this .env = env ;
316+ }
317+
318+ void ensureInitialized () {
319+ if (!initialized ) {
320+ CompilerDirectives .transferToInterpreterAndInvalidate ();
321+ initialize ();
322+ }
323+ }
324+
325+ private void initialize () {
326+ // perform initialization
327+ initialized = true ;
328+ }
329+
330+ static final ContextReference <LanguageContext > REF = ContextReference .create (NonConstantLanguageContextTestLanguage .class );
331+ }
332+
333+ @ Override
334+ protected LanguageContext createContext (Env env ) {
335+ return new LanguageContext (env );
336+ }
337+
338+ @ Override
339+ protected CallTarget parse (ParsingRequest request ) throws Exception {
340+ BaseRootNode rootNode = new BaseRootNode () {
341+ @ Override
342+ public Object execute (VirtualFrame frame ) {
343+ LanguageContext languageContext = LanguageContext .REF .get (this );
344+ languageContext .ensureInitialized ();
345+ // ...
346+ return NullObject .SINGLETON ;
347+ }
348+ };
349+ rootNode .name = ROOT_NODE_NAME ;
350+ return rootNode .getCallTarget ();
351+ }
352+ }
353+
354+ @ Test
355+ public void testNonConstantLanguageContext () throws IOException {
356+ try (Engine engine = Engine .newBuilder ().options (ENGINE_OPTIONS ).build ()) {
357+ Source source = Source .newBuilder (NonConstantLanguageContextTestLanguage .ID , "" , "TestSource" ).build ();
358+ AtomicReference <OptimizedCallTarget > captureTargetReference = new AtomicReference <>();
359+ final OptimizedTruffleRuntimeListener listener2 = new OptimizedTruffleRuntimeListener () {
360+ @ Override
361+ public void onCompilationSuccess (OptimizedCallTarget target , AbstractCompilationTask task , TruffleCompilerListener .GraphInfo graph ,
362+ TruffleCompilerListener .CompilationResultInfo result ) {
363+ OptimizedTruffleRuntimeListener .super .onCompilationSuccess (target , task , graph , result );
364+ captureTargetReference .set (target );
365+ }
366+ };
367+ ((OptimizedTruffleRuntime ) Truffle .getRuntime ()).addListener (listener2 );
368+ try {
369+ try (Context context1 = Context .newBuilder ().engine (engine ).build ()) {
370+ context1 .eval (source );
371+ }
372+ } finally {
373+ ((OptimizedTruffleRuntime ) Truffle .getRuntime ()).removeListener (listener2 );
374+ }
375+ assertDeoptLoop ((BaseRootNode ) captureTargetReference .get ().getRootNode (), NonConstantLanguageContextTestLanguage .ROOT_NODE_NAME , new Consumer <>() {
376+
377+ int calleeIndex = 0 ;
378+
379+ @ Override
380+ public void accept (CallTarget target ) {
381+ try (Context context2 = Context .newBuilder ().engine (engine ).build ()) {
382+ context2 .eval (source );
383+ }
384+ }
385+ }, 1 , 1 );
386+ }
387+ }
388+
389+ static class MyFunction {
390+ private final RootNode root ;
391+
392+ MyFunction (int id ) {
393+ this .root = new RootNode (null ) {
394+ @ Override
395+ public Object execute (VirtualFrame frame ) {
396+ return NullObject .SINGLETON ;
397+ }
398+
399+ @ Override
400+ public String getName () {
401+ return "callee" + id ;
402+ }
403+
404+ @ Override
405+ public String toString () {
406+ return getName ();
407+ }
408+ };
409+ }
410+ }
411+
412+ @ Test
413+ public void testLookupAndDispatch () {
414+ assertDeoptLoop (new BaseRootNode () {
415+
416+ @ Child IndirectCallNode callNode = IndirectCallNode .create ();
417+
418+ @ Override
419+ public Object execute (VirtualFrame frame ) {
420+ MyFunction function = (MyFunction ) frame .getArguments ()[0 ];
421+ CallTarget target = function .root .getCallTarget ();
422+ return callNode .call (target );
423+ }
424+
425+ }, "tooBroadDispatch" , new Consumer <>() {
426+
427+ int calleeIndex = 0 ;
428+
429+ @ Override
430+ public void accept (CallTarget target ) {
431+ target .call (new MyFunction (calleeIndex ++));
432+ }
433+ }, 0 , 1 );
434+ }
435+
436+ @ Test
437+ public void testSkippedException () {
438+ assertDeoptLoop (new BaseRootNode () {
439+
440+ @ Override
441+ public Object execute (VirtualFrame frame ) {
442+ try {
443+ throw new IndexOutOfBoundsException ();
444+ } catch (RuntimeException e ) {
445+ //
446+ }
447+ return null ;
448+ }
449+
450+ }, "skippedException" , new Consumer <>() {
451+ @ Override
452+ public void accept (CallTarget target ) {
453+ target .call ();
454+ }
455+ }, 0 , 1 );
293456 }
294457
295458 private static final int MAX_EXECUTIONS = 1024 ;
296459
297- private void assertDeoptLoop (BaseRootNode root , String name , Consumer <CallTarget > callStrategy , int compilationsPerIteration ) {
460+ private void assertDeoptLoop (BaseRootNode root , String name , Consumer <CallTarget > callStrategy , int previousCompilations , int compilationsPerIteration ) {
298461 root .name = name ;
299462 CallTarget callTarget = root .getCallTarget ();
300463 callTargetFilter .set (callTarget );
@@ -313,7 +476,7 @@ private void assertDeoptLoop(BaseRootNode root, String name, Consumer<CallTarget
313476 iterationCounter ++;
314477 }
315478
316- assertTrue (iterationCounter * compilationsPerIteration > TruffleCompilerOptions .DeoptCycleDetectionThreshold .getDefaultValue ());
479+ assertTrue (previousCompilations + iterationCounter * compilationsPerIteration > TruffleCompilerOptions .DeoptCycleDetectionThreshold .getDefaultValue ());
317480 assertEquals (Boolean .FALSE , compilationResult .get ());
318481 String failedReason = compilationFailedReason .get ();
319482 assertNotNull (failedReason );
0 commit comments