#ifndef _LIB_COMPILE_LOCK_H
#define _LIB_COMPILE_LOCK_H

#include "arch/memory.h"
#include "arch/atomic.h"

#include <semaphore.h>
#include <stdbool.h>

struct vm_exec_env;

/*
 * Compilation lock status. The state graph for compilation lock
 * is as follows:
 *
 * (INITIAL) ---> (COMPILING) -----> (COMPILED_OK)
 *     ^             |        |
 *     |_____________|        \----> (COMPILED_ERRONOUS)
 *
 */
enum compile_lock_status {
	STATUS_INITIAL = 0,
	STATUS_COMPILING,

	/* below are final states. */
	STATUS_COMPILED_OK,
	STATUS_COMPILED_ERRONOUS,

	/* Pseudo state returned by compile_lock_enter() */
	STATUS_REENTER
};

struct compile_lock {
	/* compilation status */
	atomic_t status;

	/* Number of threads waiting for status update. */
	atomic_t nr_waiting;

	/* semaphore for threads waiting for status update. */
	sem_t wait_sem;

	/* True if lock is reentrant */
	bool reentrant;

	/* Compiling thread. Relevant only when reentrant. */
	struct vm_exec_env *compiling_ee;
};

int compile_lock_init(struct compile_lock *cl, bool reentrant);

/*
 * Enter compilation lock. Only one thread will be allowed.
 * If this method returns STATUS_COMPILING then this thread
 * is eligible for compilation and this thread only must
 * call compile_lock_leave() when compilation is done.
 *
 * If lock is reentrant and compilation is reentered then
 * returns STATUS_REENTER.
 */
enum compile_lock_status compile_lock_enter(struct compile_lock *cl);

/*
 * Finish compilation with given status. Only compiling thread
 * can call this.
 */
void compile_lock_leave(struct compile_lock *cl,
			enum compile_lock_status status);

static inline enum compile_lock_status
compile_lock_get_status(struct compile_lock *cl)
{
	return atomic_read(&cl->status);
}

static inline void compile_lock_set_status(struct compile_lock *cl,
					   enum compile_lock_status status)
{
	/* We need full barrier around status set. */
	smp_mb();
	atomic_set(&cl->status, status);
	smp_mb();
}

#endif
