/*
 * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package vm.mlvm.share;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import nsk.share.Log.TraceLevel;
import vm.share.options.Option;

/**
 * The test looks for late CPU stores, which is not visible for other CPU.
 * <p>
 * In absence of synchronization (such as memory barriers and so on), typical modern CPUs
 * (Intel x86 32/64, SPARC in TSO mode) put stores into buffer instead of immediately writing them to memory.
 *
 * So the following program:
 * <code>
 *    write A
 *    read B
 * </code>
 * can actually be transformed to and seen by other processors as:
 * <code>
 *    read B
 *    write A
 *  </code>
 * <p>
 * DekkerTest runs two threads, A and B, which perform operations on a shared array of Actors concurrently.
 * Reordering mentioned above is detected by the test and reported as failure
 * (unless mayFail option is specified in the constructor or command-line option).
 *
 * <p>
 * The tests should subclass Actor with test-specific data and code.
 * The Actor subclass requirements:
 *
 * <ul>
 * <li>the class should have two data fields:
 * <code>
 *   A
 *   B
 * </code>
 *   of the same type, which are able hold one of two values.
 *   Let's call these values TRUE and FALSE (these can be any two values, say 0 and 1).
 *
 * <li>* actorA() is called from thread A and should implement the following pseudo-code:
 * <code>
 *   A = TRUE
 *   optional MEMBAR #StoreLoad
 *   return (B == TRUE)
 * </code>
 *
 * <li>actorB() is called from thread B and should do the following:
 * <code>
 *   {
 *       B = TRUE
 *       optional MEMBAR #StoreLoad
 *       return (A == TRUE)
 *   }
 * </code>
 *
 * <li>reset() method should do the following:
 * <code>
 *   {
 *       A = FALSE
 *       B = FALSE
 *   }
 * </code>
 *
 *   The use of memory barriers in actorX() methods is up to the implementor -- depending on the goal of testing.
 *
 */
public class DekkerTest extends MlvmTest {

    @Option(name = "actorClass", default_value = "", description = "Name of actor class (see test comments)")
    private String actorClassNameOpt = "";

    @Option(name = "mayFail", default_value = "false", description = "Don't report test failure (use it just a stress test)")
    private boolean mayFailOpt;

    @Option(name = "iterations", default_value = "1000000", description = "Number of iterations in each Actor thread (i.e., shared array size)")
    private int iterationsOpt = 1000000;

    /**
     * Actor interface, which should be implemented for using with DekkerTest.
     * The requirements for Actor implementation are outlined in {@link DekkerTest} class documentation.
     */
    public interface Actor {
        /**
         * Sets fields A and B to false
         */
        void reset();

        /**
         * Sets field A to true, and returns field B contents
         * @return field B contents
         * @throws Throwable in case of error
         */
        boolean actorA() throws Throwable;

        /**
         * Sets field B to true, and returns field A contents
         * @return field A contents
         * @throws Throwable in case of error
         */
        boolean actorB() throws Throwable;
    }

    private static final class ResultData {
        public boolean a;
        public boolean b;

        public ResultData() {
        }

        public void reset() {
            a = false;
            b = false;
        }
    }

    private static class RaceStatistics {
        public int syncErrors;
        public int runSideBySide;
        public int A_outruns_B;
        public int B_outruns_A;

        public void reset() {
            syncErrors = 0;
            runSideBySide = 0;
            A_outruns_B = 0;
            B_outruns_A = 0;
        }
    }

    private Class<? extends Actor> actorClass;
    private final CyclicBarrier startBarrier = new CyclicBarrier(2); // We have two actors
    private Actor[] testData;
    private ResultData[] results;
    private final RaceStatistics raceStatistics = new RaceStatistics();

    public DekkerTest() {
    }

    /**
     * Sets actor class.
     * @param ac Actor class, which implements DekkerTest.Actor interface
     */
    public void setActorClass(Class<? extends Actor> ac) {
        actorClass = ac;
    }

