blob: 5284303927d1741f96fb2e7e6ca32577553eb0b0 [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.sc
import org.codehaus.groovy.classgen.asm.AbstractBytecodeTestCase
import org.codehaus.groovy.runtime.MethodClosure
import static org.codehaus.groovy.control.CompilerConfiguration.DEFAULT as config
class StaticCompilationTest extends AbstractBytecodeTestCase {
void testEmptyMethod() {
def bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {}
''')
assert bytecode.hasStrictSequence(
['public m()V', 'L0', 'LINENUMBER 3 L0', 'RETURN']
)
}
void testPrimitiveReturn1() {
def bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
int m() { 1 }
''')
assert bytecode.hasStrictSequence(
['ICONST_1', 'IRETURN']
)
}
void testPrimitiveReturn2() {
def bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
long m() { 1L }
''')
assert bytecode.hasStrictSequence(
['LCONST_1', 'LRETURN']
)
}
void testPrimitiveReturn3() {
def bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
short m() { 1 }
''')
assert bytecode.hasStrictSequence(
['ICONST_1', 'I2S', 'IRETURN']
)
}
void testPrimitiveReturn4() {
def bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
byte m() { 1 }
''')
assert bytecode.hasStrictSequence(
['ICONST_1', 'I2B', 'IRETURN']
)
}
void testIdentityReturns() {
def bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
int m(int i) { i }
''')
assert bytecode.hasStrictSequence(
['ILOAD', 'IRETURN']
)
bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
long m(long l) { l }
''')
assert bytecode.hasStrictSequence(
['LLOAD', 'LRETURN']
)
bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
short m(short l) { l }
''')
assert bytecode.hasStrictSequence(
['ILOAD', 'IRETURN']
)
bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
float m(float l) { l }
''')
assert bytecode.hasStrictSequence(
['FLOAD', 'FRETURN']
)
bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
double m(double l) { l }
''')
assert bytecode.hasStrictSequence(
['DLOAD', 'DRETURN']
)
bytecode = compile(method: 'm', '''
@groovy.transform.CompileStatic
Object m(Object l) { l }
''')
assert bytecode.hasStrictSequence(
['ALOAD', 'ARETURN']
)
}
void testSingleAssignment() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
int a = 1
}
''').hasSequence([
"ICONST_1",
"ISTORE",
"RETURN"
])
}
void testReturnSingleAssignment() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
int m() {
int a = 1
}
''').hasSequence([
"ICONST_1",
"ISTORE",
"ILOAD",
"IRETURN"
])
}
void testIntLeftShift() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
int a = 1
int b = a << 32
}
''').hasStrictSequence([
"ILOAD",
"BIPUSH 32",
"ISHL"
])
}
void testLongLeftShift() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
long a = 1L
long b = a << 32
}
''').hasStrictSequence([
"LLOAD",
"BIPUSH 32",
"LSHL"
])
}
void testArrayGet() {
if (config.indyEnabled) return;
// this test is done with indy in another tests case
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m(int[] arr) {
arr[0]
}
''').hasStrictSequence([
"ALOAD 1",
"ICONST_0",
"INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArrayGet ([II)I"
])
}
void testArraySet() {
if (config.indyEnabled) return;
// this test is done with indy in another tests case
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m(int[] arr) {
arr[0] = 0
}
''').hasStrictSequence([
"ICONST_0",
"ISTORE 2",
"ALOAD 1",
"ICONST_0",
"ILOAD 2",
"INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArraySet ([III)V"
])
}
/* void testPlusPlus() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
int i = 0
i++
}
''').hasStrictSequence([
"IINC",
])
}
void testMinusMinus() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
int i = 0
i--
}
''').hasStrictSequence([
"IINC",
])
}
void testPlusEquals() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
int m() {
int i = 0
i += 13
return i
}
''').hasStrictSequence([
"ILOAD",
"ILOAD",
"IADD",
"ISTORE"
])
}
void testPlusEqualsFromArgs() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m(int i, int j) {
i += j
}
''').hasStrictSequence([
"ILOAD",
"ILOAD",
"IADD",
"ISTORE"
])
}*/
void testFlow() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
String m(String str) {
def obj = 1
obj = str
obj.toUpperCase()
}
m 'Cedric'
''').hasStrictSequence([
"ICONST",
"INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;",
"ASTORE",
"L1",
"ALOAD 2",
"POP",
"L2",
"LINENUMBER",
"ALOAD 1",
"ASTORE 3",
"ALOAD 3",
"ASTORE 2",
"ALOAD 3",
"POP",
"L3",
"LINENUMBER",
"ALOAD 2",
"CHECKCAST java/lang/String",
"INVOKEVIRTUAL java/lang/String.toUpperCase ()Ljava/lang/String;",
"ARETURN",
"L4"
])
}
void testInstanceOf() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m(Object str) {
if (str instanceof String) {
str.toUpperCase()
}
}
m 'Cedric'
''').hasStrictSequence([
"ALOAD",
"CHECKCAST java/lang/String",
"INVOKEVIRTUAL java/lang/String.toUpperCase ()Ljava/lang/String;"
])
}
void testShouldGenerateDirectConstructorCall() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
class Foo {
String msg
Foo(int x, String y) { msg = y*x }
static Foo foo() {
Foo result = [2,'Bar']
}
}
''').hasStrictSequence([
'ICONST_2',
'LDC "Bar"',
'INVOKESPECIAL Foo.<init> (ILjava/lang/String;)V'
])
}
void testShouldGenerateDirectArrayConstruct() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
int[] arr = [123,456]
}
''').hasStrictSequence([
'ICONST_2',
'NEWARRAY T_INT',
'DUP',
'ICONST_0',
'BIPUSH 123',
'IASTORE'
])
}
void testShouldGenerateDirectBooleanArrayConstruct() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
boolean[] arr = [123,false]
}
''').hasStrictSequence([
'ICONST_2',
'NEWARRAY T_BOOLEAN',
'DUP',
'ICONST_0',
'BIPUSH 123',
'INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;',
'INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox (Ljava/lang/Object;)Z',
'BASTORE'
])
}
void testShouldTriggerDirectCallToOuterClassGetter() {
assert compile(method: 'fromInner', classNamePattern: '.*Inner.*', '''
class Holder {
String value
}
@groovy.transform.CompileStatic
class Outer {
String outerProperty = 'outer'
private class Inner {
String fromInner() {
Holder holder = new Holder()
holder.value = outerProperty
holder.value
}
}
String blah() {
new Inner().fromInner()
}
}
def o = new Outer()
assert o.blah() == 'outer'
''').hasStrictSequence([
'GETFIELD Outer$Inner.this$0',
'INVOKEVIRTUAL Outer.getOuterProperty',
'DUP',
'ASTORE',
'ALOAD',
'ALOAD',
'INVOKEVIRTUAL Holder.setValue'
])
}
void testShouldOptimizeBytecodeByAvoidingCreationOfMopMethods() {
def shell = new GroovyShell()
def clazz = shell.evaluate '''
import groovy.transform.TypeCheckingMode
import groovy.transform.CompileStatic
@CompileStatic
class A {
def doSomething() { 'A' }
}
@CompileStatic
class B extends A {
def doSomething() { 'B' + super.doSomething() }
}
B
'''
assert clazz instanceof Class
assert clazz.name == 'B'
def mopMethods = clazz.declaredMethods.findAll { it.name =~ /(super|this)\$/ }
assert mopMethods.empty
}
void testShouldNotOptimizeBytecodeForMopMethodsBecauseOfSkip() {
def shell = new GroovyShell()
def clazz = shell.evaluate '''
import groovy.transform.TypeCheckingMode
import groovy.transform.CompileStatic
@CompileStatic
class A {
def doSomething() { 'A' }
}
@CompileStatic
class B extends A {
@CompileStatic(TypeCheckingMode.SKIP)
def doSomething() { 'B' + super.doSomething() }
}
B
'''
assert clazz instanceof Class
assert clazz.name == 'B'
def mopMethods = clazz.declaredMethods.findAll { it.name =~ /(super|this)\$/ }
assert !mopMethods.empty
}
// GROOVY-7124
void testUseInvokeVirtualPreferredOverInvokeInterface() {
assert compile(method: 'foo', classNamePattern: 'B', '''
interface A { void m() }
class B implements A {
void m() {}
@groovy.transform.CompileStatic
void foo() {
m()
}
}
''').hasStrictSequence(['INVOKEVIRTUAL B.m'])
}
void testShouldNotTryToCastToSupposedDelegateType() {
assertScript '''
@groovy.transform.CompileStatic
class ClassCastOhNoes {
def foo(def o) {
def cl = {
delegate.getClass()
}
cl.delegate = o
cl()
}
}
new ClassCastOhNoes().foo(100)
'''
}
void testShouldOptimizeCharInit() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
char c = 'x'
}
''').hasStrictSequence([
'LINENUMBER 4',
'BIPUSH 120',
'ISTORE'
])
}
void testShouldOptimizeCharComparison() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
char c1 = 'x'
char c2 = 'x'
boolean b = c1==c2
}
''').hasSequence([
'LINENUMBER 4',
'BIPUSH 120',
'ISTORE',
'BIPUSH 120',
'ISTORE',
'ILOAD',
'ILOAD',
'IF_ICMPNE',
])
// make sure the code passes
assertScript '''
@groovy.transform.CompileStatic
void m() {
char c1 = 'x'
char c2 = 'x'
boolean b = c1==c2
assert b
char c3 = 'z'
b = c1==c3
assert !b
}
m()
'''
}
void testForEachLoopOptimization() {
def loop_init = [
'ALOAD', // load array (might be more complex than just ALOAD in general case)
'DUP',
'ASTORE', // store array to local var
'ARRAYLENGTH', // get array len
'ISTORE', // store it to local var
'ICONST_0',
'ISTORE', // initialize loop index
'ILOAD', // load loop index
'ILOAD', // load array length
'IF_ICMPGE' // if greater or equal, end of loop
]
// int[]
def intExample = '''
@groovy.transform.CompileStatic
void m(int[] arr) {
for (int i: arr) {
println(i)
}
}
m([1,2] as int[])
'''
assert compile(method:'m', intExample).hasSequence([*loop_init,'IALOAD','ISTORE'])
// short[]
def shortExample = '''
@groovy.transform.CompileStatic
void m(short[] arr) {
for (short i: arr) {
println(i)
}
}
m([1,2] as short[])
'''
assert compile(method:'m', shortExample).hasSequence([*loop_init,'SALOAD','ISTORE'])
// byte[]
def byteExample = '''
@groovy.transform.CompileStatic
void m(byte[] arr) {
for (byte i: arr) {
println(i)
}
}
m([1,2] as byte[])
'''
assert compile(method:'m', byteExample).hasSequence([*loop_init,'BALOAD','ISTORE'])
// long[]
def longExample = '''
@groovy.transform.CompileStatic
void m(long[] arr) {
for (long i: arr) {
println(i)
}
}
m([1,2] as long[])
'''
assert compile(method:'m', longExample).hasSequence([*loop_init,'LALOAD','LSTORE'])
// char[]
def charExample = '''
@groovy.transform.CompileStatic
void m(char[] arr) {
for (char i: arr) {
println(i)
}
}
m('foo'.toCharArray())
'''
assert compile(method:'m', charExample).hasSequence([*loop_init,'CALOAD','ISTORE'])
// boolean[]
def boolExample = '''
@groovy.transform.CompileStatic
void m(boolean[] arr) {
for (boolean i: arr) {
println(i)
}
}
m([true,false] as boolean[])
'''
assert compile(method:'m', boolExample).hasSequence([*loop_init,'BALOAD','ISTORE'])
// float[]
def floatExample = '''
@groovy.transform.CompileStatic
void m(float[] arr) {
for (float i: arr) {
println(i)
}
}
m([1.5f,2.0f] as float[])
'''
assert compile(method:'m', floatExample).hasSequence([*loop_init,'FALOAD','FSTORE'])
// double[]
def doubleExample = '''
@groovy.transform.CompileStatic
void m(double[] arr) {
for (double i: arr) {
println(i)
}
}
m([1.1,2.2] as double[])
'''
assert compile(method:'m', doubleExample).hasSequence([*loop_init,'DALOAD','DSTORE'])
// Any[]
def anyExample = '''
@groovy.transform.CompileStatic
void m(String[] arr) {
for (String i: arr) {
println(i)
}
}
m(['a','b'] as String[])
'''
assert compile(method:'m', anyExample).hasSequence([*loop_init,'AALOAD','ASTORE'])
// now check that everything runs fine
[byteExample,shortExample, intExample, charExample, boolExample, longExample, floatExample, doubleExample, anyExample].each { script ->
assertScript(script)
}
}
void testCompareWithCharOptimization() {
String code = '''
@groovy.transform.CompileStatic
boolean m(char[] arr) {
char c = arr[0]
' '==c
}
assert m(' abc '.toCharArray()) == true
'''
assert compile(method:'m',code).hasSequence(['BIPUSH','ILOAD','IF_ICMPNE'])
assertScript(code)
code = '''
@groovy.transform.CompileStatic
boolean m(char[] arr) {
char c = arr[0]
c==' '
}
assert m(' abc '.toCharArray()) == true
'''
assert compile(method:'m',code).hasSequence(['ILOAD','BIPUSH','IF_ICMPNE'])
assertScript(code)
}
void testShouldRemoveUnnecessaryCast() {
assert compile(method: 'm', '''
@groovy.transform.CompileStatic
void m() {
char c = (char) 'x'
}
''').hasStrictSequence([
'LINENUMBER 4',
'BIPUSH 120',
// No checkcast, but the idea is to check that further optimization was done
// because the RHS is no longer a CastExpression but a ConstantExpression
'ISTORE'
])
}
void testInstanceMethodReference() {
// dynamic case should be a method closure
assert String::toUpperCase instanceof MethodClosure
// static case should be compiled into functional interface
String code = '''
import groovy.transform.CompileStatic
import java.util.stream.Collectors
@CompileStatic
void m() {
assert ['foo'].stream().map(String::toUpperCase).collect(Collectors.toList()) == ['FOO']
}
m()
'''
assert compile(method:'m', code).hasSequence([
'INVOKEDYNAMIC apply()Ljava/util/function/Function;',
/* handle kind 0x6 : INVOKESTATIC */
'java/lang/invoke/LambdaMetafactory.metafactory',
/* handle kind 0x5 : INVOKEVIRTUAL */
'java/lang/String.toUpperCase()Ljava/lang/String;'
])
assertScript(code)
}
}