furnace 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rbx/
data/.rvmrc ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
+ # development environment upon cd'ing into the directory
5
+
6
+ # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
+ environment_id="rbx-head@furnace"
8
+
9
+ #
10
+ # Uncomment following line if you want options to be set only for given project.
11
+ #
12
+ # PROJECT_JRUBY_OPTS=( --1.9 )
13
+
14
+ #
15
+ # First we attempt to load the desired environment directly from the environment
16
+ # file. This is very fast and efficient compared to running through the entire
17
+ # CLI and selector. If you want feedback on which environment was used then
18
+ # insert the word 'use' after --create as this triggers verbose mode.
19
+ #
20
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
21
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
22
+ then
23
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
24
+
25
+ if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
26
+ then
27
+ . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
28
+ fi
29
+ else
30
+ # If the environment file has not yet been created, use the RVM CLI to select.
31
+ if ! rvm --create "$environment_id"
32
+ then
33
+ echo "Failed to create RVM environment '${environment_id}'."
34
+ return 1
35
+ fi
36
+ fi
37
+
38
+ #
39
+ # If you use an RVM gemset file to install a list of gems (*.gems), you can have
40
+ # it be automatically loaded. Uncomment the following and adjust the filename if
41
+ # necessary.
42
+ #
43
+ # filename=".gems"
44
+ # if [[ -s "$filename" ]]
45
+ # then
46
+ # rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
47
+ # fi
48
+
49
+ # If you use bundler, this might be useful to you:
50
+ # if command -v bundle && [[ -s Gemfile ]]
51
+ # then
52
+ # bundle install
53
+ # fi
54
+
55
+ if [[ $- == *i* ]] # check for interactive shells
56
+ then
57
+ echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
58
+ else
59
+ echo "Using: $GEM_HOME" # don't use colors in interactive shells
60
+ fi
61
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in furnace.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/furnace ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'furnace'
4
+
5
+ load ARGV[0]
6
+
7
+ pipeline = Furnace::Transform::Pipeline.new(*[
8
+ Furnace::Transform::Rubinius::ASTBuild.new,
9
+ Furnace::Transform::Rubinius::ASTNormalize.new,
10
+
11
+ Furnace::Transform::Generic::LabelNormalize.new,
12
+ Furnace::Transform::Generic::CFGBuild.new,
13
+ Furnace::Transform::Generic::CFGNormalize.new,
14
+
15
+ Furnace::Transform::Generic::ANFBuild.new,
16
+
17
+ Furnace::Transform::Optimizing::FoldConstants.new,
18
+ ])
19
+
20
+ cfg, = pipeline.run(method(:main).executable)
21
+
22
+ puts cfg.to_graphviz
data/furnace.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "furnace/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "furnace"
7
+ s.version = Furnace::VERSION
8
+ s.authors = ["Peter Zotov"]
9
+ s.email = ["whitequark@whitequark.org"]
10
+ s.homepage = "http://github.com/whitequark/furnace"
11
+ s.summary = %q{A static Ruby compiler}
12
+ s.description = %q{Furnace aims to compile Ruby code into small static } <<
13
+ %q{executables by restricting its metaprogramming features.}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,6 @@
1
+ module Furnace
2
+ module ANF
3
+ class Edge < Struct.new(:source, :target, :param)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,60 @@
1
+ module Furnace
2
+ module ANF
3
+ class Graph
4
+ attr_reader :nodes, :edges
5
+ attr_accessor :root
6
+
7
+ def initialize
8
+ @root = nil
9
+ @nodes = Set.new
10
+ @edges = Set.new
11
+ end
12
+
13
+ def find(label)
14
+ @nodes.find { |node| node.label == label }
15
+ end
16
+
17
+ def eliminate_dead_code
18
+ live_set = search
19
+ @nodes &= live_set
20
+ end
21
+
22
+ def search
23
+ seen_set = Set.new
24
+ work_set = Set.new
25
+
26
+ work_set.add @root
27
+
28
+ while work_set.any?
29
+ node = work_set.first
30
+ work_set.delete node
31
+ seen_set.add node
32
+
33
+ yield node if block_given?
34
+
35
+ node.leaving_edges.map(&:target).each do |target|
36
+ work_set.add target unless seen_set.include? target
37
+ end
38
+ end
39
+
40
+ seen_set
41
+ end
42
+
43
+ def to_graphviz
44
+ Graphviz.new do |graph|
45
+ @nodes.each do |node|
46
+ graph.node node.object_id, node.to_human_readable
47
+
48
+ case node
49
+ when ANF::IfNode
50
+ graph.edge node.object_id, node.leaving_edge(true).target.object_id, "true"
51
+ graph.edge node.object_id, node.leaving_edge(false).target.object_id, "false"
52
+ when ANF::LetNode, ANF::InNode
53
+ graph.edge node.object_id, node.leaving_edge.target.object_id
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ module Furnace
2
+ module ANF
3
+ class IfNode < Node
4
+ attr_reader :condition
5
+
6
+ def initialize(graph, condition)
7
+ super(graph)
8
+
9
+ @condition = condition
10
+ end
11
+
12
+ def to_human_readable
13
+ "if\n#{humanize @condition}"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Furnace
2
+ module ANF
3
+ class InNode < Node
4
+ attr_reader :expressions
5
+
6
+ def initialize(graph, expressions)
7
+ super(graph)
8
+
9
+ @expressions = expressions
10
+ end
11
+
12
+ def to_human_readable
13
+ "in\n#{@expressions.map { |e| "#{e.to_sexp(1)}" }.join "\n"}"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ module Furnace
2
+ module ANF
3
+ class LetNode < Node
4
+ attr_reader :arguments
5
+
6
+ def initialize(graph, arguments)
7
+ super(graph)
8
+
9
+ @arguments = arguments
10
+ end
11
+
12
+ def try_eliminate
13
+ if identity?
14
+ entering_edges.each do |edge|
15
+ edge.target = leaving_edge.target
16
+ end
17
+ end
18
+ end
19
+
20
+ def identity?
21
+ @arguments.reduce(true) { |r, (k, v)| r && (v === k) }
22
+ end
23
+
24
+ def try_propagate
25
+ end
26
+
27
+ def static?(node)
28
+ [ NilClass, TrueClass, FalseClass, Fixnum, Symbol,
29
+ AST::LocalVariable, AST::InstanceVariable ].include? node.class
30
+ end
31
+
32
+ def to_human_readable
33
+ "let\n#{@arguments.map { |k, v| " #{k} = #{humanize v}" }.join "\n"}"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ module Furnace
2
+ module ANF
3
+ class Node
4
+ attr_reader :graph
5
+
6
+ def initialize(graph)
7
+ @graph = graph
8
+ end
9
+
10
+ def leaving_edges
11
+ @graph.edges.select { |edge| edge.source == self }
12
+ end
13
+
14
+ def leaving_edge(param=nil)
15
+ @graph.edges.find { |edge| edge.source == self && edge.param == param }
16
+ end
17
+
18
+ def entering_edges
19
+ @graph.edges.select { |edge| edge.target == self }
20
+ end
21
+
22
+ def humanize(node)
23
+ if node.is_a? AST::Node
24
+ node.to_sexp
25
+ else
26
+ node.inspect
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module Furnace
2
+ module ANF
3
+ class ReturnNode < Node
4
+ attr_reader :result
5
+
6
+ def initialize(graph, result)
7
+ super(graph)
8
+
9
+ @result = result
10
+ end
11
+
12
+ def to_human_readable
13
+ "return\n#{humanize @result}"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,70 @@
1
+ module Furnace
2
+ module AST
3
+ class Node
4
+ attr_accessor :type, :parent, :children, :metadata
5
+
6
+ def initialize(type, children=[], metadata={})
7
+ @type, @children, @metadata = type.to_sym, children, metadata
8
+ end
9
+
10
+ def update(type, children=nil, metadata={})
11
+ @type = type
12
+ @children = children || @children
13
+
14
+ # If something non-nil is passed, including default value, then merge.
15
+ # Else, clear metadata store.
16
+ if metadata
17
+ @metadata.merge!(metadata)
18
+ else
19
+ @metadata = {}
20
+ end
21
+
22
+ self
23
+ end
24
+
25
+ def index
26
+ parent.children.find_index(self)
27
+ end
28
+
29
+ def next
30
+ parent.children[index + 1]
31
+ end
32
+
33
+ def prev
34
+ parent.children[index - 1]
35
+ end
36
+
37
+ def to_s
38
+ "(#{fancy_type} ...)"
39
+ end
40
+
41
+ def to_sexp(indent=0)
42
+ str = "#{" " * indent}(#{fancy_type}"
43
+
44
+ children.each do |child|
45
+ if child.is_a? Node
46
+ str << "\n#{child.to_sexp(indent + 1)}"
47
+ else
48
+ str << " #{child.inspect}"
49
+ end
50
+ end
51
+
52
+ str << ")"
53
+
54
+ str
55
+ end
56
+ alias :inspect :to_sexp
57
+
58
+ protected
59
+
60
+ def fancy_type
61
+ dasherized = @type.to_s.gsub('_', '-')
62
+ if @metadata[:label]
63
+ "#{@metadata[:label]}:#{dasherized}"
64
+ else
65
+ dasherized
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,36 @@
1
+ module Furnace
2
+ module AST
3
+ class SymbolicNode
4
+ def initialize(name)
5
+ @name = name.to_sym
6
+ end
7
+
8
+ def to_sym
9
+ @name
10
+ end
11
+
12
+ def ===(name)
13
+ @name == name.to_sym
14
+ end
15
+
16
+ def inspect
17
+ @name.to_s
18
+ end
19
+ end
20
+
21
+ class MethodName < SymbolicNode
22
+ def inspect
23
+ ".#{@name}"
24
+ end
25
+ end
26
+
27
+ class LocalVariable < SymbolicNode
28
+ def inspect
29
+ "%#{@name}"
30
+ end
31
+ end
32
+
33
+ class Constant < SymbolicNode
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ module Furnace
2
+ module AST
3
+ module Visitor
4
+ def visit(node)
5
+ node.children.map! do |child|
6
+ if child.is_a? Node
7
+ visit child
8
+
9
+ if child.type == :expand
10
+ child = child.children
11
+ end
12
+ end
13
+
14
+ child
15
+ end
16
+
17
+ node.children.flatten!
18
+
19
+ node.children.delete_if do |child|
20
+ if child.is_a? Node
21
+ child.parent = node
22
+
23
+ child.type == :remove
24
+ end
25
+ end
26
+
27
+ # Invoke a specific handler
28
+ on_handler = :"on_#{node.type}"
29
+ if respond_to? on_handler
30
+ send on_handler, node
31
+ end
32
+
33
+ # Invoke a generic handler
34
+ if respond_to? :on_any
35
+ send :on_any, node
36
+ end
37
+
38
+ node
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ module Furnace
2
+ module CFG
3
+ class Edge
4
+ attr_accessor :source_operation, :source_label, :target_label
5
+
6
+ def initialize(cfg, source_operation, source_label, target_label)
7
+ @cfg, @source_operation, @source_label, @target_label =
8
+ cfg, source_operation, source_label, target_label
9
+ end
10
+
11
+ def source
12
+ @cfg.find_node(@source_label)
13
+ end
14
+
15
+ def target
16
+ @cfg.find_node(@target_label) if @target_label
17
+ end
18
+
19
+ def source=(node)
20
+ @source_label = node.label
21
+ end
22
+
23
+ def target=(node)
24
+ if node
25
+ @target_label = node.label
26
+ else
27
+ @target_label = nil
28
+ end
29
+ end
30
+
31
+ def inspect
32
+ "<#{@source_label.inspect} -> #{@target_label.inspect}>"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,59 @@
1
+ module Furnace
2
+ module CFG
3
+ class Graph
4
+ attr_reader :nodes, :edges
5
+
6
+ def initialize
7
+ @nodes = Set.new
8
+ @edges = Set.new
9
+
10
+ @pending_label = nil
11
+ @pending_operations = []
12
+ end
13
+
14
+ def find_node(label)
15
+ if node = @nodes.find { |n| n.label == label }
16
+ node
17
+ else
18
+ raise "Cannot find CFG node #{label}"
19
+ end
20
+ end
21
+
22
+ def expand(label, operation)
23
+ @pending_label ||= label
24
+ @pending_operations << operation
25
+ end
26
+
27
+ def transfer(targets)
28
+ return unless @pending_label
29
+
30
+ @nodes << CFG::Node.new(self, @pending_label, @pending_operations)
31
+
32
+ targets.each do |operation, target|
33
+ @edges << CFG::Edge.new(self, operation, @pending_label, target)
34
+ end
35
+
36
+ @pending_label = nil
37
+ @pending_operations = []
38
+ end
39
+
40
+ def to_graphviz
41
+ Graphviz.new do |graph|
42
+ @nodes.each do |node|
43
+ graph.node node.label, node.operations.map(&:inspect).join("\n")
44
+ end
45
+
46
+ @edges.each do |edge|
47
+ if edge.source_operation.nil?
48
+ label = "~"
49
+ else
50
+ label = edge.source_operation
51
+ end
52
+
53
+ graph.edge edge.source_label, edge.target_label, label
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,39 @@
1
+ module Furnace
2
+ module CFG
3
+ class Node
4
+ attr_reader :label, :operations
5
+
6
+ def initialize(cfg, label, operations)
7
+ @cfg, @label, @operations = cfg, label, operations
8
+ end
9
+
10
+ def entering_edges
11
+ @cfg.edges.select { |e| e.target == self }
12
+ end
13
+
14
+ def leaving_edges
15
+ @cfg.edges.select { |e| e.source == self }
16
+ end
17
+
18
+ def leaving_edge(source)
19
+ leaving_edges.find { |e| e.source_operation == source }
20
+ end
21
+
22
+ def default_leaving_edge
23
+ leaving_edge(nil)
24
+ end
25
+
26
+ def ==(other)
27
+ self.label == other.label
28
+ end
29
+
30
+ def inspect
31
+ if @label
32
+ "<#{@label}:#{@operations.map(&:inspect).join ", "}>"
33
+ else
34
+ "<!exit>"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ module Furnace
2
+ class Graphviz
3
+ def initialize
4
+ @code = "digraph {\n"
5
+ @code << "node [labeljust=l,nojustify=true,fontname=monospace];"
6
+ @code << "rankdir=TB;"
7
+
8
+ yield self
9
+
10
+ @code << "}"
11
+ end
12
+
13
+ def node(name, content)
14
+ content.gsub!("&", "&amp;")
15
+ content.gsub!(">", "&gt;")
16
+ content.gsub!("<", "&lt;")
17
+ content = content.lines.map { |l| %Q{<tr><td align="left">#{l}</td></tr>} }.join
18
+
19
+ @code << %Q{#{name.inspect} [shape=box,label=<<table border="0">#{content}</table>>];\n}
20
+ end
21
+
22
+ def edge(from, to, label="")
23
+ @code << %Q{#{from.inspect} -> #{to.inspect} [label=#{label.inspect}];\n}
24
+ end
25
+
26
+ def to_s
27
+ @code
28
+ end
29
+ end
30
+ end