furnace 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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