Permalink
Browse files

deterministic module_eval simulated at the top-level: allows for attr…

…_reader/writer/accessor-like simulation! Renamed GenericBinding to Base, cleaned up

scope handling by the CFG builder immensely, fixed yield with args in a few cases.
  • Loading branch information...
1 parent e3583c5 commit 3c0533574f2a0cf78ced7cf7764962fc898985c9 Michael Edgar committed May 17, 2011
@@ -40,7 +40,6 @@ def self.perform_load_time_analysis(inputs, opts={})
time = Benchmark.realtime { annotator.annotate_with_text(tree, text) }
puts "Time spent running #{annotator.class} on #{filename}: #{time}"
else
- #annotator.annotate_with_text(tree, text)
ControlFlow.perform_cfg_analysis(tree, text, opts)
end
end
@@ -3,7 +3,7 @@ module SexpAnalysis
# The arity of a method is an instance of Arity. It's basically a range
# with some helper methods.
class Arity < Range
- # arguments: [Binding::GenericBinding]
+ # arguments: [Binding::Base]
def self.for_arglist(arguments)
min, max = 0, 0
arguments.each do |arg|
@@ -1,9 +1,9 @@
module Laser
module SexpAnalysis
module Bindings
- # This class represents a GenericBinding in Ruby. It may have a known type,
+ # This class represents a Base in Ruby. It may have a known type,
# class, value (if constant!), and a variety of other details.
- class GenericBinding
+ class Base
include Comparable
attr_accessor :name, :annotated_type, :inferred_type, :ast_node, :uses, :definition
attr_reader :value
@@ -63,7 +63,7 @@ def inspect
end
end
- class BlockBinding < GenericBinding
+ class BlockBinding < Base
attr_reader :argument_bindings, :ast_body
def initialize(name, value)
super(name, value)
@@ -74,15 +74,15 @@ def expr_type
end
end
- class KeywordBinding < GenericBinding
+ class KeywordBinding < Base
private :bind!
end
# Constants have slightly different properties in their bindings: They shouldn't
# be rebound. However.... Ruby allows it. It prints a warning when the rebinding
# happens, but we should be able to detect this statically. Oh, and they can't be
# bound inside a method. That too is easily detected statically.
- class ConstantBinding < GenericBinding
+ class ConstantBinding < Base
# Require an additional force parameter to rebind a Constant. That way, the user
# can configure whether rebinding counts as a warning or an error.
def bind!(val, force=false)
@@ -94,25 +94,25 @@ def bind!(val, force=false)
end
# We may want to track # of assignments/reads from local vars, so we should subclass
- # GenericBinding for it.
- class LocalVariableBinding < GenericBinding
+ # Base for it.
+ class LocalVariableBinding < Base
end
- class TemporaryBinding < GenericBinding
+ class TemporaryBinding < Base
def non_ssa_name
name.rpartition('#').first
end
end
- class InstanceVariableBinding < GenericBinding
+ class InstanceVariableBinding < Base
end
# Possible extension ideas:
# - Initial definition point?
- class GlobalVariableBinding < GenericBinding
+ class GlobalVariableBinding < Base
end
- class ArgumentBinding < GenericBinding
+ class ArgumentBinding < Base
attr_reader :kind, :default_value_sexp
def initialize(name, value, kind, default_value = nil)
super(name, value)
@@ -35,6 +35,7 @@ def self.bootstrap
if Scope.const_defined?("GlobalScope")
raise BootstrappingError.new('GlobalScope has already been initialized')
else
+ global.lexical_target = object_class.binding
Scope.const_set("GlobalScope", global)
end
class_scope.parent = Scope::GlobalScope
@@ -179,7 +179,7 @@ def to_s
else range = 1..-1
end
args = ins[range].map do |arg|
- if Bindings::GenericBinding === arg
+ if Bindings::Base === arg
then arg.name
else arg.inspect
end
@@ -12,6 +12,7 @@ def initialize(sexp, formals=[], scope=Scope::GlobalScope)
@formals = formals
@graph = @enter = @exit = nil
@scope_stack = [scope]
+ @self_stack = []
@temporary_counter = 0
@temporary_table = Hash.new do |hash, keys|
@temporary_counter += 1
@@ -21,8 +22,6 @@ def initialize(sexp, formals=[], scope=Scope::GlobalScope)
def build
initialize_graph
- @namespace_stack = [ClassRegistry['Object'].binding]
- @self_stack = []
@current_node = @sexp
@self_register = Bindings::TemporaryBinding.new('self', current_scope.self_ptr)
build_prologue
@@ -37,23 +36,10 @@ def build
@graph
end
- def push_namespace(class_or_mod)
- @namespace_stack.push class_or_mod
- end
-
def current_namespace
- @namespace_stack.last
- end
-
- def pop_namespace
- @namespace_stack.pop
- end
-
- def with_namespace(namespace)
- push_namespace namespace
- yield
- ensure
- pop_namespace
+ scope = current_scope
+ scope = scope.parent if @scope_stack.size == 1 && @sexp.type != :program
+ scope.lexical_target
end
def push_scope(scope)
@@ -88,9 +74,9 @@ def pop_self
@self_stack.pop
copy_instruct(@self_register, current_self)
end
-
- def with_self(obj)
- push_self obj
+
+ def with_self(namespace)
+ push_self namespace
yield
ensure
pop_self
@@ -147,7 +133,8 @@ def build_prologue
reset_visibility_stack
build_exception_blocks
if @sexp.type != :program
- push_scope(Scope::ClosedScope.new(current_scope, current_self))
+ dynamic_context = Scope::ClosedScope.new(current_scope, current_self)
+ @scope_stack = [dynamic_context]
@block_arg = call_instruct(ClassRegistry['Laser#Magic'].binding,
:current_block, value: true, raise: false)
@block_arg.name = 't#current_block'
@@ -911,7 +898,7 @@ def return0_instruct
end
def return_instruct(node)
- result = evaluate_args_into_array node[1][1]
+ result = evaluate_args_into_array node[1]
return_uncond_jump_instruct result
end
@@ -948,7 +935,7 @@ def yield_instruct(arg=nil, opts={})
def yield_instruct_with_arg(node, opts={})
opts = {raise: true, value: true}.merge(opts)
- result = evaluate_args_into_array node[1][1]
+ result = evaluate_args_into_array node[1]
yield_instruct result, opts
end
@@ -958,6 +945,10 @@ def evaluate_args_into_array(args)
if args[0] == :args_add_star
# if there's a splat, always return an actual array object of all the arguments.
compute_varargs(args)
+ elsif args[0] == :paren
+ evaluate_args_into_array(args[1])
+ elsif args[0] == :args_add_block
+ evaluate_args_into_array(args[1])
elsif args.size > 1
# if there's more than 1 argument, but no splats, then we just pack
# them into an array and return that array.
@@ -1219,9 +1210,7 @@ def class_instruct(class_name, superclass, body, opts={value: true})
start_block after_exists_check
call_instruct(Bootstrap::VISIBILITY_STACK, :push, const_instruct(:public), raise: false, value: false)
# use this namespace!
- with_namespace the_class_holder do
- module_eval_instruct(the_class_holder, body, opts)
- end
+ module_eval_instruct(the_class_holder, body, opts)
call_instruct(Bootstrap::VISIBILITY_STACK, :pop, raise: false, value: false)
end
@@ -1269,9 +1258,7 @@ def module_instruct(module_name, body, opts={value: true})
start_block after_exists_check
call_instruct(Bootstrap::VISIBILITY_STACK, :push, const_instruct(:public), raise: false, value: false)
- with_namespace the_module_holder do
- module_eval_instruct(the_module_holder, body, opts)
- end
+ module_eval_instruct(the_module_holder, body, opts)
call_instruct(Bootstrap::VISIBILITY_STACK, :pop, raise: false, value: false)
end
@@ -1291,19 +1278,14 @@ def singleton_class_instruct(receiver, body, opts={value: false})
start_block has_singleton
singleton = call_instruct(receiver_val, :singleton_class, value: true, raise: false)
- with_namespace singleton do
- module_eval_instruct(singleton, body, opts)
- end
+ module_eval_instruct(singleton, body, opts)
end
- # Runs the block as a module evaluation by the given receiver. When
- # we call module_eval, we know its raising characteristics, so we
- # can generate efficient jumps here.
- #
- # TODO(adgar): figure out resume...
- def module_eval_instruct(receiver, body, opts = {value: false})
- with_self(receiver) do
- result = walk_node body, opts
+ def module_eval_instruct(new_self, body, opts)
+ with_scope(ClosedScope.new(current_scope, new_self)) do
+ with_self new_self do
+ walk_node body, opts
+ end
end
end
@@ -1392,7 +1374,7 @@ def copy_instruct(lhs, rhs)
end
def evaluate_if_needed(node, opts={})
- if Bindings::GenericBinding === node
+ if Bindings::Base === node
node
else
walk_node(node, opts)
@@ -1426,7 +1408,7 @@ def single_assign_instruct(lhs, rhs, opts={})
# rhs may or may not be evaluated, and we're okay with that
multiple_assign_instruct(lhs[1], rhs, opts)
else
- if Bindings::GenericBinding === rhs
+ if Bindings::Base === rhs
rhs_val = rhs
elsif rhs.type == :mrhs_new_from_args || rhs.type == :args_add_star ||
rhs.type == :mrhs_add_star
@@ -1857,7 +1839,7 @@ def generic_aref_instruct(receiver, args, val, opts={})
# This is different from normal splatting because we are computing based
# upon the argument list of the method, not a normal arg_ node.
#
- # returns: (Bindings::GenericBinding | [Bindings::GenericBinding], Boolean)
+ # returns: (Bindings::Base | [Bindings::Base], Boolean)
def compute_zsuper_arguments(node)
args_to_walk = node.scope.method.arguments
is_vararg = args_to_walk.any? { |arg| arg.kind == :rest }
@@ -29,7 +29,7 @@ def deep_dup(temp_lookup, opts={})
new_body = @body[1..-1].map do |arg|
case arg
when Bindings::ConstantBinding then arg
- when Bindings::GenericBinding then temp_lookup[arg]
+ when Bindings::Base then temp_lookup[arg]
else arg.dup rescue arg
end
end
@@ -43,21 +43,6 @@ def method_missing(meth, *args, &blk)
@body.send(meth, *args, &blk)
end
- def simulate!
- case type
- when :assign
- lhs, rhs = self[1..2]
- if Bindings::GenericBinding === rhs
- lhs.bind! rhs.value
- lhs.inferred_type = rhs.inferred_type
- else
- # literal assignment e.g. fixnum/float/string
- lhs.bind! rhs
- lhs.inferred_type = Types::ClassType.new(rhs.class.name, :invariant)
- end
- end
- end
-
def method_call?
[:call, :call_vararg, :super, :super_vararg].include?(type)
end
@@ -111,15 +96,15 @@ def explicit_targets
# Gets all bindings that are operands in this instruction
def operands
- self[operand_range].select { |x| Bindings::GenericBinding === x}
+ self[operand_range].select { |x| Bindings::Base === x}
end
# Replaces the operands with a new list. Used by SSA renaming.
def replace_operands(new_operands)
# splice in new operands: replace bindings with bindings.
index = operand_range.begin
while new_operands.any? && index < @body.size
- if Bindings::GenericBinding === self[index]
+ if Bindings::Base === self[index]
self[index] = new_operands.shift
end
index += 1
@@ -282,7 +282,8 @@ def infer_type_and_raising(instruction, receiver, method_name, args)
rescue TypeError => err
type = Types::TOP
raised = Frequency::ALWAYS
- instruction.node.add_error(NoMatchingTypeSignature.new("No method named #{method_name} with matching types was found", instruction.node))
+ instruction.node.add_error(NoMatchingTypeSignature.new(
+ "No method named #{method_name} with matching types was found", instruction.node))
else
raised = raiseability_for_instruction(instruction)
end
@@ -350,7 +351,7 @@ def constant_propagation_evaluate(instruction)
case instruction.type
when :assign
rhs = instruction[2]
- if Bindings::GenericBinding === rhs
+ if Bindings::Base === rhs
# temporary <- temporary
new_value = rhs.value
new_type = rhs.inferred_type
@@ -428,10 +429,12 @@ def apply_special_case(receiver, method_name, *args)
end
elsif method_name == :===
if LaserModule === receiver.value && args.first != UNDEFINED
- result = Types.subtype?(args.first.expr_type, Types::ClassType.new(receiver.value.path, :covariant))
+ instance_type = args.first.expr_type
+ module_type = Types::ClassType.new(receiver.value.path, :covariant)
+ result = Types.subtype?(instance_type, module_type)
if result
return [true, Types::TRUECLASS, Frequency::NEVER]
- else
+ elsif !(Types.overlap?(instance_type, module_type))
return [false, Types::FALSECLASS, Frequency::NEVER]
end
end
@@ -23,7 +23,7 @@ class ControlFlowGraph < RGL::ControlFlowGraph
attr_accessor :root, :block_register, :final_exception, :final_return
attr_reader :formals, :uses, :definition, :constants, :live, :globals
- attr_reader :yield_type, :raise_type, :in_ssa
+ attr_reader :yield_type, :raise_type, :in_ssa, :yield_arity
attr_reader :self_type, :formal_types, :block_type
attr_reader :all_cached_variables
# postdominator blocks for: all non-failed-yield exceptions, yield-failing
@@ -183,14 +183,26 @@ def analyze(opts={})
simulate([], :mutation => true)
else
static_single_assignment_form unless @in_ssa
+ Laser.debug_puts('>>> Starting CP <<<')
perform_constant_propagation
+ Laser.debug_puts('>>> Finished CP <<<')
if opts[:optimize]
+ Laser.debug_puts('>>> Killing Unexecuted Edges <<<')
kill_unexecuted_edges
+ Laser.debug_puts('>>> Finished Killing Unexecuted Edges <<<')
+ Laser.debug_puts('>>> Pruning Totally Useless Blocks <<<')
prune_totally_useless_blocks
+ Laser.debug_puts('>>> Finished Pruning Totally Useless Blocks <<<')
+ Laser.debug_puts('>>> Dead Code Discovery <<<')
perform_dead_code_discovery
+ Laser.debug_puts('>>> Finished Dead Code Discovery <<<')
+ Laser.debug_puts('>>> Adding Unused Variable Warnings <<<')
add_unused_variable_warnings
+ Laser.debug_puts('>>> Finished Adding Unused Variable Warnings <<<')
# Don't need these anymore
+ Laser.debug_puts('>>> Pruning Unexecuted Blocks <<<')
prune_unexecuted_blocks
+ Laser.debug_puts('>>> Finished Pruning Unexecuted Blocks <<<')
find_yield_properties if @root.type != :program
find_raise_properties
Oops, something went wrong.

0 comments on commit 3c05335

Please sign in to comment.