/*
 * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */


package org.graalvm.compiler.jtt.backend;

import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.LRETURN;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.jtt.JTTTest;
import org.graalvm.compiler.api.test.ExportingClassLoader;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import jdk.vm.ci.meta.ResolvedJavaMethod;
import junit.framework.AssertionFailedError;

/**
 * This test let the compiler deal with a large amount of constant data in a method. This data is
 * stored typically in the constant section of the native method. Especially on the SPARC platform
 * the backend can address only 8k of memory with an immediate offset. Beyond this barrier, a
 * different addressing mode must be used.
 *
 * In order to do this this test generates a large method containing a large switch statement in
 * form of
 *
 * <code>
 *  static long run(long a) {
 *    switch(a) {
 *    case 1:
 *    return 0xF0F0F0F0F0L + 1;
 *    case 2:
 *    return 0xF0F0F0F0F0L + 2;
 *    ....
 *    default:
 *    return 0;
 *    }
 *
 *  }
 *  </code>
 *
 */
@RunWith(Parameterized.class)
public class LargeConstantSectionTest extends JTTTest {
    private static final String NAME = "LargeConstantSection";
    private static final long LARGE_CONSTANT = 0xF0F0F0F0F0L;
    private static LargeConstantClassLoader LOADER;
    @Parameter(value = 0) public int numberBlocks;

    @Parameters(name = "{0}")
    public static Collection<Object[]> data() {
        List<Object[]> parameters = new ArrayList<>();
        for (int i = 4; i < 13; i += 2) {
            parameters.add(new Object[]{1 << i});
        }
        return parameters;
    }

    @Before
    public void before() {
        LOADER = new LargeConstantClassLoader(LargeConstantSectionTest.class.getClassLoader());
    }

    public class LargeConstantClassLoader extends ExportingClassLoader {
        public LargeConstantClassLoader(ClassLoader parent) {
            super(parent);
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            if (name.equals(NAME)) {
                ClassWriter cw = new ClassWriter(0);
                MethodVisitor mv;
                cw.visit(52, ACC_PUBLIC + ACC_SUPER, NAME, null, "java/lang/Object", null);

                mv = cw.visitMethod(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "run", "(I)J", null, null);
                mv.visitCode();
                Label begin = new Label();
                mv.visitLabel(begin);
                mv.visitVarInsn(ILOAD, 0);
                Label[] labels = new Label[numberBlocks];
                int[] keys = new int[numberBlocks];
                for (int i = 0; i < labels.length; i++) {
                    labels[i] = new Label();
                    keys[i] = i;
                }
                Label defaultLabel = new Label();
                mv.visitLookupSwitchInsn(defaultLabel, keys, labels);
                for (int i = 0; i < labels.length; i++) {
                    mv.visitLabel(labels[i]);
                    mv.visitFrame(Opcodes.F_NEW, 1, new Object[]{Opcodes.INTEGER}, 0, new Object[]{});
                    mv.visitLdcInsn(Long.valueOf(LARGE_CONSTANT + i));
                    mv.visitInsn(LRETURN);
                }
                mv.visitLabel(defaultLabel);
                mv.visitFrame(Opcodes.F_NEW, 1, new Object[]{Opcodes.INTEGER}, 0, new Object[]{});
                mv.visitLdcInsn(Long.valueOf(3L));
                mv.visitInsn(LRETURN);
                Label end = new Label();
                mv.visitLabel(end);
                mv.visitLocalVariable("a", "I", null, begin, end, 0);
                mv.visitMaxs(2, 1);
                mv.visitEnd();
                byte[] bytes = cw.toByteArray();
                return defineClass(name, bytes, 0, bytes.length);
            } else {
                return super.findClass(name);
            }
        }
    }

    @Test
    @SuppressWarnings("try")
    public void run0() throws Exception {
        test("run", numberBlocks - 3);
    }

    @Override
    protected ResolvedJavaMethod getResolvedJavaMethod(String methodName) {
        try {
            for (Method method : LOADER.findClass(NAME).getDeclaredMethods()) {
                if (method.getName().equals(methodName)) {
                    return asResolvedJavaMethod(method);
                }
            }
        } catch (ClassNotFoundException e) {
            throw new AssertionFailedError("Cannot find class " + NAME);
        }
        throw GraalError.shouldNotReachHere();
    }
}
