blob: 5bae19dfe02412df6a56689d16897314b572f2c0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.groovy.classgen.asm
import static org.codehaus.groovy.control.CompilerConfiguration.DEFAULT as config
class MethodPatternsTest extends AbstractBytecodeTestCase {
void testUnoptimizedIfWithNestedOptimizedLoop(){
if (config.indyEnabled) return;
// in this example the if block contains statements that will not be optimized
// but we still want to optimize the for loops, which can.
// The test will check there is a optimized bytecode sequence for the loops.
assert compile('''
long sum = 0;
double m = 1;
if( true ) {
System.err.println( "START");
long t0 = System.currentTimeMillis();
for( int j=0; j<1000; j++ ) {
for( int i=0; i<100_000; i++ ) {
sum = sum + i;
m = m*i;
}
}
long t1 = System.currentTimeMillis();
System.err.println( "END - " + (t1-t0)+"ms");
}
System.err.println( "Done: "+sum+" "+m );
''').hasSequence([
// for (int j=0; j<1000; j++) start and condition
'ICONST_0',
'SIPUSH 1000',
'IF_ICMPGE',
'ICONST_1',
// for (int i=0; i<100_000; i++) start and condition
'ICONST_0',
'LDC 100000',
'IF_ICMPGE',
'ICONST_1',
'GOTO',
'ICONST_0',
'IFEQ',
// sum = sum + i
'LLOAD',
'ILOAD',
'I2L',
'LADD',
// m = m * i
'DLOAD',
'ILOAD',
'I2D',
'DMUL',
// for (int i=0; i<100_000; i++) increment
'ILOAD',
'ICONST_1',
'IADD',
'ISTORE',
// for (int j=0; j<1000; j++) increment
'ILOAD',
'ICONST_1',
'IADD',
'ISTORE'
])
}
// make a test for native compilation of the ackerman function
// and ensure the nested call is optimized
void testAckerman() {
if (config.indyEnabled) return;
assert compile(method: 'A', '''
int A(int x, int y) {
if (x == 0) return y+1
if (y == 0) return A(x-1, 1)
return A(x-1, A(x, y-1))
}
''').hasSequence([
// if (x==0) return y+1
'ILOAD 1',
'ICONST_0',
'IF_ICMPNE',
'ICONST_1',
'ICONST_0',
'IFEQ', // x==0 and branching till here
'ILOAD 2',
'ICONST_1',
'IADD', // y+1
'IRETURN', // return
// if (y==0) return A(x-1,1)
'ILOAD 2',
'ICONST_0',
'IF_ICMPNE',
'ICONST_1',
'ICONST_0',
'IFEQ', // y==0 and branching till here
'ALOAD 0',
'ILOAD 1',
'ICONST_1',
'ISUB', // x-1 argument
'ICONST_1',
'INVOKEVIRTUAL script.A (II)I', // A(x-1,1)
'IRETURN', //return
// return A(x-1,A(x,y-1))
'ALOAD 0',
'ILOAD 1',
'ICONST_1',
'ISUB', // outer A x-1 argument
'ALOAD 0',
'ILOAD 1', // inner A x argument
'ILOAD 2',
'ICONST_1',
'ISUB', //inner A y-1 argument
'INVOKEVIRTUAL script.A (II)I', // inner A
'INVOKEVIRTUAL script.A (II)I', // outer A
'IRETURN' //return
])
}
void testForLoopSettingArray() {
if (config.indyEnabled) return;
assert compile('''
int n = 10
int[] x = new int[n]
for (int i = 0; i < n; i++) x[i] = i + 1
''').hasSequence([
'ILOAD',
'ILOAD',
'IF_ICMPGE',
'ICONST_1',
'GOTO',
'ICONST_0',
'IFEQ',
'ILOAD',
'ICONST_1',
'IADD',
'ISTORE',
'ALOAD',
'ILOAD',
'ILOAD',
'INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArraySet ([III)V',
'ILOAD',
'POP',
'ILOAD',
'DUP',
'ISTORE',
'ICONST_1',
'IADD',
'DUP',
'ISTORE',
'POP',
'ILOAD',
'POP',
'GOTO',
])
}
void testArrayIncrement() {
if (config.indyEnabled) return;
assert compile('''
int n = 10
int[] x = new int[n]
for (int i = 0; i < n; i++) x[i]++
''').hasSequence([
'ICONST_0',
'ISTORE',
'ILOAD',
'POP',
'ILOAD',
'ILOAD',
'IF_ICMPGE',
'ICONST_1',
'GOTO',
'ICONST_0',
'IFEQ',
'ILOAD',
'ISTORE',
'ALOAD',
'ILOAD',
'INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArrayGet ([II)I',
'DUP',
'ISTORE',
'ICONST_1',
'IADD',
'ISTORE',
'ALOAD',
'ILOAD',
'ILOAD',
'INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArraySet ([III)V',
'ILOAD',
'POP',
'ILOAD',
'POP',
'ILOAD',
'DUP',
'ISTORE',
'ICONST_1',
'IADD',
'DUP',
'ISTORE',
'POP',
'ILOAD',
'POP',
'GOTO',
])
}
void testForLoopSettingArrayWithOperatorUsedInAssignmentAndArrayRHS() {
if (config.indyEnabled) return;
assert compile('''
int n = 10
int[] x = new int[n]
int[] y = new int[n]
for (int i = 0; i < n; i++) x[i] += y[i]
''').hasSequence ([
'ILOAD',
'ILOAD',
'IF_ICMPGE',
'ICONST_1',
'GOTO',
'ICONST_0',
'IFEQ',
'ILOAD',
'ISTORE',
'ALOAD',
'DUP',
'ILOAD',
'INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArrayGet ([II)I',
'ALOAD',
'ILOAD',
'INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArrayGet ([II)I',
'IADD',
'DUP',
'ISTORE',
'ILOAD',
'SWAP',
'INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArraySet ([III)V',
'ILOAD',
'DUP',
'ISTORE',
'ICONST_1',
'IADD',
'ISTORE',
'GOTO'
])
}
void testRightShiftUnsignedWithLongArgument() {
if (config.indyEnabled) return;
assert compile(method: "hashCode", '''
class X{
long _tagReservationDate
String userId, partnerItemId, trackingTag
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (partnerItemId?.hashCode() ?: 0)
result = prime * result + (int) (_tagReservationDate ^ (_tagReservationDate >>> 32))
result = prime * result + (trackingTag?.hashCode() ?: 0)
result = prime * result + (userId?.hashCode() ?: 0)
return result;
}
}
''').hasSequence ([
'IMUL',
'ALOAD 0',
'GETFIELD X._tagReservationDate : J',
'ALOAD 0',
'GETFIELD X._tagReservationDate : J',
'BIPUSH 32',
'LUSHR',
'LXOR',
'L2I',
'IADD',
])
}
void testObjectArraySet() {
if (config.indyEnabled) return;
assert compile(method: "foo", '''
class X {
void foo() {
X[] xa = new X[1]
xa[0] = new X()
}
}
''').hasSequence ([
'ICONST_0',
'INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.objectArraySet ([Ljava/lang/Object;ILjava/lang/Object;)V',
])
}
void testBooleanArraySet() {
if (config.indyEnabled) return;
assert compile(method: "foo", '''
class X{
void foo() {
boolean[] xa = new boolean[1]
xa[0] = false
}
}
''').hasSequence ([
'ICONST_0',
'INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.zArraySet ([ZIZ)V',
])
}
void testArray() {
if (config.indyEnabled) return;
def methods = [
"short" : [1, "sArraySet ([SIS)V", "sArrayGet ([SI)S"],
"int" : [1, "intArraySet ([III)V", "intArrayGet ([II)I"],
"boolean" : [false, "zArraySet ([ZIZ)V", "zArrayGet ([ZI)Z"],
"long" : [1l, "lArraySet ([JIJ)V","lArrayGet ([JI)J"],
"float" : [1f, "fArraySet ([FIF)V", "fArrayGet ([FI)F"],
"byte" : [1, "bArraySet ([BIB)V", "bArrayGet ([BI)B"],
"char" : [1, "cArraySet ([CIC)V", "cArrayGet ([CI)C"],
"double" : [1d, "dArraySet ([DID)V", "dArrayGet ([DI)D"]
]
methods.each {
assert compile(method: "foo", """
class X{
void foo() {
${it.key}[] xa = new ${it.key}[1]
xa[0] = ${it.value[0]}
}
}
""").hasSequence ([
'ICONST_0',
"INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8."+it.value[1],
])
assert compile(method: "foo", """
class X{
${it.key} foo() {
${it.key}[] xa = new ${it.key}[1]
xa[0]
}
}
""").hasSequence ([
'ICONST_0',
"INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.${it.value[2]}",
])
}
assertScript """
a=[3:5]
class B {
int v;
}
B b = new B();
b.v = 3
clos = {
if (it!=null) {
a[it.v] += 3
}
}
clos.call(b)
assert b.v == 3
assert a[3] == 8
"""
}
void testFib() {
if (config.indyEnabled) return;
assert compile(method: "fib", """
int fib(int i) {
i < 2 ? 1 : fib(i - 2) + fib(i - 1)
}
""").hasSequence ([
'ILOAD 1',
'ICONST_2',
'IF_ICMPGE',
'ICONST_1',
'GOTO',
'ICONST_0',
'IFEQ',
'ICONST_1',
'GOTO',
'ALOAD 0',
'ILOAD 1',
'ICONST_2',
'ISUB',
'INVOKEVIRTUAL script.fib (I)I',
'ALOAD 0',
'ILOAD 1',
'ICONST_1',
'ISUB',
'INVOKEVIRTUAL script.fib (I)I',
'IADD',
'IRETURN'
])
// check that there is no fastpath for this method, since n is Object
def seq = compile(method: "fib", """
def fib(n) {
n<=2L?n:fib(n-1L)+fib(n-2L)
}
""").toSequence()
// isOrigXY is used for the fastpath guards
assert !seq.contains("isOrig")
}
void testNoBoxUnbox() {
if (config.indyEnabled) return;
assert compile(method: "someCode", """
public boolean someCall() {
return true;
}
public boolean someCode() {
boolean val = someCall()
}
""").hasSequence([
'ALOAD',
'INVOKEVIRTUAL script.someCall ()Z',
'ISTORE',
'ILOAD',
'IRETURN',
])
}
void testDiv() {
if (config.indyEnabled) return;
def types = [
"byte", "short", "int", "long", "double", "float"]
types.each {type ->
assert compile(method: "someCode","""
def someCode() {
$type v = 5/4
}
""").hasSequence(["IDIV"])
}
types.each {type ->
assert compile(method: "someCode","""
def someCode() {
$type v = 0
v = 5/4
}
""").hasSequence(["IDIV"])
}
assert compile(method: "someCode", """
def someCode() {
long l = 5l/4l
}
""").hasSequence(["LDIV"])
assert compile(method: "someCode", """
def someCode() {
long l
l = 5l/4l
}
""").hasSequence(["LDIV"])
}
}