|
@@ -134,7 +134,6 @@ class ModelverseJit(object):
|
|
|
self.nop_insertion_enabled = True
|
|
|
self.jit_success_log_function = None
|
|
|
self.jit_code_log_function = None
|
|
|
- self.compile_function_body = compile_function_body_baseline
|
|
|
|
|
|
def set_jit_enabled(self, is_enabled=True):
|
|
|
"""Enables or disables the JIT."""
|
|
@@ -510,342 +509,3 @@ class ModelverseJit(object):
|
|
|
raise JitCompilationFailedException("Function was marked '%s'." % jit_runtime.MUTABLE_FUNCTION_KEY)
|
|
|
|
|
|
#raise primitive_functions.PrimitiveFinished("pass")
|
|
|
-
|
|
|
-def compile_function_body_interpret(jit, function_name, body_id, task_root, header=None):
|
|
|
- print("INTERPRET")
|
|
|
- """Create a function that invokes the interpreter on the given function."""
|
|
|
- (parameter_ids, parameter_list, _), = yield [
|
|
|
- ("CALL_ARGS", [jit.jit_signature, (body_id,)])]
|
|
|
- param_dict = dict(list(zip(parameter_ids, parameter_list)))
|
|
|
- body_bytecode, = yield [("CALL_ARGS", [jit.jit_parse_bytecode, (body_id,)])]
|
|
|
- def __interpret_function(**kwargs):
|
|
|
- if header is not None:
|
|
|
- (done, result), = yield [("CALL_KWARGS", [header, kwargs])]
|
|
|
- if done:
|
|
|
- raise primitive_functions.PrimitiveFinished(result)
|
|
|
-
|
|
|
- local_args = {}
|
|
|
- inner_kwargs = dict(kwargs)
|
|
|
- for param_id, name in list(param_dict.items()):
|
|
|
- local_args[param_id] = inner_kwargs[name]
|
|
|
- del inner_kwargs[name]
|
|
|
-
|
|
|
- yield [("TAIL_CALL_ARGS",
|
|
|
- [bytecode_interpreter.interpret_bytecode_function,
|
|
|
- (function_name, body_bytecode, local_args, inner_kwargs)])]
|
|
|
-
|
|
|
- jit.jit_globals[function_name] = __interpret_function
|
|
|
- raise primitive_functions.PrimitiveFinished(__interpret_function)
|
|
|
-
|
|
|
-def compile_function_body_baseline(
|
|
|
- jit, function_name, body_id, task_root,
|
|
|
- header=None, compatible_temporary_protects=False):
|
|
|
- print("BASELINE")
|
|
|
- """Have the baseline JIT compile the function with the given name and body id."""
|
|
|
- (parameter_ids, parameter_list, _), = yield [
|
|
|
- ("CALL_ARGS", [jit.jit_signature, (body_id,)])]
|
|
|
- if None in parameter_list:
|
|
|
- raise JitCompilationFailedException('JIT exception...')
|
|
|
- param_dict = dict(list(zip(parameter_ids, parameter_list)))
|
|
|
- body_param_dict = dict(list(zip(parameter_ids, [p + "_ptr" for p in parameter_list])))
|
|
|
- body_bytecode, = yield [("CALL_ARGS", [jit.jit_parse_bytecode, (body_id,)])]
|
|
|
- state = bytecode_to_tree.AnalysisState(
|
|
|
- jit, body_id, task_root, body_param_dict,
|
|
|
- jit.max_instructions)
|
|
|
- constructed_body, = yield [("CALL_ARGS", [state.analyze, (body_bytecode,)])]
|
|
|
-
|
|
|
- if header is not None:
|
|
|
- constructed_body = tree_ir.create_block(header, constructed_body)
|
|
|
-
|
|
|
- # Optimize the function's body.
|
|
|
- constructed_body, = yield [("CALL_ARGS", [optimize_tree_ir, (constructed_body,)])]
|
|
|
-
|
|
|
- # Wrap the tree IR in a function definition.
|
|
|
- constructed_function = create_function(
|
|
|
- function_name, parameter_list, param_dict,
|
|
|
- body_param_dict, constructed_body, jit.get_source_map_name(function_name),
|
|
|
- compatible_temporary_protects)
|
|
|
-
|
|
|
- # Convert the function definition to Python code, and compile it.
|
|
|
- raise primitive_functions.PrimitiveFinished(
|
|
|
- jit.jit_define_function(function_name, constructed_function))
|
|
|
-
|
|
|
-def compile_function_body_fast(jit, function_name, body_id, _):
|
|
|
- print("FAST2")
|
|
|
- """Have the fast JIT compile the function with the given name and body id."""
|
|
|
- (parameter_ids, parameter_list, _), = yield [
|
|
|
- ("CALL_ARGS", [jit.jit_signature, (body_id,)])]
|
|
|
- param_dict = dict(list(zip(parameter_ids, parameter_list)))
|
|
|
-
|
|
|
- print("Got body bytecode: " + str(body_id))
|
|
|
- constructed_body, = yield [("CALL_ARGS", [jit.new_compile, (body_id,)])]
|
|
|
- print("Constructed body:")
|
|
|
- print(constructed_body)
|
|
|
-
|
|
|
- constructed_function = create_bare_function(function_name, parameter_list, constructed_body)
|
|
|
-
|
|
|
- # Convert the function definition to Python code, and compile it.
|
|
|
- raise primitive_functions.PrimitiveFinished(
|
|
|
- jit.jit_define_function(function_name, constructed_function))
|
|
|
-
|
|
|
-def compile_function_body_fast_original(jit, function_name, body_id, _):
|
|
|
- print("FAST_ORIGINAL")
|
|
|
- """Have the fast JIT compile the function with the given name and body id."""
|
|
|
- (parameter_ids, parameter_list, _), = yield [
|
|
|
- ("CALL_ARGS", [jit.jit_signature, (body_id,)])]
|
|
|
- param_dict = dict(list(zip(parameter_ids, parameter_list)))
|
|
|
- if None in param_dict:
|
|
|
- del param_dict[None]
|
|
|
- print("REMOVING NONE IN PARAMETERS for ")
|
|
|
- print(locals())
|
|
|
- body_bytecode, = yield [("CALL_ARGS", [jit.jit_parse_bytecode, (body_id,)])]
|
|
|
- bytecode_analyzer = bytecode_to_cfg.AnalysisState(jit, function_name, param_dict)
|
|
|
- bytecode_analyzer.analyze(body_bytecode)
|
|
|
- entry_point, = yield [
|
|
|
- ("CALL_ARGS", [cfg_optimization.optimize, (bytecode_analyzer.entry_point, jit)])]
|
|
|
- if jit.jit_code_log_function is not None:
|
|
|
- jit.jit_code_log_function(
|
|
|
- "CFG for function '%s' at '%d':\n%s" % (
|
|
|
- function_name, body_id,
|
|
|
- '\n'.join(map(str, cfg_ir.get_all_reachable_blocks(entry_point)))))
|
|
|
-
|
|
|
- # Lower the CFG to tree IR.
|
|
|
- constructed_body = cfg_to_tree.lower_flow_graph(entry_point, jit)
|
|
|
-
|
|
|
- # Optimize the tree that was generated.
|
|
|
- constructed_body, = yield [("CALL_ARGS", [optimize_tree_ir, (constructed_body,)])]
|
|
|
- print("OUTPUT: " + str(constructed_body))
|
|
|
- constructed_function = create_bare_function(function_name, parameter_list, constructed_body)
|
|
|
-
|
|
|
- # Convert the function definition to Python code, and compile it.
|
|
|
- raise primitive_functions.PrimitiveFinished(
|
|
|
- jit.jit_define_function(function_name, constructed_function))
|
|
|
-
|
|
|
-def favor_large_functions(body_bytecode):
|
|
|
- """Computes the initial temperature of a function based on the size of
|
|
|
- its body bytecode. Larger functions are favored and the temperature
|
|
|
- is incremented by one on every call."""
|
|
|
- # The rationale for this heuristic is that it does some damage control:
|
|
|
- # we can afford to decide (wrongly) not to fast-jit a small function,
|
|
|
- # because we can just fast-jit that function later on. Since the function
|
|
|
- # is so small, it will (hopefully) not be able to deal us a heavy blow in
|
|
|
- # terms of performance.
|
|
|
- #
|
|
|
- # If we decide not to fast-jit a large function however, we might end up
|
|
|
- # in a situation where said function runs for a long time before we
|
|
|
- # realize that we really should have jitted it. And that's exactly what
|
|
|
- # this heuristic tries to avoid.
|
|
|
- return len(body_bytecode.get_reachable()), 1
|
|
|
-
|
|
|
-def favor_small_functions(body_bytecode):
|
|
|
- """Computes the initial temperature of a function based on the size of
|
|
|
- its body bytecode. Smaller functions are favored and the temperature
|
|
|
- is incremented by one on every call."""
|
|
|
- # The rationale for this heuristic is that small functions are easy to
|
|
|
- # fast-jit, because they probably won't trigger the non-linear complexity
|
|
|
- # of fast-jit's algorithms. So it might be cheaper to fast-jit small
|
|
|
- # functions and get a performance boost from that than to fast-jit large
|
|
|
- # functions.
|
|
|
- return ADAPTIVE_FAST_JIT_TEMPERATURE_THRESHOLD - len(body_bytecode.get_reachable()), 1
|
|
|
-
|
|
|
-ADAPTIVE_JIT_LOOP_INSTRUCTION_MULTIPLIER = 4
|
|
|
-
|
|
|
-#ADAPTIVE_BASELINE_JIT_TEMPERATURE_THRESHOLD = 100
|
|
|
-# TODO bug in the ByteCode Interpreter results in erroneous execution with random jumps; disable for now!
|
|
|
-ADAPTIVE_BASELINE_JIT_TEMPERATURE_THRESHOLD = -float('inf')
|
|
|
-"""The threshold temperature at which the adaptive JIT will use the baseline JIT."""
|
|
|
-
|
|
|
-ADAPTIVE_FAST_JIT_TEMPERATURE_THRESHOLD = 250
|
|
|
-"""The threshold temperature at which the adaptive JIT will use the fast JIT."""
|
|
|
-
|
|
|
-def favor_loops(body_bytecode):
|
|
|
- """Computes the initial temperature of a function. Code within a loop makes
|
|
|
- the function hotter; code outside loops makes the function colder. The
|
|
|
- temperature is incremented by one on every call."""
|
|
|
- reachable_instructions = body_bytecode.get_reachable()
|
|
|
- # First set the temperature to the negative number of instructions.
|
|
|
- temperature = ADAPTIVE_BASELINE_JIT_TEMPERATURE_THRESHOLD - len(reachable_instructions)
|
|
|
- for instruction in reachable_instructions:
|
|
|
- if isinstance(instruction, bytecode_ir.WhileInstruction):
|
|
|
- # Then increase the temperature by the number of instructions reachable
|
|
|
- # from loop bodies. Note that the algorithm will count nested loops twice.
|
|
|
- # This is actually by design.
|
|
|
- loop_body_instructions = instruction.body.get_reachable(
|
|
|
- lambda x: not isinstance(
|
|
|
- x, (bytecode_ir.BreakInstruction, bytecode_ir.ContinueInstruction)))
|
|
|
- temperature += ADAPTIVE_JIT_LOOP_INSTRUCTION_MULTIPLIER * len(loop_body_instructions)
|
|
|
-
|
|
|
- return temperature, 1
|
|
|
-
|
|
|
-def favor_small_loops(body_bytecode):
|
|
|
- """Computes the initial temperature of a function. Code within a loop makes
|
|
|
- the function hotter; code outside loops makes the function colder. The
|
|
|
- temperature is incremented by one on every call."""
|
|
|
- reachable_instructions = body_bytecode.get_reachable()
|
|
|
- # First set the temperature to the negative number of instructions.
|
|
|
- temperature = ADAPTIVE_FAST_JIT_TEMPERATURE_THRESHOLD - 50 - len(reachable_instructions)
|
|
|
- for instruction in reachable_instructions:
|
|
|
- if isinstance(instruction, bytecode_ir.WhileInstruction):
|
|
|
- # Then increase the temperature by the number of instructions reachable
|
|
|
- # from loop bodies. Note that the algorithm will count nested loops twice.
|
|
|
- # This is actually by design.
|
|
|
- loop_body_instructions = instruction.body.get_reachable(
|
|
|
- lambda x: not isinstance(
|
|
|
- x, (bytecode_ir.BreakInstruction, bytecode_ir.ContinueInstruction)))
|
|
|
- temperature += (
|
|
|
- (ADAPTIVE_JIT_LOOP_INSTRUCTION_MULTIPLIER ** 2) *
|
|
|
- int(math.sqrt(len(loop_body_instructions))))
|
|
|
-
|
|
|
- return temperature, max(int(math.log(len(reachable_instructions), 2)), 1)
|
|
|
-
|
|
|
-class AdaptiveJitState(object):
|
|
|
- """Shared state for adaptive JIT compilation."""
|
|
|
- def __init__(
|
|
|
- self, temperature_counter_name,
|
|
|
- temperature_increment, can_rejit_name):
|
|
|
- self.temperature_counter_name = temperature_counter_name
|
|
|
- self.temperature_increment = temperature_increment
|
|
|
- self.can_rejit_name = can_rejit_name
|
|
|
-
|
|
|
- def compile_interpreter(
|
|
|
- self, jit, function_name, body_id, task_root):
|
|
|
- """Compiles the given function as a function that controls the temperature counter
|
|
|
- and calls the interpreter."""
|
|
|
- def __increment_temperature(**kwargs):
|
|
|
- if jit.jit_globals[self.can_rejit_name]:
|
|
|
- temperature_counter_val = jit.jit_globals[self.temperature_counter_name]
|
|
|
- temperature_counter_val += self.temperature_increment
|
|
|
- jit.jit_globals[self.temperature_counter_name] = temperature_counter_val
|
|
|
- if temperature_counter_val >= ADAPTIVE_BASELINE_JIT_TEMPERATURE_THRESHOLD:
|
|
|
- if temperature_counter_val >= ADAPTIVE_FAST_JIT_TEMPERATURE_THRESHOLD:
|
|
|
- yield [
|
|
|
- ("CALL_ARGS",
|
|
|
- [jit.jit_rejit,
|
|
|
- (task_root, body_id, function_name, compile_function_body_fast)])]
|
|
|
- else:
|
|
|
- yield [
|
|
|
- ("CALL_ARGS",
|
|
|
- [jit.jit_rejit,
|
|
|
- (task_root, body_id, function_name, self.compile_baseline)])]
|
|
|
- result, = yield [("CALL_KWARGS", [jit.jit_globals[function_name], kwargs])]
|
|
|
- raise primitive_functions.PrimitiveFinished((True, result))
|
|
|
-
|
|
|
- raise primitive_functions.PrimitiveFinished((False, None))
|
|
|
-
|
|
|
- yield [
|
|
|
- ("TAIL_CALL_ARGS",
|
|
|
- [compile_function_body_interpret,
|
|
|
- (jit, function_name, body_id, task_root, __increment_temperature)])]
|
|
|
-
|
|
|
- def compile_baseline(
|
|
|
- self, jit, function_name, body_id, task_root):
|
|
|
- """Compiles the given function with the baseline JIT, and inserts logic that controls
|
|
|
- the temperature counter."""
|
|
|
- (_, parameter_list, _), = yield [
|
|
|
- ("CALL_ARGS", [jit.jit_signature, (body_id,)])]
|
|
|
-
|
|
|
- # This tree represents the following logic:
|
|
|
- #
|
|
|
- # if can_rejit:
|
|
|
- # global temperature_counter
|
|
|
- # temperature_counter = temperature_counter + temperature_increment
|
|
|
- # if temperature_counter >= ADAPTIVE_FAST_JIT_TEMPERATURE_THRESHOLD:
|
|
|
- # yield [("CALL_KWARGS", [jit_runtime.JIT_REJIT_FUNCTION_NAME, {...}])]
|
|
|
- # yield [("TAIL_CALL_KWARGS", [function_name, {...}])]
|
|
|
-
|
|
|
- header = tree_ir.SelectInstruction(
|
|
|
- tree_ir.LoadGlobalInstruction(self.can_rejit_name),
|
|
|
- tree_ir.create_block(
|
|
|
- tree_ir.DeclareGlobalInstruction(self.temperature_counter_name),
|
|
|
- tree_ir.IgnoreInstruction(
|
|
|
- tree_ir.StoreGlobalInstruction(
|
|
|
- self.temperature_counter_name,
|
|
|
- tree_ir.BinaryInstruction(
|
|
|
- tree_ir.LoadGlobalInstruction(self.temperature_counter_name),
|
|
|
- '+',
|
|
|
- tree_ir.LiteralInstruction(self.temperature_increment)))),
|
|
|
- tree_ir.SelectInstruction(
|
|
|
- tree_ir.BinaryInstruction(
|
|
|
- tree_ir.LoadGlobalInstruction(self.temperature_counter_name),
|
|
|
- '>=',
|
|
|
- tree_ir.LiteralInstruction(ADAPTIVE_FAST_JIT_TEMPERATURE_THRESHOLD)),
|
|
|
- tree_ir.create_block(
|
|
|
- tree_ir.RunGeneratorFunctionInstruction(
|
|
|
- tree_ir.LoadGlobalInstruction(jit_runtime.JIT_REJIT_FUNCTION_NAME),
|
|
|
- tree_ir.DictionaryLiteralInstruction([
|
|
|
- (tree_ir.LiteralInstruction('task_root'),
|
|
|
- bytecode_to_tree.load_task_root()),
|
|
|
- (tree_ir.LiteralInstruction('body_id'),
|
|
|
- tree_ir.LiteralInstruction(body_id)),
|
|
|
- (tree_ir.LiteralInstruction('function_name'),
|
|
|
- tree_ir.LiteralInstruction(function_name)),
|
|
|
- (tree_ir.LiteralInstruction('compile_function_body'),
|
|
|
- tree_ir.LoadGlobalInstruction(
|
|
|
- jit_runtime.JIT_COMPILE_FUNCTION_BODY_FAST_FUNCTION_NAME))]),
|
|
|
- result_type=tree_ir.NO_RESULT_TYPE),
|
|
|
- bytecode_to_tree.create_return(
|
|
|
- tree_ir.create_jit_call(
|
|
|
- tree_ir.LoadGlobalInstruction(function_name),
|
|
|
- [(name, tree_ir.LoadLocalInstruction(name))
|
|
|
- for name in parameter_list],
|
|
|
- tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME)))),
|
|
|
- tree_ir.EmptyInstruction())),
|
|
|
- tree_ir.EmptyInstruction())
|
|
|
-
|
|
|
- # Compile with the baseline JIT, and insert the header.
|
|
|
- yield [
|
|
|
- ("TAIL_CALL_ARGS",
|
|
|
- [compile_function_body_baseline,
|
|
|
- (jit, function_name, body_id, task_root, header, True)])]
|
|
|
-
|
|
|
-def compile_function_body_adaptive(
|
|
|
- jit, function_name, body_id, task_root,
|
|
|
- temperature_heuristic=favor_loops):
|
|
|
- print("ADAPTIVE")
|
|
|
- """Compile the function with the given name and body id. An execution engine is picked
|
|
|
- automatically, and the function may be compiled again at a later time."""
|
|
|
- # The general idea behind this compilation technique is to first use the baseline JIT
|
|
|
- # to compile a function, and then switch to the fast JIT when we determine that doing
|
|
|
- # so would be a good idea. We maintain a 'temperature' counter, which has an initial value
|
|
|
- # and gets incremented every time the function is executed.
|
|
|
-
|
|
|
- body_bytecode, = yield [("CALL_ARGS", [jit.jit_parse_bytecode, (body_id,)])]
|
|
|
- initial_temperature, temperature_increment = temperature_heuristic(body_bytecode)
|
|
|
- if jit.jit_success_log_function is not None:
|
|
|
- jit.jit_success_log_function(
|
|
|
- "Initial temperature for '%s': %d" % (function_name, initial_temperature))
|
|
|
-
|
|
|
- if initial_temperature >= ADAPTIVE_FAST_JIT_TEMPERATURE_THRESHOLD:
|
|
|
- # Initial temperature exceeds the fast-jit threshold.
|
|
|
- # Compile this thing with fast-jit right away.
|
|
|
- if jit.jit_success_log_function is not None:
|
|
|
- jit.jit_success_log_function(
|
|
|
- "Compiling '%s' with fast-jit." % function_name)
|
|
|
- yield [("CALL_ARGS", [compile_function_body_fast, (jit, function_name, body_id, task_root)])]
|
|
|
- print("NEXT...")
|
|
|
-
|
|
|
- temperature_counter_name = jit.import_value(
|
|
|
- initial_temperature, function_name + "_temperature_counter")
|
|
|
-
|
|
|
- can_rejit_name = jit.get_can_rejit_name(function_name)
|
|
|
- jit.jit_globals[can_rejit_name] = True
|
|
|
-
|
|
|
- state = AdaptiveJitState(temperature_counter_name, temperature_increment, can_rejit_name)
|
|
|
-
|
|
|
- if initial_temperature >= ADAPTIVE_BASELINE_JIT_TEMPERATURE_THRESHOLD:
|
|
|
- # Initial temperature exceeds the baseline JIT threshold.
|
|
|
- # Compile this thing with baseline JIT right away.
|
|
|
- if jit.jit_success_log_function is not None:
|
|
|
- jit.jit_success_log_function(
|
|
|
- "Compiling '%s' with baseline-jit." % function_name)
|
|
|
- yield [
|
|
|
- ("TAIL_CALL_ARGS",
|
|
|
- [state.compile_baseline, (jit, function_name, body_id, task_root)])]
|
|
|
- else:
|
|
|
- # Looks like we'll use the interpreter initially.
|
|
|
- if jit.jit_success_log_function is not None:
|
|
|
- jit.jit_success_log_function(
|
|
|
- "Compiling '%s' with bytecode-interpreter." % function_name)
|
|
|
- yield [
|
|
|
- ("TAIL_CALL_ARGS",
|
|
|
- [state.compile_interpreter, (jit, function_name, body_id, task_root)])]
|