Pārlūkot izejas kodu

Implement experimental bytecode->CFG transformation

jonathanvdc 8 gadi atpakaļ
vecāks
revīzija
7d2b7d2035
2 mainītis faili ar 223 papildinājumiem un 31 dzēšanām
  1. 172 18
      kernel/modelverse_jit/bytecode_to_cfg.py
  2. 51 13
      kernel/modelverse_jit/cfg_ir.py

+ 172 - 18
kernel/modelverse_jit/bytecode_to_cfg.py

@@ -2,53 +2,207 @@
 
 import modelverse_jit.bytecode_ir as bytecode_ir
 import modelverse_jit.cfg_ir as cfg_ir
+import modelverse_jit.runtime as jit_runtime
 
 class AnalysisState(object):
     """State that is common to the bytecode->CFG transformation of a function."""
     def __init__(self):
-        pass
+        self.counter = cfg_ir.SharedCounter()
+        self.analyzed_instructions = set()
+        self.loop_instructions = {}
+        self.current_block = cfg_ir.BasicBlock(self.counter)
+
+    def analyze(self, instruction):
+        """Analyzes the given instruction as a basic block."""
+        if instruction in self.analyzed_instructions:
+            raise jit_runtime.JitCompilationFailedException(
+                'Cannot jit non-tree instruction graph.')
+
+        self.analyzed_instructions.add(instruction)
+
+        # Find an analyzer.
+        instruction_type = type(instruction)
+        if instruction_type in self.instruction_analyzers:
+            # Analyze the instruction.
+            result = self.instruction_analyzers[instruction_type](self, instruction)
+
+            # Check if the instruction has a 'next' instruction. If so, analyze it!
+            if instruction.next_instruction is not None:
+                next_result = self.analyze(instruction.next_instruction)
+                if next_result.has_result() or (not result.has_result()):
+                    result = next_result
+
+            return result
+        else:
+            raise jit_runtime.JitCompilationFailedException(
+                "Unknown instruction type: '%s'" % type(instruction))
+
+    def emit_select(self, create_condition, create_if_body, create_else_body):
+        """Emits a 'select' instruction."""
+        # Create blocks that look like this:
+        #
+        # !current_block(...):
+        #     ...
+        #     $cond = <condition>
+        #     select $cond, !if_block(), !else_block()
+        #
+        # !if_block():
+        #     $if_result = <if-body>
+        #     jump !phi_block($if_result)
+        #
+        # !else_block():
+        #     $else_result = <else-body>
+        #     jump !phi_block($else_result)
+        #
+        # !phi_block($result = block-parameter):
+        #     ...
+        #
+        if_block = cfg_ir.BasicBlock(self.counter)
+        else_block = cfg_ir.BasicBlock(self.counter)
+        phi_block = cfg_ir.BasicBlock(self.counter)
+        param_def = phi_block.append_parameter(cfg_ir.BlockParameter())
+
+        condition = create_condition()
+        self.current_block.flow = cfg_ir.SelectFlow(
+            condition, cfg_ir.Branch(if_block), cfg_ir.Branch(else_block))
+
+        self.current_block = if_block
+        if_result = create_if_body()
+        self.current_block.flow = cfg_ir.create_jump(phi_block, [if_result])
+
+        self.current_block = else_block
+        else_result = create_else_body()
+        self.current_block.flow = cfg_ir.create_jump(phi_block, [else_result])
+
+        self.current_block = phi_block
+        return param_def
 
     def analyze_if(self, instruction):
-        raise NotImplementedError()
+        """Analyzes an 'if' instruction."""
+        return self.emit_select(
+            lambda: self.analyze(instruction.condition),
+            lambda: self.analyze(instruction.if_clause),
+            lambda: self.analyze(instruction.else_clause))
 
     def analyze_while(self, instruction):
-        raise NotImplementedError()
+        """Analyzes a 'while' instruction."""
+        # Create blocks that look like this:
+        #
+        # !current_block(...):
+        #     ...
+        #     jump !loop_condition()
+        #
+        # !loop_condition():
+        #     $condition = <condition>
+        #     select $condition, !loop_body(), !loop_exit()
+        #
+        # !loop_body():
+        #     $result = <body>
+        #     jump !loop_condition()
+        #
+        # !loop_exit():
+        #     $nothing = literal None
+        #     ...
+        loop_condition_block = cfg_ir.BasicBlock(self.counter)
+        loop_body_block = cfg_ir.BasicBlock(self.counter)
+        loop_exit_block = cfg_ir.BasicBlock(self.counter)
+        self.loop_instructions[instruction] = (loop_condition_block, loop_exit_block)
+
+        self.current_block.flow = cfg_ir.create_jump(loop_condition_block)
+
+        self.current_block = loop_condition_block
+        condition_value = self.analyze(instruction.condition)
+        self.current_block.flow = cfg_ir.SelectFlow(
+            condition_value, cfg_ir.Branch(loop_body_block), cfg_ir.Branch(loop_exit_block))
+
+        self.current_block = loop_body_block
+        self.analyze(instruction.body)
+        self.current_block.flow = cfg_ir.create_jump(loop_condition_block)
+
+        self.current_block = loop_exit_block
+        return loop_exit_block.append_definition(cfg_ir.Literal(None))
 
     def analyze_return(self, instruction):
