@@ -107,6 +107,48 @@ public Object execute(VirtualFrame frame) {
107107 }, "alwaysDeopt" , CallTarget ::call , 1 );
108108 }
109109
110+ @ Test
111+ public void testAlwaysDeoptNoInvalidate () {
112+ AssertionError expectedError = Assert .assertThrows (AssertionError .class , () -> assertDeoptLoop (new BaseRootNode () {
113+ @ CompilerDirectives .TruffleBoundary
114+ static void boundaryMethod () {
115+
116+ }
117+
118+ @ Override
119+ public Object execute (VirtualFrame frame ) {
120+ int arg = (int ) frame .getArguments ()[0 ];
121+ int threshold = TruffleCompilerOptions .DeoptCycleDetectionThreshold .getDefaultValue ();
122+ if (arg < threshold ) {
123+ CompilerDirectives .transferToInterpreterAndInvalidate ();
124+ }
125+ // call boundary method to prevent compiler from moving the following deoptimization
126+ // up
127+ boundaryMethod ();
128+ CompilerDirectives .transferToInterpreter ();
129+ return null ;
130+ }
131+ }, "alwaysDeoptNoInvalidate" , new Consumer <CallTarget >() {
132+ int i ;
133+
134+ @ Override
135+ public void accept (CallTarget callTarget ) {
136+ callTarget .call (i ++);
137+ if (i == TruffleCompilerOptions .DeoptCycleDetectionThreshold .getDefaultValue () + 1 ) {
138+ /*
139+ * Invalidate the target that was just deoptimized (but not invalidated) by
140+ * transferToInterpreter. The exact same compilation is then repeated in the
141+ * next iteration, but because deoptimize nodes with deoptimization action
142+ * "None" (like the one used for transferToInterpreter) don't trigger deopt loop
143+ * detection, no deopt loop should be detected.
144+ */
145+ ((OptimizedCallTarget ) callTarget ).invalidate ("Force one more recompile" );
146+ }
147+ }
148+ }, 1 ));
149+ Assert .assertEquals ("No deopt loop detected after " + MAX_EXECUTIONS + " executions" , expectedError .getMessage ());
150+ }
151+
110152 @ Test
111153 public void testLocalDeopt () {
112154 assertDeoptLoop (new BaseRootNode () {
0 commit comments