Skip to content

Commit

Permalink
add a real README
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewfl committed Nov 29, 2016
1 parent ad2b7b0 commit a6a04da
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 27 deletions.
20 changes: 0 additions & 20 deletions README

This file was deleted.

128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Redmagic

Redmagic is a tracing JIT for interpreters that are written in C/C++. It operates by generating traces from observing x86_64 instruction execution and transparently switching between generated code and the interpreter loop.

### Tracing introduction

Tracing is a JIT technique which has been previously implemented in PyPy, Tracermonkey and other JITs. It works by identifying loops in a program a these loops will represent the "hot spots" of the program as they are going to be executed a number of times during the programs life time. (eg a finite sized program without loops must terminate, loops are the only thing that can make the program's life extend longer)

To identify loops, it will track all *backwards jumps* at the bytecode level as this will indicate that the interpreted language is inside a loop. The JIT will leave the loop when the backwards jump's condition is not meet and it will leave any optimized loop code when the loop's condition *fallsthrough*.



## API
See [src/redmagic.h](src/redmagic.h) for the JIT's public interface.

A small sample interpreter loop with redmagic integration is demoed in [src/main.cc](src/main.cc).

A full integration is show with [cPython](#python-int)

#### Basic API

`redmagic_start(void)`: Initializes redmagic's internal data structures, to be called inside main or before any other methods are called.

`redmagic_backwards_branch(void *id)`: Indicates that the interpreter is taking a backwards branch which means that the user's program is starting a loop. The argument `void *id` is an opaque reference which is used to reference a given loop inside the user's program. This does not need to be a valid pointer to memory (can cast an int etc) but if this pointer is not unique to the loop referenced then this can cause erroneous behavior.

`redmagic_fellthrough_branch(void *id)`: Exits the JITed code. The `id` **must** match the `id` that was passed to `redmagic_backwards_branch` otherwise this operation will be treated as a nop.

#### Disable tracing API

`redmagic_do_not_trace_function(void *function_pointer)`: There will often exits a number of methods which do not make since to inline as inlining them will not improve the performance of the user's program. (Eg `malloc` will have a lot of internal branching and inlining it will not improve performance).

`redmagic_ensure_not_traced(void)`: There may be methods such as error handlers which do not make since to trace. By placing this method call at the top of such methods, this will abort any currently running trace and ensure that the program is returned to executing normally.

`redmagic_disable_branch(void *id)`: If there is some loop which the JIT should not attempt to optimize, then passing that loop's `id` to this method will prevent any further attempts to optimize it.


##### Temp disable
Sometimes there are blocks such as I/O where it does not make since to perform tracing. One option would be to extract all I/O operations into different methods and register these methods with `redmagic_do_not_trace_function`, alternately we can use Temp disable to create a block of code where tracing does not take place inside of any method.

```
// normal traced code
i = i + 2;
redmagic_temp_disable();
// this is not traced
printf("%l\n", i);
// ...
redmagic_temp_enable();
// resume the optimized traced code
i = i + 3;
```

#### Frames
In the case of recursion, it is possible that a loop ends up calling itself, which can make tracking its entry and exits difficult if it has the same `id`. To counter this, branchable frames inform redmagic that a new stack frame is being pushed onto the stack. This way, a loop will only "exit" if it receives a `redmagic_fellthrough_branch` at the correct stack depth.

```
// Push stack frame
unsigned long redmagic_frame_tracking;
redmagic_begin_branchable_frame(&redmagic_frame_tracking);
// exit stack frame
redmagic_end_branchable_frame(&redmagic_frame_tracking);
```

#### Merge blocks
There might be small branching operations which we want to inline, but do not want to reconstruct a complete trace of the user's program loop. An example of this could be reference counting, where the operation of decrementing and checking a reference could end up generating two difference traces due to the difference in how a program's branch behaves.



```
// small operation that we want to inline in our trace but do not
// want the direction of this branch cause two different traces
// to be generated
redmagic_begin_merge_block();
if(--(obj->reference_count) == 0) {
delete_obj(obj);
}
redmagic_end_merge_block();
// at this point, regardless of how any branches behaved in the
// previous block, they will merge back to the same generated trace
```


## Build instructions

build instructions:

pacman -S strace python2
git submodules update --init --recursive
./make release

build python:

git clone https://github.com/matthewfl/cpython.git
cd cpython
git checkout redmagic3
ln -s ../redmagic/src/redmagic.h .
ln -s ../redmagic/build/libredmagic.so.* .
./configure --with-pydebug --prefix=/usr
make


### Python integration <a name='python-int'></a>

There exists a partially complete implementation for cPython 2.7 [here](https://github.com/matthewfl/cpython/tree/redmagic3) ([diff](https://github.com/matthewfl/cpython/compare/116b7d5350970fbe330f4bc8e6985f01142cf8dd...matthewfl:redmagic3?expand=1)).
Compared to most other implementations of JITs for python, once can see that the modification to cPython is fairly small with only a handful of lines inside of the interpreter loop being annotated and


## Licence
license: LGPLv3


# TODO
### Bugs
* Redmagic can currently boot the standard python repl and work with a number of simple programs. It can also start IPython repl, however it will usually crash after a few key presses.

### Performance issues
* When evaluating the traced program the system has to decode *all* instructions to determine if there is something that needs to be rewritten. This includes all jumps and references to register `%rip` which will be changed when executing under the generated code. Any instruction that is left unchanged will be simply `memcpy`ed over to the generation buffer while all other instructions will have to be further processed. Further processing includes running the program up to the instruction in question, stopping the program, capturing the value of all of the program's registers, resuming redmagic's tracing mechanism, determining how the instruction in question would behave in this case, and finally generating appropriate code for a given instruction which might include trampolines to resume tracing in the case that a conditional check fails. Given that a typical program can be expected to perform a branch every 15-100 instructions, there is an immense overhead to the tracing operation.
* Given the overhead of generating a trace, we should only trace methods for which we can justify performing a very slow tracing operation to avoid causing a program to degrade in performance. ATM, redmagic only uses a counter to determine which loops should be traced, additional information such as estimated number of instructions involved in a loop should be incorporated.

### Optimizations
* ATM, the JIT does not perform any optimizations outside of replacing branches with a trace. There are still a number of peephole optimizations which should be implemented to identify values which will not changed as a loop executes.
* Register aliasing should be used in place of accessing memory. ATM the JIT does not identify when statements are accessing similar locations in memory which means that registers are being used suboptimally.
* Memory optimizations: Inside of a trace, the system should identify items which are allocated and then shortly deallocated (temporary values etc).


These optimizations are necessary as languages such as python have a high overhead with data structures and pointer dereferences which can have a higher overhead then the dispatch inside of the interpreter loop.
7 changes: 6 additions & 1 deletion src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
//#endif
#define CONF_TIMER_DELAY_MS CONF_BUILD_TOGGLE(10000, 60000)

// Takes method which is num milliseconds, num instructions
#define CONF_TRACE_INSTRUCTION_LIMIT_PER_TIME(X) \
X(1000, 100000) \
X(60000, 10000000)

#define CONF_ESTIMATE_INSTRUCTIONS

// instead of taking a trace all the way to the bottom of the loop, attempt to merge back at the ret inside of a method
Expand All @@ -60,6 +65,6 @@
//#define CONF_CHECK_RET_ADDRESS

// check the instruction pointer when performing a merge close block
#define CONF_CHECK_MERGE_RIP
//#define CONF_CHECK_MERGE_RIP

#endif // REDMAGIC_CONFIG_H_
9 changes: 9 additions & 0 deletions src/jit_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ namespace redmagic {
// tbb::concurrent_unordered_map<uint64_t, branch_info> branches;
// tbb::concurrent_unordered_set<uint64_t> no_trace_methods;

#ifdef CONF_USE_TIMERS
#define TRACE_SPEED_LIMIT(time, cnt) \
timespec speed_limit_time ## time = {0, 0} ; \
long speed_limit_last_icount ## time = 0;

CONF_TRACE_INSTRUCTION_LIMIT_PER_TIME(TRACE_SPEED_LIMIT);
#undef TRACE_SPEED_LIMIT
#endif

friend class Tracer;
};

Expand Down
21 changes: 17 additions & 4 deletions src/manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ namespace redmagic {
#ifdef CONF_GLOBAL_ABORT
extern long global_icount_abort;
#endif

extern long global_icount;
}

class UnprotectMalloc {
Expand Down Expand Up @@ -635,6 +637,20 @@ void* Manager::backwards_branch(void *id, void *ret_addr, void **stack_ptr) {
should_perform_trace = false;
}
}

#define TRACE_SPEED_LIMIT(time, cnt) \
if(time_ms(time_delta(speed_limit_time ## time, now)) >= time) { \
speed_limit_time ## time = now; \
speed_limit_last_icount ## time = global_icount; \
} else if(speed_limit_last_icount ## time + cnt < global_icount) { \
should_perform_trace = false; \
}

CONF_TRACE_INSTRUCTION_LIMIT_PER_TIME(TRACE_SPEED_LIMIT)

#undef TRACE_SPEED_LIMIT


#endif
// #ifdef CONF_ESTIMATE_INSTRUCTIONS
// if(info->avg_observed_instructions == 0) {
Expand Down Expand Up @@ -924,7 +940,7 @@ void* Manager::end_branchable_frame(void *ret_addr, void **stack_ptr) {
}
}
#endif
assert(head->frame_stack_ptr > (mem_loc_t)stack_ptr);
assert(head->frame_stack_ptr >= (mem_loc_t)stack_ptr);
branchable_frame_id--;
assert(head->frame_id <= branchable_frame_id);
return NULL;
Expand Down Expand Up @@ -1018,9 +1034,6 @@ bool Manager::should_trace_method(void *id) {
return true;
}

namespace redmagic {
extern long global_icount;
}

void Manager::print_info() {
UnprotectMalloc upm;
Expand Down
17 changes: 16 additions & 1 deletion src/tracer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2026,7 +2026,22 @@ void Tracer::replace_rip_instruction() {
// if there was a blacklist what instructions should go on it?


assert(0);
#ifndef CONF_VERBOSE
// then nothing would have printed so figure out what to print and print that
ud_set_syntax(&disassm, UD_SYN_ATT);
set_pc(ud_insn_off(&disassm));
// redo the disassemble
ud_disassemble(&disassm);
red_printf("[%10lu %8i %#016lx] \t%-38s %-20s %s\n", global_icount, icount, ud_insn_off(&disassm), ud_insn_asm(&disassm), ud_insn_hex(&disassm), "??");
#endif


red_printf("failed to replace rip instruction\n");


manager->print_info();

::abort();
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/user_interface.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

// useful for compiling against while not causing any "problems"
#define DISABLE_REDMAGIC
//#define DISABLE_REDMAGIC

// this is define in a .c file instead of .s so that we can
// interface with the -fPIC methods & macros, also need to have @plt at the end
Expand Down
6 changes: 6 additions & 0 deletions tools/run-ipython-expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env expect

spawn gdb "/home/matthew/.virtualenvs/redmagic/bin/python" "--eval-command=run -m IPython" "--eval-command=quit" "-batch"

expect "In \[1\]:"
send "\n"

0 comments on commit a6a04da

Please sign in to comment.