new world
[rubydium.git] / baby / clean.rb
blob83a148c16af136f91db7f37c4206b11b15add075
1 require 'nanovm'
2 require 'nanovm/nano.rb'
4 # $: << "../ruby_parser-1.0.0/lib"
5 # require "ruby_parser"
6 require "rubygems"
7 require 'parse_tree'
9 require "./debug.rb"
10 require 'src/misc.rb'
11 require 'src/bench.rb'
13 require 'pp'
14 require '3rdparty/breakpoint/breakpoint'
15 require 'digest/md5'
16 require 'pstore'
17 require 'yaml/store.rb' # including this shaves a few seconds of testing, why?
18 require 'enumerator'
20 def pause
21    STDIN.gets
22 end
24 Context.debug = $nanovm_debug
26 class ProfFuncWithMd
28    def self.md_metadata func
29       func.metadata = {}
30    end
32    def self.md_get_id func
33       func.metadata[:path_range].first
34    end
36    def self.md_get_path_range func
37       func.metadata[:path_range]
38    end
40    def self.md_set_path_range func, new_path_range
41       func.metadata[:path_range] = new_path_range
42    end
44    def self.md_init_and_add_to_caller_map_and_return_size func, str
45       func.metadata[:caller_map] ||= ([nil] * 8)
46       func.metadata[:caller_map] << str
47       func.metadata[:caller_map].size
48    end
50    def self.md_add_to_lookup_for_debug func, name
51       func.metadata[:lookup_for] ||= []
52       func.metadata[:lookup_for]  << [name]
53    end
55    def self.md_add_to_static_scope func, scope, scope_id, sym
56       func.metadata[:used_scope_template] = scope
57       func.metadata[:used_static_scopes] ||= []
58       func.metadata[:used_static_scopes]  << [scope_id, sym]
59    end
61    def self.md_find_in_caller_map func, slow_id
62       (func.metadata[:caller_map].index [:id, slow_id]) + 1
63    end
65    def self.md_set_next_id func, curr_id
66       func.metadata[:next_id] = curr_id
67    end
69    def self.md_set_is_bouncer func
70       func.metadata[:is_bouncer] = true
71    end
73    def self.md_add_assumption func, obj
74       func.metadata[:assumptions] << obj
75    end
77    def self.md_set_specifalized func, on
78       func.metadata[:specialized] = on
79    end
81    def self.md_add_to_static_continuation_points func, next_id
82       func.metadata[:static_continuation_point] ||= []
83       func.metadata[:static_continuation_point] << next_id
84    end
86    def self.md_add_to_bouncing_cont_points func, next_id
87       func.metadata[:bouncing_continuation_point] ||= []
88       func.metadata[:bouncing_continuation_point] << next_id
89    end
91    def self.md_made_scope func
92       func.metadata[:made_scope] = true
93    end
95    def self.md_made_scope? func
96       func.metadata[:made_scope]
97    end
99    def self.md_init_or_increase_bouncer_count func
100       func.metadata[:func] ||= 0
101       func.metadata[:func] += 1
102    end
103    
104    def self.md_unset_made_scope func
105       func.metadata.delete :made_scope
106    end
108    def self.md_init_init_func func
109       func.metadata[:init_func] = []
110    end
112    def self.md_set_atom_main_label func, atom_main_label 
113       func.metadata[:atom_main_label] = atom_main_label 
114    end
115    def self.md_get_statically_dispatches_to func 
116       func.metadata[:statically_dispatches_to]
117    end
119    def self.md_get_static_continuation_point func
120       func.metadata[:static_continuation_point]
121    end
123    def self.md_get_slow_dispatches func
124       func.metadata[:slow_dispatch_to]
125    end
127    def self.md_set_last_hit_count func, count
128       func.metadata[:last_hit_count] = count
129    end
131    def self.md_inc_rebuild_count func
132       rebuild_count = func.metadata[:rebuild_count] || 0
133       func.metadata[:rebuild_count] = rebuild_count + 1
134    end
136    def self.md_get_num_params func
137       func.metadata[:num_params]
138    end
140    def self.md_get_prev_id func
141       func.metadata[:prev_id]
142    end
144    def self.md_get_was_generated_by func
145       func.metadata[:was_generated_by]
146    end
147    
148    def self.md_get_creates_bouncer_to func
149       func.metadata[:creates_bouncer_to]
150    end
152    def self.md_get_next_ids func
153       func.metadata[:next_ids]
154    end
156    def self.md_has_slow_dispatches func
157       func.metadata.has_key? :slow_dispatch_to
158    end
160    def self.md_get_next_id func
161       func.metadata[:next_id]
162    end
164    def self.md_get_last_hit_count func
165       func.metadata[:last_hit_count]
166    end
168    def self.md_get_assumptions func
169       func.metadata[:assumptions]
170    end
172    def self.md_get_generated_by func
173       func.metadata[:bouncer_generated_by]
174    end
176    def self.md_set_generated_by func, bouncer_generated_by
177       func.metadata[:bouncer_generated_by] = bouncer_generated_by
178    end
180    def self.md_has_no_bouncer_generated_annotation func
181       func.metadata[:bouncer_generated_by].nil?
182    end
184    def self.md_has_init_func func
185       func.metadata.has_key?(:init_func)
186    end
188    def self.md_is_not_real? func
189       (func.metadata[:assumptions].include? [:not_real])
190    end
192    def self.md_mark_not_real func
193       func.metadata[:assumptions] << [:not_real]
194    end
196    def self.md_no_assumptions? func
197       (func.metadata[:assumptions].empty?)
198    end
200    def self.md_not_all_static_lookups? func
201       (func.metadata[:lookup_for] and !func.metadata[:lookup_for].empty?)
202    end
204    # currently no setter!
205    def self.md_optimal_return? func
206       (!func.metadata[:optimal_return].nil? and func.metadata[:optimal_return] == true)
207    end
209    def self.md_lookups func
210       func.metadata[:lookup_for]
211    end
213    def self.md_atom_main_label func
214       func.metadata[:atom_main_label]
215    end
217    def self.md_set_with_initial func, symbol, initial, &new_value
218       func.metadata[symbol] ||= initial
219       func.metadata[symbol] = (new_value.call func.metadata[symbol])
220    end
222    def self.md_force_data_inspect func
223       func.metadata[:force_data_inspect] = true
224    end
226    def self.md_forced_data_inspect func
227       func.metadata[:force_data_inspect]
228    end
230    def self.md_set_create_scope_template func, scope
231       func.metadata[:create_scope_template] = scope
232    end
233    
234    def self.metadata_filter notes
235       notes.reject { |(a,b)| (a == :caller_map) }
236    end
238    def self.md_inspect notes
239       (self.metadata_filter notes).inspect
240    end
244 class ProfilingFunction < Function
245    attr_accessor :mem_ctx
246    def initialize *k
247       @func = super *k
248       @func
249    end
250    def inspect
251       path_range = ProfFuncWithMd::md_get_path_range(@func).inspect
252       "PFunction::[#{path_range}]- #{ProfFuncWithMd::md_inspect @func.metadata}>"
253    end
254    def self.record_hit func, str
255       top_val = ProfFuncWithMd::md_init_and_add_to_caller_map_and_return_size func, str
256       func.insn_hit NN::mk_constant(func, :int, top_val)
257    end
258    instance_methods.each {
259       |meth|
260       conditional = (meth =~ /insn_/ and meth != "insn_hit")
261       if conditional
262          define_method(meth) {
263             |*args|
264             fail "no mem_ctx!!!" unless mem_ctx
265             appending_not_hacking = (@func.pos == @func.size)
266             if appending_not_hacking and $profile
267                ProfilingFunction.record_hit(@func, (caller[1..1].join ", "))
268             end
269             if appending_not_hacking and meth == "insn_call_indirect_vtable_blah"
270                trace_stack_ptr = Value.new
271                self.insn_load_elem trace_stack_ptr, mem_ctx.stack_mem, 
272                   NN::mk_constant(self, :int, 7), :void_ptr
273                trace_stack = RbStack.new self, trace_stack_ptr, :alt
274                old_func_ptr, new_func_ptr = Value.new, Value.new
275                self.insn_load_elem new_func_ptr, mem_ctx.stack_mem, 
276                   NN::mk_constant(self, :int, 8), :void_ptr
277                self.insn_load_elem old_func_ptr, mem_ctx.stack_mem, 
278                   NN::mk_constant(self, :int, 9), :void_ptr
279                trace_stack.push new_func_ptr, old_func_ptr
280             end
281             pos = @func.pos
282             super(*args)
283             return pos
284          }
285       end
286    }
289 Annotation = Struct.new :type
291    Unset = Class.new
293    class TreeState
294       attr_accessor :path, :sexp_elt, :curr_id
295       def initialize crawler, path2anon_block, curr_id
296          @crawler = crawler
297          @curr_id = curr_id
298          @path    = crawler.id2path curr_id
299          @sexp_elt = crawler.find_path_sexp @path
300          @anon_block = Unset
301          @path2anon_block = path2anon_block 
302       end
303       def anon_block
304          @anon_block = @path2anon_block[@path] if @anon_block == Unset
305          @anon_block
306       end
307    end
309 if true
310    class Array
311       def sexp_body
312          return self[1..-1]
313       end
314    end
315    def is_a_sexp? elt
316       # complete hack!!!
317       elt.is_a? Array and elt[0].is_a? Symbol
318    end
319    def s *foo
320       return [*foo]
321    end
322 else
323    def is_a_sexp? elt
324       elt.is_a? Sexp
325    end
326    class ParseTree
327       def self.translate src
328          RubyParser.new.parse(src)
329       end
330    end
332 class CodeTree
333    attr_accessor :ast, :id2path_hash
334    def initialize sexp
335       @sexp         = ast_hacks sexp
336       @id2path_hash = {}
337       @associated   = {}
338       preload_ast @sexp, []
339       @id2path_hash[@id2path_hash.length] = []
340    end
341    def ast_hacks sexp
342      insides = sexp.sexp_body.map { |elt| is_a_sexp?(elt) ? ast_hacks(elt) : elt }
343      # turn blocks with > 2 children into [:block, child1, [:block, child2, child3]]
344      if sexp.first == :block && sexp[1].first == :dasgn_curr
345        # emulate legacy node, not produced by ParseTree and can probably be removed later on
346        return s(:block, 
347                 s(:dasgn_curr_hacked, sexp[1][1], nil), 
348                 (ast_hacks s(:block, s(:dasgn_curr_hacked, *sexp[1][1..-1]), *sexp[2..-1])))
349      elsif sexp.first == :block && sexp.length > 3 # when more than two elements in block
350        return s(:block, insides[0], ast_hacks(s(:block, *sexp[2..-1])))
351      elsif sexp.first == :scope
352        return s(:scope, [], *insides)
353      elsif sexp.first == :class
354        return s(:class, s(:colon2, nil, sexp[1]), *insides[1..-1])
355      elsif sexp.first == :while
356        return s(:while, *insides[0..-2])
357      elsif sexp.first == :defn
358        blocks = sexp[2][1][2..-1]
359        if blocks.length > 1
360          blocks = [ast_hacks(s(:block, *blocks))]
361        end
362        return s(:defn_hacked, insides[0], sexp[2][1][1].sexp_body.to_a, *blocks)
363      else
364        return s(sexp.first, *insides)
365      end
366    end
367    def self.find_subpaths path_list, subpath_root, inclusive = false
368       bench("find_subpaths") {
369          return path_list.find_all {
370             |path|
371             prefix = path.slice 0, subpath_root.length
372             (path.length > subpath_root.length) \
373         and (prefix === subpath_root)
374          } + (inclusive ? [subpath_root] : [])
375       }
376    end
377    def preload_ast sexp, path
378       # puts "preload_ast: #{path.inspect}: #{ast.inspect}\n\t: #{sexp.inspect}"
379       rest = (sexp.empty?) ? [] : sexp[1..-1]
380       s_order_arr = rest.inject([]) { |oarr, elt| oarr << [elt, oarr.size]; oarr }
381       s_order_arr << s_order_arr.slice!(0) if (sexp.first == :call)
382       s_order_arr.each {
383          |(inner_sexp, idx)|
384          new_path = path + [idx]
385          if inner_sexp.class.to_s =~ /(Sexp|Array)/
386             preload_ast inner_sexp, new_path
387             if (sexp.first == :call) and idx == 0 # push before the first param
388                # FIXME - extract
389                assoc_path = path + [-1]
390                fail "already associated something with #{assoc_path.inspect}" if @associated.has_key? assoc_path
391                @associated[assoc_path] = :push_block
392                @id2path_hash[@id2path_hash.length] = assoc_path
393             end
394          end
395          @id2path_hash[@id2path_hash.length] = new_path
396       }
397       if [:vcall,:fcall].include? sexp.first
398          # FIXME - extract
399          assoc_path = path + [-1]
400          fail "already associated something with #{assoc_path.inspect}" if @associated.has_key? assoc_path
401          @associated[assoc_path] = :push_block
402          @id2path_hash[@id2path_hash.length] = assoc_path
403       end
404    end
405    def path2id path
406       @id2path_hash.index path
407    end
408    def id2path id
409       @id2path_hash[id]
410    end
411    def find_path_sexp to_find, sexp = nil, path = nil
412       # puts "sexp finding:#{to_find.inspect} child-of:#{sexp.inspect} current:#{path.inspect}"
413       return @associated[to_find] if @associated.has_key? to_find
414       sexp = @sexp if sexp.nil?
415       path = []    if path.nil?
416       return sexp  if to_find.empty?
417       arr = is_a_sexp?(sexp) ? sexp.sexp_body : sexp
418       arr.each_with_index {
419          |inner_sexp, idx|
420          # if the current element of to_find == the current idx
421          if idx == to_find[path.length]
422             if (path.length + 1) == to_find.length
423                 return inner_sexp
424             else
425                 return find_path_sexp(to_find, inner_sexp, path + [idx])
426             end
427          end
428       }
429       raise "couldn't find index! find:#{to_find.inspect} childof:#{sexp.inspect} curr:#{path.inspect}"
430    end
431    def paths2orderdesc paths
432       paths.collect { 
433          |path|
434          <<EOF
435    #{path.inspect} (#{path2id path}),
436       # => #{find_path_sexp(path).inspect}
438       }.join ""
439    end
442 # active profiling
443 class AstCursor
444    attr_accessor :ast_id
445    def initialize ast_id
446       @ast_id = ast_id
447    end
448    def hits_for_id func, slow_id
449       idx = ProfFuncWithMd::md_find_in_caller_map func, slow_id
450       idx.nil? ? 0 : func.profile_hash[idx]
451    end
452    def id_hit? old_functions, get_count = false
453       count = 0
454       old_functions.each {
455          |func|
456          next if ProfFuncWithMd::md_get_path_range(func).nil? or func.profile_hash.nil?
457          if ProfFuncWithMd::md_get_path_range(func).include? @ast_id
458             count += hits_for_id func, @ast_id
459             break if !get_count and count > 0
460          end
461       }
462       get_count ? count : (count > 0)
463    end
466 class LoggedHash < Hash
467    def initialize name
468       @name = name
469    end
470    def []= a, b
471       puts cyan("Hash::#{@name} -- setting key #{a.inspect} with value #{b.inspect}")
472       super a, b
473    end
474    def [] a
475       tmp = super a
476       puts cyan("Hash::#{@name} -- (key #{a} -> #{tmp})")
477       return tmp
478    end
481 module NN
482    def self.mk_type func, type_sym
483       fail "sorry #{type_sym.inspect} is an invalid type!" unless Typing::ID_MAP.has_key? type_sym
484       return mk_constant(func, :int, Typing::ID_MAP[type_sym])
485    end
486    def self.mk_constant func, constant_type, integer
487       return_value = Value.new
488       func.fill_value_with_constant return_value, constant_type, integer
489       return_value 
490    end
491    def self.mk_bytearray func, length
492       size = Value.new
493       func.fill_value_with_constant size, :int, length
494       alloced_global_addr = Value.new
495       func.insn_call_alloc_bytearray alloced_global_addr, size
496       alloced_global_addr 
497    end
500 module Typing
501    ID_MAP = [
502       :nil, :bool, :int, :type, :block, :const, :bytearray, :undef, :multi_arg
503    ].inject({}) { |h,s| h.merge({s=>h.size+1}) }
506 module PostProcs
507    CHAR = proc { |id| id.chr }
508    ID   = proc { |id| id.id2name }
509    NULL = proc { |id| id.to_s }
510    TYPE = proc { |type_id| Typing::ID_MAP.index(type_id).to_s }
513 RuntimePrintCallback = Struct.new :postproc, :value # rename - RuntimePrinter
515 class DebugLogger
516    $message_hash, $message_hash_post_proc = {}, {}
518    def self.add_message id, string, &block
519       $message_hash[id] = string
520       if !block.nil?
521          $message_hash_post_proc[id] = block
522       end
523    end
525    def self.runtime_print_string func, *values, &block
526       if values.first.is_a? Symbol
527          stream = values.shift
528          return unless check_dbg(stream)
529       end
530       # puts "GOING TO ADD SOMETHING ARGH! - #{values.inspect}"
531       values.unshift "RT: " if $debug
532       values.each {
533          |value|
534          if value.is_a? String
535             DebugLogger::add_message value.object_id, value, &block
536             func.insn_call_print_int NN::mk_constant(func, :int, value.object_id)
537          elsif value.is_a? RuntimePrintCallback
538             DebugLogger::add_message value.postproc.object_id, "", &value.postproc
539             func.insn_call_print_int NN::mk_constant(func, :int, value.postproc.object_id)
540             if !value.value.nil?
541                func.insn_call_print_int value.value 
542             else
543                func.insn_call_print_int NN::mk_constant(func, :int, 0)
544             end
545          else
546             DebugLogger::add_message value.object_id, "", &PostProcs::NULL
547             func.insn_call_print_int NN::mk_constant(func, :int, value.object_id)
548             func.insn_call_print_int value
549          end
550       }
551    end
554 class MemContext
555    attr_accessor :stack_mem, :return_stack_mem, :all_locals_mem,
556       :locals_mem, :return_rbstack, :locals_dict
557    def initialize stack_mem, return_stack_mem, all_locals_mem, locals_mem
558       @stack_mem, @return_stack_mem, @all_locals_mem, @locals_mem = \
559          stack_mem, return_stack_mem, all_locals_mem, locals_mem 
560    end
561    def flush
562       @return_rbstack.flush if @return_rbstack
563       @locals_dict.flush if @locals_dict
564    end
565    def can_flush?
566       (!@return_rbstack.nil? || !@locals_dict.nil?)
567    end
570 class Comparison
571    def self.right?
572       ARGV.include? "--right"
573    end
574    def self.left?
575       ARGV.include? "--left"
576    end
579 module Comparisons
580    def right?
581       Comparison.right?
582    end
583    def left?
584       Comparison.left?
585    end
588    class RbStack
589       def initialize func, mem, sym, clever = false
590          @virtual_stack = []
591          @func, @mem, @sym, @clever = func, mem, sym, clever
592          @clever = false if (ARGV.include? "--slow")
593       end
594       def pop_stack
595          tmp = @virtual_stack
596          @virtual_stack = []
597          tmp
598       end
599       def push_stack stack
600          fail "push_stack called with an already used stack!?" unless @virtual_stack.empty?
601          @virtual_stack = stack
602       end
603       def stack_sym_info
604          case @sym
605          when :ret
606             name = "return_stack"
607             postproc = PostProcs::TYPE
608          else
609             name = "unknown"
610             postproc = PostProcs::NULL
611          end
612          return name, postproc
613       end
614       def push value, type
615          puts "push_to_stack - #{caller[0..1].join " -- "}" if check_dbg(:rt_primitives)
616       if @clever
617 #        puts "#{self}: PUSHING on to the stack"
618          @virtual_stack.push [value, type]
619 #        puts "#{self}: virtual stack -> #{@virtual_stack.inspect}"
620          return
621       end
622          push_raw value, type
623       end
624       def push_raw value, type
625 #        puts "push_raw!!!"
626          orig_stack_position = Value.new
627          @func.insn_load_elem orig_stack_position, @mem, NN::mk_constant(@func, :int, 0), :int
628          temp = Value.new
629          @func.insn_add temp, orig_stack_position, NN::mk_constant(@func, :int, 1)
630          @func.insn_store_elem @mem, temp, value
631          # type is high
632          @func.insn_add temp, orig_stack_position, NN::mk_constant(@func, :int, 2)
633          @func.insn_store_elem @mem, temp, type
634          if check_dbg(:rt_stack)
635             name, postproc = stack_sym_info
636             DebugLogger::runtime_print_string @func, name, ".push( ", temp, " => ", value,
637                   " : type ", RuntimePrintCallback.new(postproc, type), " )\n"
638          end
639          @func.insn_store_elem @mem, NN::mk_constant(@func, :int, 0), temp
640          orig_stack_position
641       end
642       def pop
643          puts "pop_from_stack - #{caller.first}" if check_dbg(:rt_primitives)
644       if @clever
645          if @virtual_stack.empty?
646             # TODO - find out if its valid that this is frequently the clause followed true!
647 #           puts "#{self}: POPPING from below the stack!"
648          else
649 #           puts "#{self}: A Normal Pop"
650             value, type = *(@virtual_stack.pop)
651 #           puts "#{self}: virtual stack -> #{@virtual_stack.inspect}"
652             return value, type
653          end
654       end
655 #        puts "pop_raw!!!"
656          orig_stack_position, type, value = Value.new, Value.new, Value.new
657          @func.insn_load_elem orig_stack_position, @mem, NN::mk_constant(@func, :int, 0), :int
658          # FIXME - rename temp to new_position
659          temp = Value.new
660          # type is high
661          @func.insn_load_elem type, @mem, orig_stack_position, :int
662          @func.insn_sub temp, orig_stack_position, NN::mk_constant(@func, :int, 1)
663          @func.insn_load_elem value, @mem, temp, :int
664          @func.insn_sub temp, orig_stack_position, NN::mk_constant(@func, :int, 2)
665          if check_dbg(:rt_stack)
666             name, postproc = stack_sym_info
667             zero = NN::mk_constant(@func, :int, 0)
668             lt_result = Value.new
669             skip_fail = Label.new
670             DebugLogger::runtime_print_string @func, name, ".pop( ", temp, " => ", value,
671                " : type ", RuntimePrintCallback.new(postproc, type), " )\n"
672             @func.insn_lt lt_result, temp, zero
673             @func.insn_branch_if_not lt_result, skip_fail
674             @func.insn_fail
675             @func.insn_label skip_fail
676          end
677          @func.insn_store_elem @mem, NN::mk_constant(@func, :int, 0), temp
678          return value, type
679       end
680       def flush
681       if @clever
682 #        puts "#{self}: PUSHING exit stack [#{@virtual_stack.inspect}] - #{caller.first}"
683          @virtual_stack.each { |top| push_raw *top }
684       end
685       end
686    end
688 class FieldDesc 
689    attr_reader :position, :type
690    def initialize position, type
691       @position, @type = position, type
692    end
693    def load func, mem
694       tmp = Value.new
695       func.insn_load_elem tmp, mem, NN::mk_constant(func, :int, @position), @type
696       tmp 
697    end
698    def store func, mem, value
699       func.insn_store_elem mem, NN::mk_constant(func, :int, @position), value
700    end
703 TypedLocal = Struct.new :value, :type
705 class DictLookup
706    include Comparisons
707    attr_accessor :func, :scope_linkage, :mem_ctx, :scope_ast_id, :needs_new_scope
708    FIELD__CURRENT_SCOPE_ID = FieldDesc.new 1, :int # stack_mem
709    def initialize eval_ctx, func, scope_linkage, mem_ctx
710       idbg(:dbg_dictlookup) { magenta("----------- DICTIONARY CREATED -----------") }
711       @eval_ctx, @func, @scope_linkage, @mem_ctx = eval_ctx, func, scope_linkage, mem_ctx
712       @temps = {}
713       @switched, @taken = false, false
714       @needs_new_scope = false
715    end
716    def get_scope_from_id func, mem_ctx, current_scope_id 
717       mem_tmp = Value.new
718       idx_into_scopesstack = Value.new
719       func.insn_add idx_into_scopesstack, current_scope_id, NN::mk_constant(func, :int, 1)
720       func.insn_load_elem mem_tmp, mem_ctx.all_locals_mem, idx_into_scopesstack, :void_ptr
721       DebugLogger::runtime_print_string func, :dict_lookup, "LOADED THE CURRENT SCOPE - ",
722          current_scope_id, " (", mem_tmp , ")\n"
723       mem_tmp 
724    end
725    def create_scope
726       fail "erm. locals_mem was already set!" if !mem_ctx.locals_mem.nil?
727       scope_mem, new_scope_id = @eval_ctx.create_new_scope @func, @mem_ctx, @scope_ast_id
728       FIELD__CURRENT_SCOPE_ID.store @func, @mem_ctx.stack_mem, new_scope_id
729       mem_ctx.locals_mem = scope_mem
730       @needs_new_scope = false
731       new_scope_id
732    end
733    def load_current_scope
734       fail "erm. locals_mem was already set!" if !mem_ctx.locals_mem.nil?
735       current_scope_id = FIELD__CURRENT_SCOPE_ID.load func, mem_ctx.stack_mem
736       mem_tmp = get_scope_from_id(func, mem_ctx, current_scope_id)
737       mem_ctx.locals_mem = mem_tmp
738    end
739    def flush
740       return if @needs_new_scope
741       idbg(:dbg_dictlookup) { magenta("----------- CREATING SCOPE -----------") }
742       @temps.each_pair {
743          |sym, local|
744          raw_assign_value sym, local.value, local.type
745       }
746    end
747    def assign_value local_sym, popped_int, type
748       fail "sorry, you switched scope and then tried to use it directly after!" if @switched
749       idbg(:dbg_dictlookup) { magenta("----------- #{local_sym} := <> -----------") }
750       if not (@temps.has_key? local_sym)
751          @temps[local_sym] = TypedLocal.new Value.new, Value.new
752          @func.create_local @temps[local_sym].value, :int
753          @func.create_local @temps[local_sym].type,  :int
754       end
755       @func.insn_store @temps[local_sym].value, popped_int
756       @func.insn_store @temps[local_sym].type,  type
757    end
758    def raw_assign_value local_sym, popped_int, type, mem = nil
759       mem ||= @mem_ctx.locals_mem
760       create = proc {
761          DebugLogger::runtime_print_string @func, :rt_assign, "new value created successfully!\n"
762       }
763       current_idx = DictHelpers::lookup_id_in_dict(@eval_ctx, @func, 
764          local_sym.to_i, mem, @scope_linkage, &create)
765       temp = Value.new
766       @func.insn_store_elem mem, current_idx, popped_int
767       @func.insn_add temp, current_idx, NN::mk_constant(@func, :int, 1)
768       @func.insn_store_elem mem, temp, type
769       if check_dbg(:rt_assign)
770          type_sym = RuntimePrintCallback.new(PostProcs::TYPE, type)
771          DebugLogger::runtime_print_string @func,
772             "conditionally popping and locally assigning a value (",
773             popped_int, ":", type_sym, ") to: #{local_sym} in mem ", mem, "\n"
774       end
775    end
776    def load_local_var dict_id
777       fail "sorry, you switched scope and then tried to use it directly after!" if @switched
778       idbg(:dbg_dictlookup) { magenta("----------- #{dict_id} == ? -----------") }
779       if @temps.has_key? dict_id
780          return @temps[dict_id].value, @temps[dict_id].type
781       else
782          return raw_load_local_var(dict_id)
783       end
784    end
785    def raw_load_local_var dict_id
786       access = proc { 
787          DebugLogger::runtime_print_string @func, "#{dict_id.to_s} not found in scope (",
788             @mem_ctx.locals_mem, ") - NB. this can cause creation of the item as nil\n"
789          @eval_ctx.gen_data_inspect @func, @mem_ctx
790          @func.insn_return NN::mk_constant(@func, :int, 911)
791       }
792       current_idx = DictHelpers::lookup_id_in_dict(@eval_ctx, @func, dict_id.to_i,
793                         @mem_ctx.locals_mem, @scope_linkage, &access)
794       addr_plus_one, proc_addr, type = Value.new, Value.new, Value.new 
795       @func.insn_load_elem proc_addr, @mem_ctx.locals_mem, current_idx, :int
796       @func.insn_add addr_plus_one, current_idx, NN::mk_constant(@func, :int, 1)
797       @func.insn_load_elem type, @mem_ctx.locals_mem, addr_plus_one, :int
798       return proc_addr, type
799    end
800    def switch_to_scope_with_id func, scope_val, mem_ctx
801       FIELD__CURRENT_SCOPE_ID.store func, mem_ctx.stack_mem, scope_val
802       @switched = true
803    end
804    def force_creation
805       create_scope if @needs_new_scope
806    end
807    def take_scope_id func, mem_ctx
808       # at this point a delayed scope creation must actually be 
809       # performed in order that we have a newly allocated scope id
810       @taken = true
811       return create_scope if @needs_new_scope
812       return FIELD__CURRENT_SCOPE_ID.load(func, mem_ctx.stack_mem)
813    end
816 Hints = Struct.new(:opt_call_cnt, :opt_call_dst, :opt_call_src)
817 class Hints
818    def initialize hash
819       hash.each_pair {
820          |key, val|
821          self.send("#{key}=".to_sym, val)
822       }
823    end
826 class DictHelpers
828    def self.append_to_dict func, dict_mem, int_id, state_cache = nil
829       ret, temp, count, temp_mult3, end_byte = Value.new, Value.new, nil, Value.new, Value.new
830       DebugLogger::runtime_print_string func, :rt_find_index, "creating!\n"
831    # dict_mem[0] := dict_mem[0] + 1
832       if !state_cache.nil?
833          if state_cache.count_local.nil?
834             count = state_cache.count_local = Value.new
835             func.create_local state_cache.count_local, :int
836             func.insn_load_elem count, dict_mem, NN::mk_constant(func, :int, DICT_LENGTH_IDX), :int
837          else
838             count = state_cache.count_local
839          end
840       else
841          count = Value.new
842          func.insn_load_elem count, dict_mem, NN::mk_constant(func, :int, DICT_LENGTH_IDX), :int
843       end
844       func.insn_add temp, count, NN::mk_constant(func, :int, 1)
845    # end_byte = (count * 3) + 1
846       func.insn_mul temp_mult3, count, NN::mk_constant(func, :int, 3)
847       func.insn_add end_byte, temp_mult3, NN::mk_constant(func, :int, 1)
848    # dict_mem[end_byte] = id
849       func.insn_store_elem dict_mem, end_byte, int_id
850       func.insn_add ret, end_byte, NN::mk_constant(func, :int, 1)
851       if state_cache.nil?
852          func.insn_store_elem dict_mem, NN::mk_constant(func, :int, 0), temp
853       end
854       ret
855    end
857    def self.lookup_id_in_dict eval_ctx, func, int_id, dict_mem, scope_linkage
858       actual_int_id = int_id
859       if (!int_id.is_a? Value) && $opt_scope_templates
860          scope_hash = eval_ctx.scope_hash
861          idbg(:scope_templates) { "CHECKING IF WE CAN REUSE!!! #{int_id} -> #{int_id.id2name} - " +
862                                  "#{ProfFuncWithMd::md_inspect func.metadata}\n#{scope_hash.inspect}" }
863          if int_id.id2name.nil?
864             puts "EEK, NO SYMBOL! #{caller[0..2]}"
865          end
866          scope_id = ProfFuncWithMd::md_get_path_range(func).first
867          pair_chained_to = scope_linkage.detect { |(k,v)| v.include? scope_id }
868          if pair_chained_to 
869             scope_creation_id = pair_chained_to[0]
870             scope_id = scope_creation_id 
871          end
872          if scope_hash && scope = scope_hash[scope_id] # FIXME
873             if int_id.id2name && sym = int_id.id2name.to_sym
874                idx = scope.index sym
875                if !idx.nil?
876                   current_byte = 2 + 3*(idx)
877                   idbg(:scope_templates) { "using #{sym} -> #{current_byte} : for scope #{scope_id}" }
878                   ProfFuncWithMd::md_add_to_static_scope func, scope, scope_id, sym
879                   pos_val = NN::mk_constant(func, :int, current_byte)
880                   return pos_val 
881                end
882          end
883          end
884       end
885       puts "dbg_lookup_id_in_dict - #{caller.first}" if check_dbg(:rt_primitives)
886       int_id = int_id.is_a?(Value) ? int_id : NN::mk_constant(func, :int, int_id)
887       current_byte = Value.new
888       bench("dbg_lookup_id_in_dict") {
889          # predeclare label(s)
890          found, continue_looping, empty = Label.new, Label.new, Label.new
891          temp, end_byte, cond_result, current_id = Value.new, Value.new, Value.new, Value.new, Value.new
892          # end byte := (dict_mem[0] * 3) + 1
893             func.insn_load_elem end_byte, dict_mem, NN::mk_constant(func, :int, 0), :int
894             DebugLogger::runtime_print_string func, :rt_find_index_fine, "len = ", end_byte, "\n"
895          # if (len == 0) goto empty
896          DebugLogger::runtime_print_string func, :rt_find_index_fine, "blub= ", end_byte, "\n"
897             func.insn_eq cond_result, end_byte, NN::mk_constant(func, :int, 0)
898             func.insn_branch_if cond_result, empty
899             func.insn_sub temp, end_byte, NN::mk_constant(func, :int, 1)
900             func.insn_store end_byte, temp
901          DebugLogger::runtime_print_string func, :rt_find_index_fine, "subbibg= ", end_byte, "\n"
902             func.insn_mul temp, end_byte, NN::mk_constant(func, :int, 3)
903             func.insn_store end_byte, temp
904             func.insn_add temp, end_byte, NN::mk_constant(func, :int, 1)
905             func.insn_store end_byte, temp
906             DebugLogger::runtime_print_string func, :rt_find_index_fine, "current_byte = end_byte = ",
907                end_byte, "\n"
908          # set end_byte to 1, we start the search there
909             func.create_local current_byte, :int
910             func.insn_store current_byte, temp
911          # while !at_end
912          # { loop again
913             loop_again = Label.new
914             func.insn_label loop_again
915                # at_end := (current_byte == initial_byte)
916                   func.insn_eq cond_result, current_byte, NN::mk_constant(func, :int, 1)
917                   DebugLogger::runtime_print_string func, :rt_find_index_fine, "should finish? == ", 
918                      cond_result, "\n"
919                # comparable := (dict_mem[current_byte + 0] == int_id)
920                   func.insn_load_elem current_id, dict_mem, current_byte, :int
921                   id_comparison_result = Value.new
922                   func.insn_eq id_comparison_result, current_id, int_id
923                # if (comparable) {
924                   func.insn_branch_if_not id_comparison_result, continue_looping
925                      sym = RuntimePrintCallback.new(PostProcs::ID, int_id)
926                      DebugLogger::runtime_print_string func, :rt_find_index_fine, 
927                         "it ", sym, "(", int_id, ") matches! at position ", current_byte, "\n"
928                      # current_byte := current_byte + 1
929                         func.insn_add temp, current_byte, NN::mk_constant(func, :int, 1)
930                         func.insn_store current_byte, temp
931                      # break
932                         func.insn_branch found
933                # }
934                   func.insn_label continue_looping
935                # current_byte := current_byte + 3
936                   func.insn_sub temp, current_byte, NN::mk_constant(func, :int, 3)
937                       # decrement by 3 each time
938                   func.insn_store current_byte, temp
939                # NOTE - see above
940                   DebugLogger::runtime_print_string func, :rt_find_index_fine,
941                      "checking loop.. (", cond_result, ")\n"
942          # }
943          # if (!found) {
944                   func.insn_branch_if_not cond_result, loop_again
945                   func.insn_label empty
946                   append_temp = DictHelpers::append_to_dict func, dict_mem, int_id
947                   func.insn_store current_byte, append_temp
948                # callback to generate more runtime
949                   yield func if block_given?
950             # }
951          # done with looping or found section is finished
952             func.insn_label found
953       }
954       if !actual_int_id.is_a? Value
955          DebugLogger::runtime_print_string func, 
956             :rt_find_index, "rt lookup for #{actual_int_id.id2name} result pos: ", current_byte, "\n"
957          ProfFuncWithMd::md_add_to_lookup_for_debug func, actual_int_id.id2name
958          DebugLogger::runtime_print_string func, 
959             :rt_data_inspect_force, "forcing data inspect, due to lookup of #{actual_int_id.id2name}\n"
960       end
961       ProfFuncWithMd::md_force_data_inspect func
962       current_byte
963    end
966 class StateCache
967    CACHE_FILE = "/tmp/rubydium.pstore"
969    def self.save_cache machine
970       y = PStore.new CACHE_FILE
971       y.transaction {
972          y['cache_id']        = machine.cache_id
973          y['scope_hash']      = machine.scope_hash
974          y['node2type_cache'] = machine.node2type_cache
975          y['scope_linkage']   = machine.scope_linkage
976          if dbg_on :cache_store
977             puts "AT SAVE!"
978             pp y
979          end
980       }
981    end
983    def self.load_cache machine
984       begin
985          y = PStore.new CACHE_FILE
986          y.transaction {
987             return false if machine.cache_id != y['cache_id'] or (ARGV.include? "--ignore-cache")
988             machine.scope_hash      = y['scope_hash']
989             machine.node2type_cache = y['node2type_cache']
990             machine.scope_linkage   = y['scope_linkage']
991             if dbg_on :cache_store
992                puts "AT LOAD!"
993                pp y
994             end
995          }
996       rescue => e
997          puts "FAILURE LOADING CACHE - REMOVING"
998          File.delete(CACHE_FILE)
999          return false
1000       end
1001    end
1005 DICT_LENGTH_IDX = 0
1006 DictAppendCache = Struct.new :count_local