/**
 * @file jvmpi_oprofile.cpp
 * JVMPI agent implementation to report jitted JVM code to OProfile
 *
 * @remark Copyright 2007 OProfile authors
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * @author Maynard Johnson
 *
 * Copyright IBM Corporation 2007
 *
 */

#include <iostream>
#include <map>
#include <string>
#include <cstring>
#include <stdexcept>
#include <cerrno>

extern "C" {
#include <stdint.h>
#include <jvmpi.h>
#include <opagent.h>
}

using namespace std;

static bool debug = false;
static op_agent_t agent_hdl;

class class_details {
public:
	string name;
	map<jmethodID, string> method_names;
	map<jmethodID, string> method_signatures;
};


static pthread_mutex_t class_map_mutex = PTHREAD_MUTEX_INITIALIZER;
static map <jobjectID, class_details> loaded_classes;

void class_load(JVMPI_Event * event)
{
	class_details cls;
	cls.name = event->u.class_load.class_name;
	JVMPI_Method * passed_methods = event->u.class_load.methods;
	for (int i = 0; i < event->u.class_load.num_methods;
	     i++, passed_methods++) {
		cls.method_names[passed_methods->method_id] =
			passed_methods->method_name;
		cls.method_signatures[passed_methods->method_id] =
			passed_methods->method_signature;
	}

	pthread_mutex_lock(&class_map_mutex);
	loaded_classes[event->u.class_load.class_id] = cls;
	pthread_mutex_unlock(&class_map_mutex);
}

void class_unload(JVMPI_Event * event)
{
	pthread_mutex_lock(&class_map_mutex);
	loaded_classes.erase(event->u.class_load.class_id);
	pthread_mutex_unlock(&class_map_mutex);
}

JVMPI_Interface * jvmpi;

void compiled_method_load(JVMPI_Event * event)
{
	jmethodID method = event->u.compiled_method_load.method_id;
	void * code_addr =  event->u.compiled_method_load.code_addr;
	jint code_size =  event->u.compiled_method_load.code_size;

	jvmpi->DisableGC();
	 /* Get the class of the method */
	jobjectID classID = jvmpi->GetMethodClass(method);
	jvmpi->EnableGC();

	pthread_mutex_lock(&class_map_mutex);
	map<jobjectID, class_details>::iterator iter =
		loaded_classes.find(classID);
	if (iter == loaded_classes.end()) {
		throw runtime_error("Error: Cannot find class for compiled"
				    " method\n");
	}

	class_details cls_info = ((class_details)iter->second);
	map<jmethodID, string>::iterator method_it =
		cls_info.method_names.find(method);
	if (method_it == cls_info.method_names.end()) {
		throw runtime_error("Error: Cannot find method name for "
				    "compiled method\n");
	}
	char const * method_name = ((string)method_it->second).c_str();
	method_it = cls_info.method_signatures.find(method);
	if (method_it == cls_info.method_signatures.end()) {
		throw runtime_error("Error: Cannot find method signature "
				    "for compiled method\n");
	}
	char const * method_signature = ((string)method_it->second).c_str();

	string const class_signature = "L" + cls_info.name + ";";
	pthread_mutex_unlock(&class_map_mutex);

	if (debug) {
		cerr << "load: class=" << class_signature << ", method ="
		     << method_name << ", method signature = "
		     << method_signature
		     << ", addr=" << code_addr << ", size="
		     << code_size << endl;
	}

	// produce a symbol name out of class name and method name
	int cnt = strlen(method_name) + strlen(class_signature.c_str()) +
		strlen(method_signature) + 2;
	char buf[cnt];
	strncpy(buf, class_signature.c_str(), cnt - 1);
	strncat(buf, method_name, cnt - strlen(buf) - 1);
	strncat(buf, method_signature, cnt - strlen(buf) - 1);
	if (op_write_native_code(agent_hdl, buf, (uint64_t) code_addr,
				 code_addr, code_size))
		perror("Error: op_write_native_code()");
}

void compiled_method_unload(JVMPI_Event * event)
{
	void * code_addr =  event->u.compiled_method_load.code_addr;
	if (debug) {
		cerr << "unload: addr="
			<< (unsigned long long) (uintptr_t) code_addr
			<< endl;
	}
	if (op_unload_native_code(agent_hdl, (uint64_t)code_addr))
		perror("Error: op_unload_native_code()");
}

void jvm_shutdown(JVMPI_Event * event)
{
	/* Checking event here is not really necessary; added only to silence
	 * the 'unused parameter' compiler warning.
	 */
	if (event)
		if (op_close_agent(agent_hdl))
			perror("Error: op_close_agent()");
}


void jvm_notify_event(JVMPI_Event * event)
{
	switch (event->event_type) {
	case JVMPI_EVENT_COMPILED_METHOD_LOAD:
		compiled_method_load(event);
		break;
	case JVMPI_EVENT_COMPILED_METHOD_UNLOAD:
		compiled_method_unload(event);
		break;
	case JVMPI_EVENT_JVM_SHUT_DOWN:
		jvm_shutdown(event);
		break;
	case JVMPI_EVENT_CLASS_LOAD:
		class_load(event);
		break;
	case JVMPI_EVENT_CLASS_UNLOAD:
		class_unload(event);
		break;
	default:
		break;
	}
}

extern "C" {
JNIEXPORT jint JNICALL JVM_OnLoad(JavaVM * jvm, char * options,
                                  void * reserved)
{
	int err;

	if (options && strstr(options, "version")) {
		cerr << "jvmpi_oprofile: current libopagent version "
				 << op_major_version() << "." << op_minor_version()
				 << endl;
		throw runtime_error("Exiting");
	}

	if (options && strstr(options, "debug=yes")) {
		debug = true;
		/* Add something braindead to silence the 'unused parameter'
		 * compiler warning.
		 */
		if (reserved)
			debug = true;
	}

	if (debug)
		cerr << "jvmpi_oprofile: agent activated" << endl;

	agent_hdl = op_open_agent();
	if (!agent_hdl) {
		perror("Error: op_open_agent()");
		throw runtime_error("Exiting");
	}

	/* The union below is used to avoid the 'dereferencing type-punned
	 * pointer will break strict-aliasing rules' compiler warning on the
	 * GetEnv call.
	 */
	union {
		JVMPI_Interface * jvmpi_ifc;
		void * jvmpi_ifc_ptr;
	} jvmpi_GetEnv_arg;
	err = jvm->GetEnv(&jvmpi_GetEnv_arg.jvmpi_ifc_ptr, JVMPI_VERSION_1);
	if (err < 0) {
		cerr << "GetEnv failed with rc=" << err << endl;
		throw runtime_error("Exiting");
	}
	jvmpi = jvmpi_GetEnv_arg.jvmpi_ifc;
	jvmpi->EnableEvent(JVMPI_EVENT_COMPILED_METHOD_LOAD, NULL);
	jvmpi->EnableEvent(JVMPI_EVENT_COMPILED_METHOD_UNLOAD, NULL);
	jvmpi->EnableEvent(JVMPI_EVENT_JVM_SHUT_DOWN, NULL);
	jvmpi->EnableEvent(JVMPI_EVENT_CLASS_LOAD, NULL);

	jvmpi->NotifyEvent = jvm_notify_event;
	return JNI_OK;
}
}