/* VM library: specializer.
Copyright (C) 2016, 2017, 2018, 2019, 2020 Luca Saiu
Written by Luca Saiu
This file is part of Jitter.
Jitter is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jitter 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 for more details.
You should have received a copy of the GNU General Public License
along with Jitter. If not, see . */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* For executable deallocation. */
/* Executable routines: initialization and finalization.
* ************************************************************************** */
/* Initialize the pointed executable routine from the pointed non-executable
routine, without filling in the actual routine data.
Fail fatally if the routine already has another executable routine. */
static void
jitter_initialize_executable_routine (struct jitter_executable_routine *er,
struct jitter_mutable_routine *r)
{
//fprintf (stderr, "[Making an executable routine at %p]\n", er);
/* Fail if this executable routine is not the first for the routine. */
if (r->executable_routine != NULL)
jitter_fatal ("cannot generate an executable routine from %p twice", r);
/* Link the two routines together thru pointers, so that destroying one can
update the other. */
r->executable_routine = er;
er->routine = r;
er->vm = r->vm;
/* Initialize the other fields, where we already have enough information. */
er->reference_count = 1;
er->slow_register_per_class_no = r->slow_register_per_class_no;
/* The remaining fields are still uninitialized. */
}
void
jitter_destroy_executable_routine (struct jitter_executable_routine *er)
{
//fprintf (stderr, "[Destroying executable routine at %p]\n", er);
/* If the non-executable routine which was translated into *er still exists
then unlink this. */
struct jitter_mutable_routine *r = er->routine;
if (r != NULL)
r->executable_routine = NULL;
/* Check that the reference count is exactly one. It is sensible to require
that when this function is called by the user the reference count be
exactly one; when called by jitter_unpin_executable_routine this field is
automatically set to 1 before arriving here, just in order to pass this
check. */
if (er->reference_count != 1)
jitter_fatal ("destroying executable routine with reference count %lu",
er->reference_count);
/* Destroy heap-allocated fields reachable from the compiled routine. */
#if (defined(JITTER_DISPATCH_SWITCH) \
|| defined(JITTER_DISPATCH_DIRECT_THREADING))
free (er->specialized_program);
#elif (defined(JITTER_DISPATCH_MINIMAL_THREADING) \
|| defined(JITTER_DISPATCH_NO_THREADING))
jitter_executable_deallocate (er->native_code);
#else
# error "unknown dispatch: this should not happen"
#endif /* dispatch */
/* Release memory for the struct itself. */
free (er);
}
/* Reference counting.
* ************************************************************************** */
void
jitter_pin_executable_routine (struct jitter_executable_routine *er)
{
/* Just increment the field. This operation is easy and fast, as it never
involves construction or destruction. */
er->reference_count ++;
}
void
jitter_unpin_executable_routine (struct jitter_executable_routine *er)
{
/* Decrement the field. If the new value does not reach zero then we are
done. */
er->reference_count --;
if (er->reference_count > 0)
return;
/* If we arrived here the reference count has reached zero. */
/* Destroy the mutable companion, if it still exists. */
struct jitter_mutable_routine *r = er->routine;
if (r != NULL)
jitter_destroy_mutable_routine (r);
/* Destroy the executable routine itself. Temporarily set the reference count
field to one, as the executable destruction function expects, then call the
executable destruction function. */
er->reference_count = 1;
jitter_destroy_executable_routine (er);
}
/* Specialization.
* ************************************************************************** */
void
jitter_add_specialized_instruction_opcode
(struct jitter_mutable_routine *p,
/* This is actually an enum vmprefix_specialized_instruction_opcode , but
the type is VM-dependent. */
jitter_uint specialized_opcode)
{
// FIXME: this comment is probably obsolete.
/* Without replication p->specialized_program holds direct-threaded code (each
VM thread followed by its zero or more residual arguments); without
replication p->specialized_program does *not* hold threads, but only
residual arguments.
In either case we push a new element onto p->replicated_blocks , which is
useful in one case at specialization time, and in the other only for
disassembling. */
struct jitter_replicated_block replicated_block = {specialized_opcode, NULL, 0};
jitter_dynamic_buffer_push (& p->replicated_blocks,
& replicated_block,
sizeof (struct jitter_replicated_block));
#ifndef JITTER_REPLICATE
# if defined(JITTER_DISPATCH_SWITCH)
union jitter_word w = {.fixnum = specialized_opcode};
# elif defined(JITTER_DISPATCH_DIRECT_THREADING)
union jitter_word w = {.thread = p->vm->threads [specialized_opcode]};
# else
# error "replication enabled, but not switch nor direct-threading"
# endif// #if defined(JITTER_DISPATCH_SWITCH)...
jitter_dynamic_buffer_push (& p->specialized_program, & w, sizeof (w));
#endif // #ifndef JITTER_REPLICATE
}
void
jitter_add_specialized_instruction_literal (struct jitter_mutable_routine *p,
jitter_uint literal)
{
// fprintf (stderr, "Adding specialized instruction literal %i\n", (int)literal);
// FIXME: this will need generalization once more literal kinds are supported.
union jitter_word w = {.ufixnum = literal};
jitter_dynamic_buffer_push (& p->specialized_program, & w, sizeof (w));
}
void
jitter_add_specialized_instruction_label_index (struct jitter_mutable_routine *p,
jitter_label_as_index
unspecialized_instruction_index)
{
// fprintf (stderr, "Adding specialized instruction label_index %i\n", (int)unspecialized_instruction_index);
jitter_int next_word_index
= jitter_dynamic_buffer_size (& p->specialized_program)
/ sizeof (jitter_int);
union jitter_word w
= {.ufixnum = unspecialized_instruction_index};
jitter_dynamic_buffer_push (& p->specialized_program, & w, sizeof (w));
jitter_dynamic_buffer_push (& p->specialized_label_indices,
& next_word_index, sizeof (jitter_int));
}
static void
jitter_backpatch_labels_in_specialized_routine (struct jitter_mutable_routine *p)
{
union jitter_word *specialized_program
= jitter_dynamic_buffer_to_pointer (& p->specialized_program);
const jitter_int *specialized_label_indices
= jitter_dynamic_buffer_to_pointer (& p->specialized_label_indices);
const jitter_int * const instruction_index_to_specialized_instruction_offset
= p->instruction_index_to_specialized_instruction_offset;
const int specialized_label_indices_no
= jitter_dynamic_buffer_size (& p->specialized_label_indices)
/ sizeof (jitter_int);
int i;
for (i = 0; i < specialized_label_indices_no; i ++)
{
union jitter_word *argument
= specialized_program + specialized_label_indices[i];
argument->pointer
= (union jitter_word *)
((char*)specialized_program
+ instruction_index_to_specialized_instruction_offset
[argument->ufixnum]);
}
}
/* Add implicit instructions at the end of an unspecialized program. */
static void
jitter_mutable_routine_add_epilog (struct jitter_mutable_routine *p)
{
/* Add the final instructions which are supposed to close every VM routine.
Having each label, including the ones at the very end of the program when
they exist, associated to an actual unspecialized instruction makes
replication easier. */
if (p->options.add_final_exitvm)
jitter_mutable_routine_append_meta_instruction
(p, p->vm->exitvm_meta_instruction);
else
jitter_mutable_routine_append_meta_instruction
(p, p->vm->unreachable_meta_instruction);
}
/* Making an executable routine.
* ************************************************************************** */
struct jitter_executable_routine*
jitter_make_executable_routine (struct jitter_mutable_routine *p)
{
if (p->stage != jitter_routine_stage_unspecialized)
jitter_fatal ("specializing non-unspecialized program");
if (p->expected_parameter_no != 0)
jitter_fatal ("specializing program with last instruction incomplete");
if (p->native_code != NULL)
jitter_fatal ("specializing program with native code already defined");
/* Add epilog instructions. This way we can be sure that the program
ends with an "exitvm" or "unreachable" instruction. */
jitter_mutable_routine_add_epilog (p);
/* Resolve label arguments in unspecialized instruction parameters. */
jitter_mutable_routine_resolve_labels (p);
/* Now label arguments refer unspecialized instruction indices. */
/* Compute jump targets. */
assert (p->jump_targets == NULL);
p->jump_targets = jitter_mutable_routine_jump_targets (p);
/* Now that we know how many instructions there are we can allocate
p->instruction_index_to_specialized_instruction_offset once and for all.
Its content will still be uninitialized. */
const int instruction_no = jitter_mutable_routine_instruction_no (p);
assert (p->instruction_index_to_specialized_instruction_offset == NULL);
p->instruction_index_to_specialized_instruction_offset
= jitter_xmalloc (sizeof (jitter_int) * instruction_no);
#ifdef JITTER_REPLICATE
/* Whenever some specialized instruction is the first one, or is reachable by
a jump, we need to insert a BEGINBASICBLOCK specialized instruction
before its specialization. This is needed for VM branches in minimal-threaded
code: there are no real threads in the thread array, only residual arguments;
however for every jump target we have to introduce a thread as the residual
argument of a BEGINBASICBLOCK special specialized instruction, which does
nothing but advancing the thread pointer past the argument.
We need this special case out of the loop to support empty programs, whose
specialization must still begin by a BEGINBASICBLOCK. Dealing with
this case within a loop is messy, because if the first instruction is
also a jump target then we need to set
p->instruction_index_to_specialized_instruction_offset [0]
before adding BEGINBASICBLOCK ; except that
p->instruction_index_to_specialized_instruction_offset [0]
it out of bounds if the program is empty.
In the no-threading case BEGINBASICBLOCK specialized instructions are
redundant but they cost nothing at run time, since they expand to zero
assembly instructions. FIXME: can I just not generate them in the
no-threading case by changing CPP conditionals? Test. */
if (instruction_no == 0)
jitter_insert_beginbasicblock (p);
#endif // #ifdef JITTER_REPLICATE
/* Specialize instructions, filling
p->instruction_index_to_specialized_instruction_offset at the same time. */
const struct jitter_instruction **instructions
= (const struct jitter_instruction **)
jitter_dynamic_buffer_to_pointer (& p->instructions);
int (* const specialize_instruction) (struct jitter_mutable_routine *p,
const struct jitter_instruction *ins)
= p->vm->specialize_instruction;
int instruction_index = 0;
while (instruction_index < instruction_no)
{
const struct jitter_instruction *next_instruction
= instructions [instruction_index];
/* The next specialized instruction will begin where the (current) free
space in specialized program begins. Notice that this may leave
uninitialized holes in
p->instruction_index_to_specialized_instruction_offset , at the indices
corresponding to some instructions specialized into
superinstructions. Also notice that the next generated native instructions
might belong to a translation of BEGINBASICBLOCK . */
p->instruction_index_to_specialized_instruction_offset [instruction_index]
= jitter_dynamic_buffer_size (& p->specialized_program);
#ifdef JITTER_REPLICATE
/* See the comment above about BEGINBASICBLOCK . */
if (instruction_index == 0 || p->jump_targets [instruction_index])
jitter_insert_beginbasicblock (p);
#endif // #ifdef JITTER_REPLICATE
/* Specialize the next instruction, obtaining as result the number of
unspecialized instructions covered by the one specialized instruction
they are translated into. This adds as many words as needed to
p->specialized_program . */
instruction_index += specialize_instruction (p, next_instruction);
}
/* Notice that p->instruction_index_to_specialized_instruction_offset is
accessed only when needed to patch labels, and it's harmless to leave its
content undefined in places where no labels are involved. */
/* Now that p->instruction_index_to_specialized_instruction_offset is filled
we have enough information to resolve label literals. */
jitter_backpatch_labels_in_specialized_routine (p);
/* The program is now specialized. FIXME: shall I free p->jump_targets
and set it to NULL now? */
p->stage = jitter_routine_stage_specialized;
#ifdef JITTER_REPLICATE
/* If replication is enabled then build the native code; this will change the
program stage again. */
jitter_replicate_program (p);
#endif // #ifdef JITTER_REPLICATE
/* Make an executable routine containing what we need to actually run the
code. */
struct jitter_executable_routine *res
= jitter_xmalloc (sizeof (struct jitter_executable_routine));
jitter_initialize_executable_routine (res, p);
/* Transfer the relevant fields from the non-executable routine to the
executable routine, invalidating the originals to avoid double freeing. */
/* Set the specialized_program field, where it exists. */
#if (defined(JITTER_DISPATCH_SWITCH) \
|| defined(JITTER_DISPATCH_DIRECT_THREADING) \
|| defined(JITTER_DISPATCH_MINIMAL_THREADING))
res->specialized_program
= jitter_dynamic_buffer_extract (& p->specialized_program);
#elif defined(JITTER_DISPATCH_NO_THREADING)
/* Nothing. */
#else
# error "unknown dispatch: this should not happen"
#endif /* dispatch */
/* Set the native_code and native_code_size fields, where they exist. */
#if (defined(JITTER_DISPATCH_SWITCH) \
|| defined(JITTER_DISPATCH_DIRECT_THREADING)) \
/* Nothing. */
#elif (defined(JITTER_DISPATCH_MINIMAL_THREADING) \
|| defined(JITTER_DISPATCH_NO_THREADING))
res->native_code = p->native_code;
res->native_code_size = p->native_code_size;
p->native_code = NULL;
#else
# error "unknown dispatch: this should not happen"
#endif /* dispatch */
/* Return a pointer to the executable routine. */
return res;
}