Permalink
Browse files

Turn '::' into internal operator :deref, and add machinery to pre-bui…

…ld scope chain and traverse it to resolve class constant names at compile time where possible
  • Loading branch information...
vidarh committed Sep 21, 2014
1 parent 3a7e275 commit 6ceb2155107c7fefcf966734ea0595003c6096a6
Showing with 156 additions and 47 deletions.
  1. +38 −2 classcope.rb
  2. +27 −39 compiler.rb
  3. +14 −5 globalscope.rb
  4. +1 −1 operators.rb
  5. +76 −0 transform.rb
View
@@ -10,7 +10,7 @@ class ClassScope < Scope
# method v-table,
# instance variables
# and class variables
attr_reader :name, :vtable, :instance_vars, :class_vars
attr_reader :vtable, :instance_vars, :class_vars
# This is the number of instance variables allowed for the class
# Class, and is used for bootstrapping. Note that it could be
@@ -32,6 +32,30 @@ def initialize(next_scope, name, offsets, superclass)
@ivaroff = @superclass ? @superclass.instance_size : 0
@instance_vars = !@superclass ? [:@__class__] : [] # FIXME: Do this properly
@class_vars = {}
@constants = {}
end
def find_constant(c)
const = @constants[c]
return const if const
return @next.find_constant(@name+"__"+c.to_s) if @next
return nil
end
def prefix
return "" if !@next
n = @next.name
return "" if n.empty?
return n + "__"
end
def local_name
@name
end
def name
prefix + @name.to_s
end
def class_scope
@@ -46,6 +70,10 @@ def add_ivar(a)
@instance_vars << a.to_sym if !@instance_vars.include?(a.to_sym)
end
def add_constant(c, v = true)
@constants[c] = v
end
def instance_size
@instance_vars.size + @ivaroff
end
@@ -58,7 +86,15 @@ def instance_size
def get_arg(a)
# Handle self
if a.to_sym == :self
return [:global,@name]
return [:global,name]
end
if (?A..?Z).member?(a.to_s[0])
if @constants.member?(a.to_sym)
return [:global,name + "__" + a.to_s]
else
return @next.get_arg(a)
end
end
# class variables.
View
@@ -36,7 +36,7 @@ class Compiler
:hash, :return,:sexp, :module, :rescue, :incr, :block,
:required, :add, :sub, :mul, :div, :eq, :ne,
:lt, :le, :gt, :ge,:saveregs, :and, :or,
:preturn, :proc, :stackframe
:preturn, :proc, :stackframe, :deref
]
Keywords = @@keywords
@@ -51,6 +51,7 @@ def initialize emitter = Emitter.new
@global_constants << :false
@global_constants << :true
@global_constants << :nil
@global_constants << :__left
@classes = {}
@vtableoffsets = VTableOffsets.new
@trace = false
@@ -245,6 +246,18 @@ def clean_method_name(name)
return cleaned
end
# Handle e.g. Tokens::Atom, which is parsed as (deref Tokens Atom)
#
# For now we are assuming statically resolvable chains, and not
# tested multi-level dereference (e.g. Foo::Bar::Baz)
#
def compile_deref(scope, left, right)
cscope = scope.find_constant(left)
raise "Unable to resolve: #{left}::#{right} statically (FIXME)" if !cscope || !cscope.is_a?(ClassScope)
get_arg(cscope,right)
end
# Compiles a function definition.
# Takes the current scope, in which the function is defined,
# the name of the function, its arguments as well as the body-expression that holds
@@ -358,6 +371,7 @@ def compile_and scope, left, right
def compile_or scope, left, right
@e.comment("compile_or: #{left.inspect} || #{right.inspect}")
# FIXME: Eek. need to make sure variables are assigned for these. Turn it into a rewrite?
compile_eval_arg(scope,[:assign, :__left, left])
compile_if(scope, :__left, :__left, right)
end
@@ -522,7 +536,7 @@ def compile_assign(scope, left, right)
end
if atype == :addr
@global_scope.globals << aparam
scope.add_constant(aparam)
@global_constants << aparam
elsif atype == :ivar
# FIXME: The register allocation here
@@ -821,52 +835,27 @@ def compile_module(scope,name, *exps)
# Takes the current scope, the name of the class as well as a list of expressions
# that belong to the class.
def compile_class(scope, name,superclass, *exps)
@e.comment("=== class #{name} ===")
superc = name == :Class ? nil : @classes[superclass]
cscope = scope.find_constant(name)
superc = @classes[superclass]
@e.comment("=== class #{cscope.name} ===")
cscope = ClassScope.new(scope, name, @vtableoffsets, superc)
@e.evict_regs_for(:self)
# FIXME: Need to be able to handle re-opening of classes
exps.each do |l2|
l2.each do |e|
if e.is_a?(Array)
if e[0] == :defm
cscope.add_vtable_entry(e[1]) # add method into vtable of class-scope to associate with class
e[3].depth_first do |exp|
exp.each do |n|
cscope.add_ivar(n) if n.is_a?(Symbol) and n.to_s[0] == ?@ && n.to_s[1] != ?@
end
end
name = cscope.name.to_sym
# The check for :Class and :Kernel is an "evil" temporary hack to work around the bootstrapping
# issue of creating these class objects before Object is initialized. A better solution (to avoid
# demanding an explicit order would be to clear the Object constant and make sure __new_class_object
#does not try to deref a null pointer
#
sscope = (name == superclass or name == :Class or name == :Kernel) ? nil : @classes[superclass]
elsif e[0] == :call && e[1] == :attr_accessor
# This is a bit presumptious, assuming noone are stupid enough to overload
# attr_accessor, attr_reader without making them do more or less the same thing.
# but the right thing to do is actually to call the method.
#
# In any case there is no actual harm in allocating the vtable
# entry.`
#
# We may do a quick temporary hack to synthesize the methods,
# though, as otherwise we need to implement the full define_method
# etc.
arr = e[1].is_a?(Array) ? e[2] : [e[2]]
arr.each {|entry|
cscope.add_vtable_entry(entry.to_s[1..-1].to_sym)
}
end
end
end
end
@classes[name] = cscope
@global_scope.globals << name
sscope = name == superclass ? nil : @classes[superclass]
ssize = sscope ? sscope.klass_size : nil
ssize = 0 if ssize.nil?
compile_exp(scope, [:assign, name.to_sym, [:sexp,[:call, :__new_class_object, [cscope.klass_size,superclass,ssize]]]])
@global_constants << name
# In the context of "cscope", "self" refers to the Class object of the newly instantiated class.
@@ -931,7 +920,6 @@ def compile_main(exp)
# We should allow arguments to main
# so argc and argv get defined, but
# that is for later.
@global_scope = GlobalScope.new(@vtableoffsets)
compile_eval_arg(@global_scope, exp)
end
end
View
@@ -5,18 +5,27 @@
# (because their value is known at compile time), but some of them are
# not. For now, we'll treat all of them as global variables.
class GlobalScope < Scope
attr_accessor :globals
attr_reader :class_scope
def initialize(offsets)
@vtableoffsets = offsets
@globals = Set.new
@globals = {}
@class_scope = ClassScope.new(self,"Object",@vtableoffsets,nil)
# Despite not following "$name" syntax, these are really global constants.
@globals << :false
@globals << :true
@globals << :nil
@globals[:false] = true
@globals[:true] = true
@globals[:nil] = true
@globals[:__left] = true
end
def add_constant(c,v = true)
@globals[c] = v
end
def find_constant(c)
@globals[c]
end
def rest?
View
@@ -111,7 +111,7 @@ def self.expect(s)
# "Fake" operator for [] following a name
"#index#" => Oper.new(100, :index, :infix),
"." => Oper.new(100, :callm, :infix, 2,2,:left),
"::" => Oper.new(100, :callm, :infix, 2,2,:left),
"::" => Oper.new(100, :deref, :infix, 2,2,:left),
".." => Oper.new(100, :range, :infix), # FIXME: Check pri - probably not right.
View
@@ -308,7 +308,83 @@ def rewrite_concat(exp)
end
end
# build_class_scopes
#
# Consider the case where I open a class, define a method that refers to an as yet undefined
# class. Then later I re-open the class and defines the earlier class as an inner class:
#
# class Foo
# def hello
# Bar.new
# end
# end
#
# class Foo
# class Bar
# end
# end
#
# To handle this case, <tt>ClassScope</tt> objects must persist across open/close of a class,
# and they do. However, to compile this to static references, I also must identify any references
# and resolve them, to be able to distinguish a possible ::Bar from ::Foo::Bar
#
# (we still need to be able to fall back to dynamic constant lookup)
#
def build_class_scopes(exps, scope = nil)
scope = @global_scope if !scope
return if !exps.is_a?(Array)
exps.each do |e|
if e.is_a?(Array)
if e[0] == :defm && scope.is_a?(ClassScope)
scope.add_vtable_entry(e[1]) # add method into vtable of class-scope to associate with class
e[3].depth_first do |exp|
exp.each do |n|
scope.add_ivar(n) if n.is_a?(Symbol) and n.to_s[0] == ?@ && n.to_s[1] != ?@
end
end
elsif e[0] == :call && e[1] == :attr_accessor
# This is a bit presumptious, assuming noone are stupid enough to overload
# attr_accessor, attr_reader without making them do more or less the same thing.
# but the right thing to do is actually to call the method.
#
# In any case there is no actual harm in allocating the vtable
# entry.`
#
# We may do a quick temporary hack to synthesize the methods,
# though, as otherwise we need to implement the full define_method
# etc.
arr = e[1].is_a?(Array) ? e[2] : [e[2]]
arr.each {|entry|
scope.add_vtable_entry(entry.to_s[1..-1].to_sym)
}
elsif e[0] == :class || e[0] == :module
superclass = e[2]
superc = @classes[superclass.to_sym]
cscope = @classes[e[1].to_sym]
cscope = ClassScope.new(scope, e[1], @vtableoffsets, superc) if !cscope
@classes[cscope.name.to_sym] = cscope
@global_scope.add_constant(cscope.name.to_sym,cscope)
scope.add_constant(e[1].to_sym,cscope)
build_class_scopes(e[3], cscope)
elsif e[0] == :sexp
else
e[1..-1].each do |x|
build_class_scopes(x,scope)
end
end
end
end
end
def preprocess exp
# The global scope is needed for some rewrites
@global_scope = GlobalScope.new(@vtableoffsets)
build_class_scopes(exp)
rewrite_concat(exp)
rewrite_range(exp)
rewrite_strconst(exp)

0 comments on commit 6ceb215

Please sign in to comment.