blob: 75e43d6e789cfc8289a2bcc0a4abf8e5e0404d93 [file] [log] [blame]
/*
* Copyright (c) 2009 Vegard Nossum
*
* This file is released under the GPL version 2 with the following
* clarification and special exception:
*
* Linking this library statically or dynamically with other modules is
* making a combined work based on this library. Thus, the terms and
* conditions of the GNU General Public License cover the whole
* combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under terms
* of your choice, provided that you also meet, for each linked independent
* module, the terms and conditions of the license of that module. An
* independent module is a module which is not derived from or based on
* this library. If you modify this library, you may extend this exception
* to your version of the library, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
* Please refer to the file LICENSE for details.
*/
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "lib/array.h"
#include "vm/classloader.h"
#include "vm/backtrace.h"
#include "vm/class.h"
#include "vm/gc.h"
#include "vm/object.h"
#include "vm/itable.h"
#include "vm/method.h"
#include "vm/trace.h"
#include "arch/itable.h"
bool opt_trace_itable;
static uint32_t itable_hash_string(const char *str)
{
/* Stolen shamelessly from
* http://en.wikipedia.org/wiki/Jenkins_hash_function */
uint32_t hash = 0;
for (unsigned int i = 0, n = strlen(str); i < n; ++i) {
hash += str[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
static uint32_t itable_hash_combine(uint32_t a, uint32_t b)
{
/* The itable can't be longer than 256 entries anyway, so we can
* compress those 32 bits from each argument into a combined 8 bit
* result. */
return ((a >> 24) ^ (a >> 16) ^ (a >> 8) ^ a)
^ ((b >> 24) ^ (b >> 16) ^ (b >> 8) ^ b);
}
unsigned int itable_hash(struct vm_method *vmm)
{
#if 0
/* Very simple hash based on the middle bits of the method pointer.
* This is probably not sufficient because the result depends
* implicitly on the way the memory allocator works. */
return ((unsigned long) vmm / 8) % VM_ITABLE_SIZE;
#endif
return itable_hash_combine(itable_hash_string(vmm->name),
itable_hash_string(vmm->type)) % VM_ITABLE_SIZE;
}
static int itable_add_entries(struct vm_class *vmc, struct list_head *itable)
{
/* Note about error handling: We don't actually clean up on error,
* but assume that the caller will free the entries that have been
* added to the itable so far. That really simplifies this whole
* function. */
if (vm_class_is_interface(vmc)) {
for (unsigned int i = 0; i < vmc->nr_methods; ++i) {
struct vm_method *vmm = &vmc->methods[i];
if (!vm_method_is_abstract(vmm))
continue;
unsigned int bucket = itable_hash(vmm);
struct itable_entry *entry = malloc(sizeof *entry);
if (!entry)
return -1;
entry->i_method = vmm;
list_add(&entry->node, &itable[bucket]);
}
}
for (unsigned int i = 0; i < vmc->nr_interfaces; ++i) {
int ret = itable_add_entries(vmc->interfaces[i], itable);
if (ret)
return ret;
}
/* Yay for tail recursion. */
if (vmc->super)
return itable_add_entries(vmc->super, itable);
return 0;
}
static int itable_entry_compare(const void *a, const void *b)
{
const struct itable_entry *ae = *(const struct itable_entry **) a;
const struct itable_entry *be = *(const struct itable_entry **) b;
if (ae->i_method < be->i_method)
return -1;
if (ae->i_method > be->i_method)
return 1;
return 0;
}
static void *itable_create_conflict_resolver(struct vm_class *vmc,
struct list_head *methods)
{
/* No methods at this index -- return something that will choke the
* caller. */
if (list_is_empty(methods))
return &itable_resolver_stub_error;
/* If it's not empty, and the first element is the same as the last,
* there can only be one element in the list. If so, we can put the
* trampoline of that method directly into the itable. */
if (list_first(methods) == list_last(methods)) {
struct itable_entry *entry = list_first_entry(methods,
struct itable_entry, node);
return vm_method_call_ptr(entry->c_method);
}
struct array sorted_table;
array_init(&sorted_table);
array_resize(&sorted_table, 64);
struct itable_entry *entry;
list_for_each_entry(entry, methods, node)
array_append(&sorted_table, entry);
array_qsort(&sorted_table, &itable_entry_compare);
array_unique(&sorted_table, &itable_entry_compare);
void *ret = emit_itable_resolver_stub(vmc,
(struct itable_entry **) sorted_table.ptr, sorted_table.size);
array_destroy(&sorted_table);
return ret;
}
static void trace_itable(struct vm_class *vmc, struct list_head *itable)
{
bool empty = true;
for (unsigned int i = 0; i < VM_ITABLE_SIZE; ++i) {
if (list_is_empty(&itable[i]))
continue;
empty = false;
break;
}
if (empty)
return;
trace_printf("trace itable (duplicates included): %s\n", vmc->name);
for (unsigned int i = 0; i < VM_ITABLE_SIZE; ++i) {
if (list_is_empty(&itable[i]))
continue;
trace_printf(" %d:\n", i);
struct itable_entry *entry;
list_for_each_entry(entry, &itable[i], node) {
struct vm_method *i_vmm = entry->i_method;
struct vm_method *c_vmm = entry->c_method;
assert(vm_class_is_interface(i_vmm->class));
assert(!vm_class_is_interface(c_vmm->class));
assert(!strcmp(c_vmm->name, i_vmm->name));
assert(!strcmp(c_vmm->type, i_vmm->type));
trace_printf(" * %s.%s%s -> %s\n",
i_vmm->class->name, i_vmm->name, i_vmm->type,
c_vmm->class->name);
}
}
trace_printf("\n");
trace_flush();
}
int vm_itable_setup(struct vm_class *vmc)
{
/* We need a temporary array of lists for storing multiple results.
* The final itable (the one that gets stored in the class struct
* itself) will only have one method per slot. */
struct list_head *itable = malloc(sizeof(*itable) * VM_ITABLE_SIZE);
if (!itable)
return -ENOMEM;
for (unsigned int i = 0; i < VM_ITABLE_SIZE; ++i)
INIT_LIST_HEAD(&itable[i]);
itable_add_entries(vmc, itable);
for (unsigned int i = 0; i < VM_ITABLE_SIZE; ++i) {
struct itable_entry *entry;
list_for_each_entry(entry, &itable[i], node) {
/* Specification */
struct vm_method *i_vmm = entry->i_method;
/* Implementation */
struct vm_method *c_vmm
= vm_class_get_method_recursive(vmc,
i_vmm->name, i_vmm->type);
assert(c_vmm);
entry->c_method = c_vmm;
}
}
if (opt_trace_itable)
trace_itable(vmc, itable);
for (unsigned int i = 0; i < VM_ITABLE_SIZE; ++i) {
vmc->itable[i]
= itable_create_conflict_resolver(vmc, &itable[i]);
}
/* Free the temporary itable */
for (unsigned int i = 0; i < VM_ITABLE_SIZE; ++i) {
struct itable_entry *entry, *tmp;
list_for_each_entry_safe(entry, tmp, &itable[i], node)
free(entry);
}
free(itable);
return 0;
}