    /**
     * Sets mayFail option. When set to true, synchronization failure is not reported as test failure (DekkerTest is used as a stress test).
     * @param mayFail if true, don't report sync failure as test failure, false otherwise
     */
    public void setMayFailOpt(boolean mayFail) {
        mayFailOpt = mayFail;
    }

    /**
     * Initializes test data, parses command-line options and checks Actor class
     */
    @Override
    public void initializeTest() {
        // Override values set by setActorClass() by command-line option, if any
        try {
            if (!actorClassNameOpt.isEmpty()) {
                actorClass = Class.forName(actorClassNameOpt).asSubclass(Actor.class);
            } else {
                if (actorClass == null) {
                    throw new RuntimeException("Test error: the actor class should be specified via command-line options or in the constructor");
                }
            }

        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Actor class '" + actorClassNameOpt + "' not found", e);
        } catch (ClassCastException e) {
            throw new RuntimeException("Test error: the actor class " + actorClass.getName() + " should implement " + Actor.class.getName() + " interface", e);
        }

        // Generate data
        int iterations = iterationsOpt * getStressOptions().getIterationsFactor();
        if (iterations <= 0) {
            throw new RuntimeException("Invalid number of iterations specified in -iterations and -stressIterationsFactor options: " + iterations);
        }

        results = new ResultData[iterations];
        for (int i = 0; i < results.length; ++i) {
            results[i] = new ResultData();
        }

        testData = new Actor[results.length];
        try {
            for (int i = 0; i < testData.length; ++i) {
                testData[i] = actorClass.newInstance();
            }
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException("Test error: can't instantiate class " + actorClass.getName(), e);
        }
    }

    /**
     * Resets the test data between test runs
     */
    @Override
    public void resetTest() {
        for (Actor a : testData) {
            a.reset();
        }
        for (ResultData d : results) {
            d.reset();
        }
    }

    private class RunnerA extends Thread {
        @Override
        public void run() {
            try {
                startBarrier.await();
                for (int i = 0; i < results.length; ++i) {
                    results[i].a = testData[i].actorA();
                }
            } catch (Throwable t) {
                markTestFailed("Exception in RunnerA", t);
            }
        }
    }

    private class RunnerB extends Thread {
        @Override
        public void run() {
            try {
                startBarrier.await();
                for (int i = 0; i < results.length; ++i) {
                    results[i].b = testData[i].actorB();
                }
            } catch (Throwable t) {
                markTestFailed("Exception in RunnerB", t);
            }
        }
    }

    @Override
    public boolean run() throws Throwable {
        RunnerA threadA = new RunnerA();
        threadA.start();

        RunnerB threadB = new RunnerB();
        threadB.start();

        threadA.join();
        threadB.join();

        if (isMarkedFailed())
            return false;

        analyzeResults();
        printResults();

        if (mayFailOpt) {
            return true;
        }

        return (raceStatistics.syncErrors == 0);
    }

    private void analyzeResults() {
        raceStatistics.reset();

        for (int i = 0; i < results.length; ++i) {
            boolean resultA = results[i].a;
            boolean resultB = results[i].b;

            if (resultA && resultB) {

                ++raceStatistics.runSideBySide;

            } else if (resultA && !resultB) {

                ++raceStatistics.A_outruns_B;

            } else if (!resultA && resultB) {

                ++raceStatistics.B_outruns_A;

            } else if (!resultA && !resultB) {

                ++raceStatistics.syncErrors;

            } else {

                throw new RuntimeException("Should not reach here");
            }
        }
    }

    private void printResults() {
        int logLevel = (raceStatistics.syncErrors != 0) ? TraceLevel.TRACE_IMPORTANT : TraceLevel.TRACE_NORMAL;

        Env.getLog().trace(logLevel, "\n"
                + "Late stores (sync. errors): " + raceStatistics.syncErrors + "\n"
                + "B outruns A               : " + raceStatistics.B_outruns_A + "\n"
                + "A outruns B               : " + raceStatistics.A_outruns_B + "\n"
                + "A and B run side by side  : " + raceStatistics.runSideBySide);
    }

    public static void main(String[] args) {
        MlvmTest.launch(args);
    }
}