-        raise NotImplementedError()
+        """Analyzes a 'return' instruction."""
+        if instruction.value is None:
+            return_value = self.current_block.append_definition(cfg_ir.Literal(None))
+        else:
+            return_value = self.analyze(instruction.value)
+        self.current_block.flow = cfg_ir.ReturnFlow(return_value)
+        self.current_block = cfg_ir.BasicBlock(self.counter)
 
     def analyze_constant(self, instruction):
-        raise NotImplementedError()
+        """Analyzes a 'constant' instruction."""
+        return self.current_block.append_definition(cfg_ir.Literal(instruction.node_id))
 
     def analyze_resolve(self, instruction):
-        raise NotImplementedError()
+        """Analyzes a 'resolve' instruction."""
+        return self.emit_select(
+            lambda:
+            self.current_block.append_definition(
+                cfg_ir.CheckLocalExists(instruction.variable)),
+            lambda:
+            self.current_block.append_definition(
+                cfg_ir.ResolveLocal(instruction.variable)),
+            lambda:
+            self.current_block.append_definition(
+                cfg_ir.ResolveGlobal(instruction.variable)))
 
     def analyze_declare(self, instruction):
-        raise NotImplementedError()
+        """Analyzes a 'declare' instruction."""
+        return self.current_block.append_definition(cfg_ir.DeclareLocal(instruction.variable))
 
     def analyze_global(self, instruction):
-        raise NotImplementedError()
+        """Analyzes a 'global' instruction."""
+        return self.current_block.append_definition(cfg_ir.DeclareGlobal(instruction.variable))
 
     def analyze_assign(self, instruction):
-        raise NotImplementedError()
+        """Analyzes an 'assign' instruction."""
+        pointer_result = self.analyze(instruction.pointer)
+        value_result = self.analyze(instruction.value)
+        return self.current_block.append_definition(
+            cfg_ir.StoreAtPointer(pointer_result, value_result))
 
     def analyze_access(self, instruction):
-        raise NotImplementedError()
+        """Analyzes an 'access' instruction."""
+        pointer_result = self.analyze(instruction.pointer)
+        return self.current_block.append_definition(cfg_ir.LoadPointer(pointer_result))
 
     def analyze_output(self, instruction):
-        raise NotImplementedError()
+        """Analyzes an 'output' instruction."""
+        value_result = self.analyze(instruction.value)
+        return self.current_block.append_definition(cfg_ir.Output(value_result))
 
-    def analyze_input(self, instruction):
-        raise NotImplementedError()
-
-    def analyze_call(self, instruction):
-        raise NotImplementedError()
+    def analyze_input(self, _):
+        """Analyzes an 'input' instruction."""
+        return self.current_block.append_definition(cfg_ir.Input())
 
     def analyze_break(self, instruction):
-        raise NotImplementedError()
+        """Analyzes a 'break' instruction."""
+        if instruction.loop in self.loop_instructions:
+            raise jit_runtime.JitCompilationFailedException(
+                "'break' instruction targets a 'while' loop that has not been defined yet.")
+
+        _, exit_block = self.loop_instructions[instruction.loop]
+        self.current_block.flow = cfg_ir.create_jump(exit_block)
 
     def analyze_continue(self, instruction):
