1 /*
2  * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 
25 package org.graalvm.compiler.core.test.inlining;
26 
27 import org.graalvm.compiler.core.common.GraalOptions;
28 import org.graalvm.compiler.core.test.GraalCompilerTest;
29 import org.graalvm.compiler.debug.DebugContext;
30 import org.graalvm.compiler.debug.DebugDumpScope;
31 import org.graalvm.compiler.debug.TTY;
32 import org.graalvm.compiler.graph.Node;
33 import org.graalvm.compiler.nodes.FullInfopointNode;
34 import org.graalvm.compiler.nodes.Invoke;
35 import org.graalvm.compiler.nodes.StructuredGraph;
36 import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
37 import org.graalvm.compiler.nodes.StructuredGraph.Builder;
38 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
39 import org.graalvm.compiler.options.OptionValues;
40 import org.graalvm.compiler.phases.OptimisticOptimizations;
41 import org.graalvm.compiler.phases.PhaseSuite;
42 import org.graalvm.compiler.phases.common.CanonicalizerPhase;
43 import org.graalvm.compiler.phases.common.DeadCodeEliminationPhase;
44 import org.graalvm.compiler.phases.common.inlining.InliningPhase;
45 import org.graalvm.compiler.phases.tiers.HighTierContext;
46 import org.graalvm.compiler.serviceprovider.GraalServices;
47 import org.junit.Assert;
48 import org.junit.Ignore;
49 import org.junit.Test;
50 
51 import jdk.vm.ci.code.site.InfopointReason;
52 import jdk.vm.ci.meta.ResolvedJavaMethod;
53 
54 import java.util.regex.Pattern;
55 
56 public class InliningTest extends GraalCompilerTest {
57 
58     @Test
testInvokeStaticInlining()59     public void testInvokeStaticInlining() {
60         assertInlined(getGraph("invokeStaticSnippet", false));
61         assertInlined(getGraph("invokeStaticOnInstanceSnippet", false));
62     }
63 
64     @SuppressWarnings("all")
invokeStaticSnippet(boolean value)65     public static Boolean invokeStaticSnippet(boolean value) {
66         return Boolean.valueOf(value);
67     }
68 
69     @SuppressWarnings({"all", "static"})
invokeStaticOnInstanceSnippet(Boolean obj, boolean value)70     public static Boolean invokeStaticOnInstanceSnippet(Boolean obj, boolean value) {
71         return obj.valueOf(value);
72     }
73 
74     @Test
testStaticBindableInlining()75     public void testStaticBindableInlining() {
76         assertInlined(getGraph("invokeConstructorSnippet", false));
77         assertInlined(getGraph("invokeFinalMethodSnippet", false));
78         assertInlined(getGraph("invokeMethodOnFinalClassSnippet", false));
79         assertInlined(getGraph("invokeMethodOnStaticFinalFieldSnippet", false));
80     }
81 
82     @Ignore("would need read elimination/EA before inlining")
83     @Test
testDependentStaticBindableInlining()84     public void testDependentStaticBindableInlining() {
85         assertInlined(getGraph("invokeMethodOnFinalFieldSnippet", false));
86         assertInlined(getGraph("invokeMethodOnFieldSnippet", false));
87     }
88 
89     @Test
testStaticBindableInliningIP()90     public void testStaticBindableInliningIP() {
91         assertManyMethodInfopoints(assertInlined(getGraph("invokeConstructorSnippet", true)));
92         assertManyMethodInfopoints(assertInlined(getGraph("invokeFinalMethodSnippet", true)));
93         assertManyMethodInfopoints(assertInlined(getGraph("invokeMethodOnFinalClassSnippet", true)));
94         assertManyMethodInfopoints(assertInlined(getGraph("invokeMethodOnStaticFinalFieldSnippet", true)));
95     }
96 
97     @Ignore("would need read elimination/EA before inlining")
98     @Test
testDependentStaticBindableInliningIP()99     public void testDependentStaticBindableInliningIP() {
100         assertManyMethodInfopoints(assertInlined(getGraph("invokeMethodOnFinalFieldSnippet", true)));
101         assertManyMethodInfopoints(assertInlined(getGraph("invokeMethodOnFieldSnippet", true)));
102     }
103 
104     @SuppressWarnings("all")
invokeConstructorSnippet(int value)105     public static Object invokeConstructorSnippet(int value) {
106         return new SuperClass(value);
107     }
108 
109     @SuppressWarnings("all")
invokeFinalMethodSnippet(SuperClass superClass, SubClassA subClassA, FinalSubClass finalSubClass)110     public static int invokeFinalMethodSnippet(SuperClass superClass, SubClassA subClassA, FinalSubClass finalSubClass) {
111         return superClass.publicFinalMethod() + subClassA.publicFinalMethod() + finalSubClass.publicFinalMethod() + superClass.protectedFinalMethod() + subClassA.protectedFinalMethod() +
112                         finalSubClass.protectedFinalMethod();
113     }
114 
115     @SuppressWarnings("all")
invokeMethodOnFinalClassSnippet(FinalSubClass finalSubClass)116     public static int invokeMethodOnFinalClassSnippet(FinalSubClass finalSubClass) {
117         return finalSubClass.publicFinalMethod() + finalSubClass.publicNotOverriddenMethod() + finalSubClass.publicOverriddenMethod() + finalSubClass.protectedFinalMethod() +
118                         finalSubClass.protectedNotOverriddenMethod() + finalSubClass.protectedOverriddenMethod();
119     }
120 
121     @SuppressWarnings("all")
invokeMethodOnStaticFinalFieldSnippet()122     public static int invokeMethodOnStaticFinalFieldSnippet() {
123         return StaticFinalFields.NumberStaticFinalField.intValue() + StaticFinalFields.SuperClassStaticFinalField.publicOverriddenMethod() +
124                         StaticFinalFields.FinalSubClassStaticFinalField.publicOverriddenMethod() + StaticFinalFields.SingleImplementorStaticFinalField.publicOverriddenMethod() +
125                         StaticFinalFields.MultipleImplementorsStaticFinalField.publicOverriddenMethod() + StaticFinalFields.SubClassAStaticFinalField.publicOverriddenMethod() +
126                         StaticFinalFields.SubClassBStaticFinalField.publicOverriddenMethod() + StaticFinalFields.SubClassCStaticFinalField.publicOverriddenMethod();
127     }
128 
129     @SuppressWarnings("all")
invokeMethodOnFinalFieldSnippet()130     public static int invokeMethodOnFinalFieldSnippet() {
131         FinalFields fields = new FinalFields();
132         return fields.numberFinalField.intValue() + fields.superClassFinalField.publicOverriddenMethod() + fields.finalSubClassFinalField.publicOverriddenMethod() +
133                         fields.singleImplementorFinalField.publicOverriddenMethod() + fields.multipleImplementorsFinalField.publicOverriddenMethod() +
134                         fields.subClassAFinalField.publicOverriddenMethod() + fields.subClassBFinalField.publicOverriddenMethod() + fields.subClassCFinalField.publicOverriddenMethod();
135     }
136 
137     @SuppressWarnings("all")
invokeMethodOnFieldSnippet()138     public static int invokeMethodOnFieldSnippet() {
139         Fields fields = new Fields();
140         return fields.numberField.intValue() + fields.superClassField.publicOverriddenMethod() + fields.finalSubClassField.publicOverriddenMethod() +
141                         fields.singleImplementorField.publicOverriddenMethod() + fields.multipleImplementorsField.publicOverriddenMethod() + fields.subClassAField.publicOverriddenMethod() +
142                         fields.subClassBField.publicOverriddenMethod() + fields.subClassCField.publicOverriddenMethod();
143     }
144 
145     public interface Attributes {
146 
getLength()147         int getLength();
148     }
149 
150     public class NullAttributes implements Attributes {
151 
152         @Override
getLength()153         public int getLength() {
154             return 0;
155         }
156 
157     }
158 
159     public class TenAttributes implements Attributes {
160 
161         @Override
getLength()162         public int getLength() {
163             return 10;
164         }
165 
166     }
167 
getAttributesLength(Attributes a)168     public int getAttributesLength(Attributes a) {
169         return a.getLength();
170     }
171 
172     @Test
testGuardedInline()173     public void testGuardedInline() {
174         NullAttributes nullAttributes = new NullAttributes();
175         for (int i = 0; i < 10000; i++) {
176             getAttributesLength(nullAttributes);
177         }
178         getAttributesLength(new TenAttributes());
179 
180         test("getAttributesLength", nullAttributes);
181         test("getAttributesLength", (Object) null);
182     }
183 
184     @Test
testClassHierarchyAnalysis()185     public void testClassHierarchyAnalysis() {
186         assertInlined(getGraph("invokeLeafClassMethodSnippet", false));
187         assertInlined(getGraph("invokeConcreteMethodSnippet", false));
188         if (GraalServices.hasLookupReferencedType()) {
189             assertInlined(getGraph("invokeSingleImplementorInterfaceSnippet", false));
190         }
191         // assertInlined(getGraph("invokeConcreteInterfaceMethodSnippet", false));
192 
193         assertNotInlined(getGraph("invokeOverriddenPublicMethodSnippet", false));
194         assertNotInlined(getGraph("invokeOverriddenProtectedMethodSnippet", false));
195         assertNotInlined(getGraph("invokeOverriddenInterfaceMethodSnippet", false));
196     }
197 
198     @Test
testClassHierarchyAnalysisIP()199     public void testClassHierarchyAnalysisIP() {
200         assertManyMethodInfopoints(assertInlined(getGraph("invokeLeafClassMethodSnippet", true)));
201         assertManyMethodInfopoints(assertInlined(getGraph("invokeConcreteMethodSnippet", true)));
202         if (GraalServices.hasLookupReferencedType()) {
203             assertManyMethodInfopoints(assertInlined(getGraph("invokeSingleImplementorInterfaceSnippet", true)));
204         }
205         //@formatter:off
206         // assertInlineInfopoints(assertInlined(getGraph("invokeConcreteInterfaceMethodSnippet", true)));
207         //@formatter:on
208 
209         assertFewMethodInfopoints(assertNotInlined(getGraph("invokeOverriddenPublicMethodSnippet", true)));
210         assertFewMethodInfopoints(assertNotInlined(getGraph("invokeOverriddenProtectedMethodSnippet", true)));
211         assertFewMethodInfopoints(assertNotInlined(getGraph("invokeOverriddenInterfaceMethodSnippet", true)));
212     }
213 
traceInliningTest()214     public static void traceInliningTest() {
215         callTrivial();
216     }
217 
callTrivial()218     private static void callTrivial() {
219         callNonTrivial();
220     }
221 
callNonTrivial()222     private static double callNonTrivial() {
223         double x = 0.0;
224         for (int i = 0; i < 10; i++) {
225             x += i * 1.21;
226         }
227         return x;
228     }
229 
230     @Test
231     @SuppressWarnings("try")
testTracing()232     public void testTracing() {
233         OptionValues options = new OptionValues(getInitialOptions(), GraalOptions.TraceInlining, true);
234         StructuredGraph graph;
235         try (TTY.Filter f = new TTY.Filter()) {
236             graph = getGraph("traceInliningTest", options, false);
237         }
238         String inliningTree = graph.getInliningLog().formatAsTree(false);
239         String expectedRegex = "compilation of org.graalvm.compiler.core.test.inlining.InliningTest.traceInliningTest.*: \\R" +
240                         "  at .*org.graalvm.compiler.core.test.inlining.InliningTest.traceInliningTest.*: <GraphBuilderPhase> org.graalvm.compiler.core.test.inlining.InliningTest.callTrivial.*: yes, inline method\\R" +
241                         "    at .*org.graalvm.compiler.core.test.inlining.InliningTest.callTrivial.*: .*\\R" +
242                         "       .*<GraphBuilderPhase> org.graalvm.compiler.core.test.inlining.InliningTest.callNonTrivial.*: .*(.*\\R)*" +
243                         "       .*<InliningPhase> org.graalvm.compiler.core.test.inlining.InliningTest.callNonTrivial.*: .*(.*\\R)*";
244         Pattern expectedPattern = Pattern.compile(expectedRegex, Pattern.MULTILINE);
245         Assert.assertTrue("Got: " + inliningTree, expectedPattern.matcher(inliningTree).matches());
246     }
247 
248     @SuppressWarnings("all")
invokeLeafClassMethodSnippet(SubClassA subClassA)249     public static int invokeLeafClassMethodSnippet(SubClassA subClassA) {
250         return subClassA.publicFinalMethod() + subClassA.publicNotOverriddenMethod() + subClassA.publicOverriddenMethod();
251     }
252 
253     @SuppressWarnings("all")
invokeConcreteMethodSnippet(SuperClass superClass)254     public static int invokeConcreteMethodSnippet(SuperClass superClass) {
255         return superClass.publicNotOverriddenMethod() + superClass.protectedNotOverriddenMethod();
256     }
257 
258     @SuppressWarnings("all")
invokeSingleImplementorInterfaceSnippet(SingleImplementorInterface testInterface)259     public static int invokeSingleImplementorInterfaceSnippet(SingleImplementorInterface testInterface) {
260         return testInterface.publicNotOverriddenMethod() + testInterface.publicOverriddenMethod();
261     }
262 
263     @SuppressWarnings("all")
invokeConcreteInterfaceMethodSnippet(MultipleImplementorsInterface testInterface)264     public static int invokeConcreteInterfaceMethodSnippet(MultipleImplementorsInterface testInterface) {
265         return testInterface.publicNotOverriddenMethod();
266     }
267 
268     @SuppressWarnings("all")
invokeOverriddenInterfaceMethodSnippet(MultipleImplementorsInterface testInterface)269     public static int invokeOverriddenInterfaceMethodSnippet(MultipleImplementorsInterface testInterface) {
270         return testInterface.publicOverriddenMethod();
271     }
272 
273     @SuppressWarnings("all")
invokeOverriddenPublicMethodSnippet(SuperClass superClass)274     public static int invokeOverriddenPublicMethodSnippet(SuperClass superClass) {
275         return superClass.publicOverriddenMethod();
276     }
277 
278     @SuppressWarnings("all")
invokeOverriddenProtectedMethodSnippet(SuperClass superClass)279     public static int invokeOverriddenProtectedMethodSnippet(SuperClass superClass) {
280         return superClass.protectedOverriddenMethod();
281     }
282 
getGraph(final String snippet, final boolean eagerInfopointMode)283     private StructuredGraph getGraph(final String snippet, final boolean eagerInfopointMode) {
284         return getGraph(snippet, null, eagerInfopointMode);
285     }
286 
287     @SuppressWarnings("try")
getGraph(final String snippet, OptionValues options, final boolean eagerInfopointMode)288     private StructuredGraph getGraph(final String snippet, OptionValues options, final boolean eagerInfopointMode) {
289         DebugContext debug = options == null ? getDebugContext() : getDebugContext(options, null, null);
290         try (DebugContext.Scope s = debug.scope("InliningTest", new DebugDumpScope(snippet, true))) {
291             ResolvedJavaMethod method = getResolvedJavaMethod(snippet);
292             Builder builder = builder(method, AllowAssumptions.YES, debug);
293             StructuredGraph graph = eagerInfopointMode ? parse(builder, getDebugGraphBuilderSuite()) : parse(builder, getEagerGraphBuilderSuite());
294             try (DebugContext.Scope s2 = debug.scope("Inlining", graph)) {
295                 PhaseSuite<HighTierContext> graphBuilderSuite = eagerInfopointMode
296                                 ? getCustomGraphBuilderSuite(GraphBuilderConfiguration.getDefault(getDefaultGraphBuilderPlugins()).withFullInfopoints(true))
297                                 : getDefaultGraphBuilderSuite();
298                 HighTierContext context = new HighTierContext(getProviders(), graphBuilderSuite, OptimisticOptimizations.ALL);
299                 debug.dump(DebugContext.BASIC_LEVEL, graph, "Graph");
300                 new CanonicalizerPhase().apply(graph, context);
301                 new InliningPhase(new CanonicalizerPhase()).apply(graph, context);
302                 debug.dump(DebugContext.BASIC_LEVEL, graph, "Graph");
303                 new CanonicalizerPhase().apply(graph, context);
304                 new DeadCodeEliminationPhase().apply(graph);
305                 return graph;
306             }
307         } catch (Throwable e) {
308             throw debug.handle(e);
309         }
310     }
311 
assertInlined(StructuredGraph graph)312     private static StructuredGraph assertInlined(StructuredGraph graph) {
313         return assertNotInGraph(graph, Invoke.class);
314     }
315 
assertNotInlined(StructuredGraph graph)316     private static StructuredGraph assertNotInlined(StructuredGraph graph) {
317         return assertInGraph(graph, Invoke.class);
318     }
319 
assertNotInGraph(StructuredGraph graph, Class<?> clazz)320     private static StructuredGraph assertNotInGraph(StructuredGraph graph, Class<?> clazz) {
321         for (Node node : graph.getNodes()) {
322             if (clazz.isInstance(node)) {
323                 fail(node.toString());
324             }
325         }
326         return graph;
327     }
328 
assertInGraph(StructuredGraph graph, Class<?> clazz)329     private static StructuredGraph assertInGraph(StructuredGraph graph, Class<?> clazz) {
330         for (Node node : graph.getNodes()) {
331             if (clazz.isInstance(node)) {
332                 return graph;
333             }
334         }
335         fail("Graph does not contain a node of class " + clazz.getName());
336         return graph;
337     }
338 
countMethodInfopoints(StructuredGraph graph)339     private static int[] countMethodInfopoints(StructuredGraph graph) {
340         int start = 0;
341         int end = 0;
342         for (FullInfopointNode ipn : graph.getNodes().filter(FullInfopointNode.class)) {
343             if (ipn.getReason() == InfopointReason.METHOD_START) {
344                 ++start;
345             } else if (ipn.getReason() == InfopointReason.METHOD_END) {
346                 ++end;
347             }
348         }
349         return new int[]{start, end};
350     }
351 
assertManyMethodInfopoints(StructuredGraph graph)352     private static StructuredGraph assertManyMethodInfopoints(StructuredGraph graph) {
353         int[] counts = countMethodInfopoints(graph);
354         if (counts[0] <= 1 || counts[1] <= 1) {
355             fail(String.format("Graph contains too few required method boundary infopoints: %d starts, %d ends.", counts[0], counts[1]));
356         }
357         return graph;
358     }
359 
assertFewMethodInfopoints(StructuredGraph graph)360     private static StructuredGraph assertFewMethodInfopoints(StructuredGraph graph) {
361         int[] counts = countMethodInfopoints(graph);
362         if (counts[0] > 1 || counts[1] > 1) {
363             fail(String.format("Graph contains too many method boundary infopoints: %d starts, %d ends.", counts[0], counts[1]));
364         }
365         return graph;
366     }
367 
368     // some interfaces and classes for testing
369     private interface MultipleImplementorsInterface {
370 
publicNotOverriddenMethod()371         int publicNotOverriddenMethod();
372 
publicOverriddenMethod()373         int publicOverriddenMethod();
374     }
375 
376     private interface SingleImplementorInterface {
377 
publicNotOverriddenMethod()378         int publicNotOverriddenMethod();
379 
publicOverriddenMethod()380         int publicOverriddenMethod();
381     }
382 
383     private static class SuperClass implements MultipleImplementorsInterface {
384 
385         protected int value;
386 
SuperClass(int value)387         SuperClass(int value) {
388             this.value = value;
389         }
390 
391         @Override
publicNotOverriddenMethod()392         public int publicNotOverriddenMethod() {
393             return value;
394         }
395 
396         @Override
publicOverriddenMethod()397         public int publicOverriddenMethod() {
398             return value;
399         }
400 
protectedNotOverriddenMethod()401         protected int protectedNotOverriddenMethod() {
402             return value;
403         }
404 
protectedOverriddenMethod()405         protected int protectedOverriddenMethod() {
406             return value;
407         }
408 
publicFinalMethod()409         public final int publicFinalMethod() {
410             return value + 255;
411         }
412 
protectedFinalMethod()413         protected final int protectedFinalMethod() {
414             return value + 255;
415         }
416     }
417 
418     private static class SubClassA extends SuperClass implements SingleImplementorInterface {
419 
SubClassA(int value)420         SubClassA(int value) {
421             super(value);
422         }
423 
424         @Override
publicOverriddenMethod()425         public int publicOverriddenMethod() {
426             return value + 2;
427         }
428 
429         @Override
protectedOverriddenMethod()430         protected int protectedOverriddenMethod() {
431             return value * 2;
432         }
433     }
434 
435     private static class SubClassB extends SuperClass {
436 
SubClassB(int value)437         SubClassB(int value) {
438             super(value);
439         }
440 
441         @Override
publicOverriddenMethod()442         public int publicOverriddenMethod() {
443             return value + 3;
444         }
445 
446         @Override
protectedOverriddenMethod()447         protected int protectedOverriddenMethod() {
448             return value * 3;
449         }
450     }
451 
452     private static class SubClassC extends SuperClass {
453 
SubClassC(int value)454         SubClassC(int value) {
455             super(value);
456         }
457 
458         @Override
publicOverriddenMethod()459         public int publicOverriddenMethod() {
460             return value + 4;
461         }
462 
463         @Override
protectedOverriddenMethod()464         protected int protectedOverriddenMethod() {
465             return value * 4;
466         }
467     }
468 
469     private static final class FinalSubClass extends SuperClass {
470 
FinalSubClass(int value)471         FinalSubClass(int value) {
472             super(value);
473         }
474 
475         @Override
publicOverriddenMethod()476         public int publicOverriddenMethod() {
477             return value + 5;
478         }
479 
480         @Override
protectedOverriddenMethod()481         protected int protectedOverriddenMethod() {
482             return value * 5;
483         }
484     }
485 
486     private static final class StaticFinalFields {
487 
488         private static final Number NumberStaticFinalField = new Integer(1);
489         private static final SuperClass SuperClassStaticFinalField = new SubClassA(2);
490         private static final FinalSubClass FinalSubClassStaticFinalField = new FinalSubClass(3);
491         private static final SingleImplementorInterface SingleImplementorStaticFinalField = new SubClassA(4);
492         private static final MultipleImplementorsInterface MultipleImplementorsStaticFinalField = new SubClassC(5);
493         private static final SubClassA SubClassAStaticFinalField = new SubClassA(6);
494         private static final SubClassB SubClassBStaticFinalField = new SubClassB(7);
495         private static final SubClassC SubClassCStaticFinalField = new SubClassC(8);
496     }
497 
498     private static final class FinalFields {
499 
500         private final Number numberFinalField = new Integer(1);
501         private final SuperClass superClassFinalField = new SubClassA(2);
502         private final FinalSubClass finalSubClassFinalField = new FinalSubClass(3);
503         private final SingleImplementorInterface singleImplementorFinalField = new SubClassA(4);
504         private final MultipleImplementorsInterface multipleImplementorsFinalField = new SubClassC(5);
505         private final SubClassA subClassAFinalField = new SubClassA(6);
506         private final SubClassB subClassBFinalField = new SubClassB(7);
507         private final SubClassC subClassCFinalField = new SubClassC(8);
508     }
509 
510     private static final class Fields {
511 
512         private Number numberField = new Integer(1);
513         private SuperClass superClassField = new SubClassA(2);
514         private FinalSubClass finalSubClassField = new FinalSubClass(3);
515         private SingleImplementorInterface singleImplementorField = new SubClassA(4);
516         private MultipleImplementorsInterface multipleImplementorsField = new SubClassC(5);
517         private SubClassA subClassAField = new SubClassA(6);
518         private SubClassB subClassBField = new SubClassB(7);
519         private SubClassC subClassCField = new SubClassC(8);
520     }
521 }
522