#include "jit/cu-mapping.h"
#include "jit/compiler.h"
#include "arch/encode.h"
#include "lib/list.h"
#include "vm/class.h"

#include <pthread.h>

static inline bool is_rex_prefix(unsigned char opc)
{
#ifdef CONFIG_X86_64
	return (opc & 0xf0) == REX;
#else
	return false;
#endif
}

static inline bool is_sse_insn(unsigned char *opc)
{
	return (opc[0] & 0xfe) == 0xf2 && opc[1] == 0x0f;
}

/*
 * This fixes relative calls generated by EXPR_INVOKE.
 *
 * Please note that this code does not care about icache flushing in SMP
 * environment. This may lead to a GPF when one CPU modifies code already
 * prefetched by another CPU on some bogus Intel CPUs (see section 7.1.3 of
 * "Intel 64 and IA-32 Architectures Software Developers Manual Volume 3A").
 *
 * It is required for other CPUs to execute a serializing instruction (to flush
 * instruction cache) between modification and execution of new instruction. To
 * achieve this, we could suspend all threads before patching, and force them
 * to execute flush_icache() on resume.
 */
void fixup_direct_calls(struct jit_trampoline *t, unsigned long target)
{
	struct fixup_site *this, *next;

	if (list_is_empty(&t->fixup_site_list))
		return;

	pthread_mutex_lock(&t->mutex);

	list_for_each_entry_safe(this, next, &t->fixup_site_list, list_node) {
		unsigned char *site_addr;
		uint32_t new_target;

		site_addr = fixup_site_addr(this);
		new_target = target - ((unsigned long) site_addr + X86_CALL_INSN_SIZE);
		cpu_write_u32(site_addr+1, new_target);

		list_del(&this->list_node);
		free_fixup_site(this);
	}

	pthread_mutex_unlock(&t->mutex);
}

static void do_fixup_static(void *site_addr, int skip_count, void *new_target)
{
	void *p = site_addr + skip_count;

#ifdef CONFIG_X86_64
	/* We need RIP-relative addressing. */
	cpu_write_u32(p, new_target - site_addr - (skip_count + 4));
#else
	cpu_write_u32(p, (unsigned long) new_target);
#endif
}

void fixup_static(struct vm_class *vmc)
{
	struct static_fixup_site *this, *next;

	pthread_mutex_lock(&vmc->mutex);

	list_for_each_entry_safe(this, next, &vmc->static_fixup_site_list, vmc_node) {
		struct vm_field *vmf = this->vmf;
		unsigned char *mach_insn;
		int skip_count = 0;
		void *new_target;

		new_target	= vmc->static_values + vmf->offset;
		mach_insn	= buffer_ptr(this->cu->objcode) + this->mach_offset;

		/* Does the instruction begin with a REX prefix? */
		if (is_rex_prefix(*mach_insn))
			skip_count += 1;

		/* Is it an SSE instruction? */
		if (is_sse_insn(mach_insn + skip_count))
			skip_count += 4;
		else
			skip_count += 2;

		do_fixup_static(mach_insn, skip_count, new_target);

		list_del(&this->vmc_node);

		pthread_mutex_lock(&this->cu->mutex);
		list_del(&this->cu_node);
		pthread_mutex_unlock(&this->cu->mutex);

		free(this);
	}

	pthread_mutex_unlock(&vmc->mutex);
}

int fixup_static_at(unsigned long addr)
{
	struct compilation_unit *cu;
	struct static_fixup_site *this, *t;

	cu = jit_lookup_cu(addr);
	assert(cu);

	pthread_mutex_lock(&cu->mutex);

	list_for_each_entry_safe(this, t, &cu->static_fixup_site_list, cu_node) {
		void *site_addr;

		site_addr = buffer_ptr(cu->objcode) + this->mach_offset;

		if ((unsigned long) site_addr == addr) {
			struct vm_class *vmc = this->vmf->class;
			int ret;

			pthread_mutex_unlock(&cu->mutex);

			/*
			 * Note: After this call, we can no longer access
			 * "this" because it may have been deleted already
			 * (from insite the class initializer of "vmc".
			 */
			ret = vm_class_ensure_init(vmc);
			if (ret)
				return ret;

			fixup_static(vmc);

			return 0;
		}
	}

	pthread_mutex_unlock(&cu->mutex);

	return 0;
}