-        raise NotImplementedError()
+        """Analyzes a 'continue' instruction."""
+        if instruction.loop in self.loop_instructions:
+            raise jit_runtime.JitCompilationFailedException(
+                "'continue' instruction targets a 'while' loop that has not been defined yet.")
+
+        cond_block, _ = self.loop_instructions[instruction.loop]
+        self.current_block.flow = cfg_ir.create_jump(cond_block)
+
+    def analyze_call(self, instruction):
+        """Analyzes the given 'call' instruction."""
+        target = self.analyze(instruction.target)
+        arg_list = []
+        for key, arg_instruction in instruction.argument_list:
+            arg_list.append((key, self.analyze(arg_instruction)))
+
+        return cfg_ir.JitFunctionCall(target, arg_list)
 
     instruction_analyzers = {
         bytecode_ir.SelectInstruction : analyze_if,

+ 51 - 13
kernel/modelverse_jit/cfg_ir.py

@@ -32,18 +32,24 @@ class BasicBlock(object):
 
     def prepend_definition(self, value):
         """Defines the given value in this basic block."""
-        assert isinstance(value, Value)
-        result = Definition(self.counter.next_value(), value)
+        result = self.create_definition(value)
         self.definitions.insert(0, result)
         return result
 
     def append_definition(self, value):
         """Defines the given value in this basic block."""
-        assert isinstance(value, Value)
-        result = Definition(self.counter.next_value(), value)
+        result = self.create_definition(value)
         self.definitions.append(result)
         return result
 
+    def create_definition(self, value=None):
+        """Creates a definition, but does not assign it to this block yet."""
+        if isinstance(value, Definition):
+            return value
+        else:
+            assert isinstance(value, Value) or value is None
+            return Definition(self.counter.next_value(), value)
+
     def remove_definition(self, definition):
         """Removes the given definition from this basic block."""
         return self.definitions.remove(definition)
@@ -59,12 +65,14 @@ class Definition(object):
     def __init__(self, index, value):
         self.index = index
         self.value = value
-        assert isinstance(value, Value)
+        if value is not None:
+            assert isinstance(value, Value)
 
     def redefine(self, new_value):
         """Tweaks this definition to take on the given new value."""
         self.value = new_value
-        assert isinstance(new_value, Value)
+        if new_value is not None:
+            assert isinstance(new_value, Value)
 
     def ref_str(self):
         """Gets a string that represents a reference to this definition."""
@@ -90,9 +98,11 @@ class Instruction(object):
 
 class Branch(Instruction):
     """Represents a branch from one basic block to another."""
-    def __init__(self, block, arguments):
+    def __init__(self, block, arguments=None):
         self.block = block
         assert isinstance(block, BasicBlock)
+        if arguments is None:
+            arguments = []
         self.arguments = arguments
         assert all([isinstance(arg, Definition) for arg in arguments])
 
@@ -188,6 +198,10 @@ class Value(Instruction):
         """Gets all definitions and instructions on which this instruction depends."""
         raise NotImplementedError()
 
+    def has_value(self):
+        """Tells if this value produces a result that is not None."""
+        return True
+
     def has_side_effects(self):
         """Tells if this instruction has side-effects."""
         return False
@@ -224,6 +238,10 @@ class Literal(Value):
         """Gets all definitions and instructions on which this instruction depends."""
         return []
 
+    def has_value(self):
+        """Tells if this value produces a result that is not None."""
+        return self.literal is not None
+
     def __str__(self):
         return 'literal %r' % self.literal
 
@@ -249,8 +267,8 @@ class JitFunctionCall(Value):
             self.target.ref_str(),
             ', '.join(['%s=%s' % (key, val.ref_str()) for key, val in self.argument_list]))
 
-class DefineLocal(Value):
-    """A value that defines a local variable."""
+class DeclareLocal(Value):
+    """A value that declares a local variable."""
     def __init__(self, variable):
         Value.__init__(self)
         self.variable = variable
@@ -259,15 +277,19 @@ class DefineLocal(Value):
         """Gets all definitions and instructions on which this instruction depends."""
         return []
 
+    def has_value(self):
+        """Tells if this value produces a result that is not None."""
+        return False
+
     def has_side_effects(self):
         """Tells if this instruction has side-effects."""
         return True
 
     def __str__(self):
-        return 'define-local %d' % self.variable.node_id
+        return 'declare-local %d' % self.variable.node_id
 
-class DefineGlobal(Value):
-    """A value that defines a global variable."""
+class DeclareGlobal(Value):
+    """A value that declares a global variable."""
     def __init__(self, variable):
         Value.__init__(self)
         self.variable = variable
@@ -276,12 +298,16 @@ class DefineGlobal(Value):
         """Gets all definitions and instructions on which this instruction depends."""
         return []
 
+    def has_value(self):
+        """Tells if this value produces a result that is not None."""
+        return False
+
     def has_side_effects(self):
         """Tells if this instruction has side-effects."""
         return True
 
     def __str__(self):
-        return 'define-global %s' % self.variable.name
+        return 'declare-global %s' % self.variable.name
 
 class CheckLocalExists(Value):
     """A value that checks if a local value has been defined (yet)."""
@@ -349,6 +375,10 @@ class StoreAtPointer(Value):
         """Gets all definitions and instructions on which this instruction depends."""
         return [self.pointer, self.value]
 
+    def has_value(self):
+        """Tells if this value produces a result that is not None."""
+        return False
+
     def has_side_effects(self):
         """Tells if this instruction has side-effects."""
         return True
@@ -380,9 +410,17 @@ class Output(Value):
         """Gets all definitions and instructions on which this instruction depends."""
         return [self.value]
 
+    def has_value(self):
+        """Tells if this value produces a result that is not None."""
+        return False
+
     def has_side_effects(self):
         """Tells if this instruction has side-effects."""
         return True
 
     def __str__(self):
         return 'output %s' % self.value.ref_str()
+
+def create_jump(block, arguments=None):
+    """Creates a jump to the given block with the given argument list."""
+    return JumpFlow(Branch(block, arguments))