2014-08-13 10:59:51 +02:00
|
|
|
require_relative "block"
|
2014-07-14 13:06:09 +02:00
|
|
|
|
2014-07-10 16:14:38 +02:00
|
|
|
module Virtual
|
2015-05-20 15:43:26 +02:00
|
|
|
# the static info of a method (with its compiled code, argument names etc ) is part of the
|
|
|
|
# runtime, ie found in Parfait::Method
|
|
|
|
|
2015-07-03 19:13:03 +02:00
|
|
|
# the source we create here is injected into the method and used only at compile-time
|
2015-03-28 19:20:01 +01:00
|
|
|
# receiver
|
2014-07-10 16:14:38 +02:00
|
|
|
# return arg (usually mystery, but for coded ones can be more specific)
|
2015-05-20 15:43:26 +02:00
|
|
|
|
2014-07-10 16:14:38 +02:00
|
|
|
#
|
2015-03-28 19:20:01 +01:00
|
|
|
# Methods are one step up from to VM::Blocks. Where Blocks can be jumped to, Methods can be called.
|
2014-08-13 10:59:51 +02:00
|
|
|
|
2015-03-28 19:20:01 +01:00
|
|
|
# Methods also have arguments and a return. These are typed by subclass instances of Value
|
2014-08-13 10:59:51 +02:00
|
|
|
|
2015-05-04 22:03:52 +02:00
|
|
|
# They also have local variables.
|
2014-08-13 10:59:51 +02:00
|
|
|
|
2015-05-30 11:20:39 +02:00
|
|
|
# Code-wise Methods are made up from a list of Blocks, in a similar way blocks are made up of
|
|
|
|
# Instructions. The function starts with one block, and that has a start and end (return)
|
2015-05-04 22:03:52 +02:00
|
|
|
|
2014-08-13 10:59:51 +02:00
|
|
|
# Blocks can be linked in two ways:
|
2015-03-28 19:20:01 +01:00
|
|
|
# -linear: flow continues from one to the next as they are sequential both logically and
|
2015-05-04 22:03:52 +02:00
|
|
|
# "physically" use the block set_next for this.
|
2015-03-28 19:20:01 +01:00
|
|
|
# This "straight line", there must be a continuous sequence from body to return
|
2014-08-13 10:59:51 +02:00
|
|
|
# Linear blocks may be created from an existing block with new_block
|
|
|
|
# - branched: You create new blocks using function.new_block which gets added "after" return
|
2015-05-04 22:03:52 +02:00
|
|
|
# These (eg if/while) blocks may themselves have linear blocks ,but the last of these
|
2014-08-13 10:59:51 +02:00
|
|
|
# MUST have an uncoditional branch. And remember, all roads lead to return.
|
2015-05-04 22:03:52 +02:00
|
|
|
|
2015-07-03 19:13:03 +02:00
|
|
|
class MethodSource
|
2015-05-20 15:43:26 +02:00
|
|
|
|
|
|
|
# create method does two things
|
|
|
|
# first it creates the parfait method, for the given class, with given argument names
|
2015-07-03 19:13:03 +02:00
|
|
|
# second, it creates MethodSource and attaches it to the method
|
2015-05-20 15:43:26 +02:00
|
|
|
#
|
|
|
|
# compile code then works with the method, but adds code tot the info
|
|
|
|
def self.create_method( class_name , method_name , args)
|
2015-05-31 17:34:18 +02:00
|
|
|
raise "uups #{class_name}.#{class_name.class}" unless class_name.is_a? Symbol
|
2015-06-01 16:31:35 +02:00
|
|
|
raise "uups #{method_name}.#{method_name.class}" unless method_name.is_a? Symbol
|
2015-06-01 07:40:17 +02:00
|
|
|
clazz = Virtual.machine.space.get_class_by_name class_name
|
2015-05-20 15:43:26 +02:00
|
|
|
raise "No such class #{class_name}" unless clazz
|
2015-05-24 12:54:17 +02:00
|
|
|
method = clazz.create_instance_method( method_name , Virtual.new_list(args))
|
2015-07-03 19:13:03 +02:00
|
|
|
method.source = MethodSource.new(method)
|
2015-05-20 15:43:26 +02:00
|
|
|
method
|
2014-07-10 16:14:38 +02:00
|
|
|
end
|
2015-06-28 21:03:21 +02:00
|
|
|
# just passing the method object in for Instructions to make decisions (later)
|
|
|
|
def initialize method , return_type = Virtual::Unknown
|
2014-08-13 10:59:51 +02:00
|
|
|
# first block we have to create with .new , as new_block assumes a current
|
2015-07-26 17:28:39 +02:00
|
|
|
enter = Block.new( "enter" , method ).add_code(MethodEnter.new( method ))
|
2015-05-20 16:11:13 +02:00
|
|
|
@return_type = return_type
|
2014-08-30 15:57:56 +02:00
|
|
|
@blocks = [enter]
|
2014-08-13 10:59:51 +02:00
|
|
|
@current = enter
|
2015-06-28 21:03:21 +02:00
|
|
|
new_block("return").add_code(MethodReturn.new(method))
|
2015-06-01 07:33:51 +02:00
|
|
|
@constants = []
|
2014-07-10 16:14:38 +02:00
|
|
|
end
|
2015-06-01 07:33:51 +02:00
|
|
|
attr_reader :blocks , :constants
|
2015-05-20 16:11:13 +02:00
|
|
|
attr_accessor :return_type , :current , :receiver
|
2014-07-10 16:14:38 +02:00
|
|
|
|
2014-07-18 10:56:46 +02:00
|
|
|
# add an instruction after the current (insertion point)
|
|
|
|
# the added instruction will become the new insertion point
|
2014-08-13 10:59:51 +02:00
|
|
|
def add_code instruction
|
2015-05-30 11:20:39 +02:00
|
|
|
unless (instruction.is_a?(Instruction) or instruction.is_a?(Register::Instruction))
|
2015-07-18 18:02:54 +02:00
|
|
|
raise instruction.to_s
|
2015-05-30 11:20:39 +02:00
|
|
|
end
|
2014-08-13 10:59:51 +02:00
|
|
|
@current.add_code(instruction) #insert after current
|
|
|
|
self
|
2014-07-10 16:14:38 +02:00
|
|
|
end
|
|
|
|
|
2014-08-13 10:59:51 +02:00
|
|
|
# return a list of registers that are still in use after the given block
|
|
|
|
# a call_site uses pushes and pops these to make them available for code after a call
|
|
|
|
def locals_at l_block
|
|
|
|
used =[]
|
|
|
|
# call assigns the return register, but as it is in l_block, it is not asked.
|
2015-06-27 20:16:46 +02:00
|
|
|
assigned = [ Register::RegisterReference.new(Virtual::RegisterMachine.instance.return_register) ]
|
2014-08-13 10:59:51 +02:00
|
|
|
l_block.reachable.each do |b|
|
|
|
|
b.uses.each {|u|
|
2015-05-04 22:03:52 +02:00
|
|
|
(used << u) unless assigned.include?(u)
|
2014-08-13 10:59:51 +02:00
|
|
|
}
|
|
|
|
assigned += b.assigns
|
|
|
|
end
|
|
|
|
used.uniq
|
|
|
|
end
|
|
|
|
|
2015-05-30 11:20:39 +02:00
|
|
|
# control structures need to see blocks as a graph, but they are stored as a list with implict
|
|
|
|
# branches
|
2014-08-13 10:59:51 +02:00
|
|
|
# So when creating a new block (with new_block), it is only added to the list, but instructions
|
|
|
|
# still go to the current one
|
|
|
|
# With this function one can change the current block, to actually code it.
|
2015-05-30 11:20:39 +02:00
|
|
|
# This juggling is (unfortunately) neccessary, as all compile functions just keep puring their
|
|
|
|
# code into the method and don't care what other compiles (like if's) do.
|
2014-08-13 10:59:51 +02:00
|
|
|
|
|
|
|
# Example: while, needs 2 extra blocks
|
|
|
|
# 1 condition code, must be its own blockas we jump back to it
|
|
|
|
# - the body, can actually be after the condition as we don't need to jump there
|
2015-05-04 22:03:52 +02:00
|
|
|
# 2 after while block. Condition jumps here
|
2015-05-30 11:20:39 +02:00
|
|
|
# After block 2, the function is linear again and the calling code does not need to know what
|
|
|
|
# happened
|
2015-05-04 22:03:52 +02:00
|
|
|
|
2014-08-13 10:59:51 +02:00
|
|
|
# But subsequent statements are still using the original block (self) to add code to
|
2015-05-30 11:20:39 +02:00
|
|
|
# So the while expression creates the extra blocks, adds them and the code and then "moves"
|
|
|
|
# the insertion point along
|
2014-08-13 10:59:51 +02:00
|
|
|
def current block
|
|
|
|
@current = block
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2015-05-04 22:03:52 +02:00
|
|
|
# create a new linear block after the current insertion block.
|
|
|
|
# Linear means there is no brach needed from that one to the new one.
|
2014-08-13 10:59:51 +02:00
|
|
|
# Usually the new one just serves as jump address for a control statement
|
|
|
|
# In code generation , the new_block is written after this one, ie zero runtime cost
|
|
|
|
# This does _not_ change the insertion point, that has do be done with insert_at(block)
|
|
|
|
def new_block new_name
|
2015-07-26 17:28:39 +02:00
|
|
|
new_b = Block.new( new_name , @blocks.first.method )
|
2014-08-13 10:59:51 +02:00
|
|
|
index = @blocks.index( @current )
|
|
|
|
@blocks.insert( index + 1 , new_b ) # + one because we want the ne after the insert_at
|
|
|
|
return new_b
|
|
|
|
end
|
|
|
|
|
2014-07-14 23:00:00 +02:00
|
|
|
def get_tmp
|
|
|
|
name = "__tmp__#{@tmps.length}"
|
|
|
|
@tmps << name
|
|
|
|
Ast::NameExpression.new(name)
|
|
|
|
end
|
2014-08-22 16:26:49 +02:00
|
|
|
|
2015-05-04 22:03:52 +02:00
|
|
|
# sugar to create instructions easily.
|
2014-08-22 16:26:49 +02:00
|
|
|
# any method will be passed on to the RegisterMachine and the result added to the insertion block
|
2015-05-04 22:03:52 +02:00
|
|
|
# With this trick we can write what looks like assembler,
|
2014-08-22 16:26:49 +02:00
|
|
|
# Example func.instance_eval
|
|
|
|
# mov( r1 , r2 )
|
|
|
|
# add( r1 , r2 , 4)
|
|
|
|
# end
|
2015-05-04 22:03:52 +02:00
|
|
|
# mov and add will be called on Machine and generate Instructions that are then added
|
2014-08-22 16:26:49 +02:00
|
|
|
# to the current block
|
|
|
|
# also symbols are supported and wrapped as register usages (for bare metal programming)
|
2015-07-18 15:12:50 +02:00
|
|
|
# def method_missing(meth, *arg_names, &block)
|
|
|
|
# add_code ::Arm::ArmMachine.send(meth , *arg_names)
|
|
|
|
# end
|
2014-08-22 16:26:49 +02:00
|
|
|
|
2015-06-05 08:20:43 +02:00
|
|
|
def byte_length
|
2015-07-01 08:47:10 +02:00
|
|
|
@blocks.inject(0) { |c , block| c += block.byte_length }
|
2014-09-16 16:16:56 +02:00
|
|
|
end
|
2015-05-24 17:05:20 +02:00
|
|
|
|
2014-08-30 12:47:51 +02:00
|
|
|
# position of the function is the position of the entry block, is where we call
|
|
|
|
def set_position at
|
2014-09-11 14:19:29 +02:00
|
|
|
at += 8 #for the 2 header words
|
2014-08-30 12:47:51 +02:00
|
|
|
@blocks.each do |block|
|
2014-08-30 16:08:30 +02:00
|
|
|
block.set_position at
|
2015-06-05 08:20:43 +02:00
|
|
|
at = at + block.byte_length
|
2014-08-30 12:47:51 +02:00
|
|
|
end
|
2014-08-22 16:26:49 +02:00
|
|
|
end
|
2015-07-18 15:12:50 +02:00
|
|
|
|
2014-07-10 16:14:38 +02:00
|
|
|
end
|
2014-08-22 16:26:49 +02:00
|
|
|
|
2014-07-10 16:14:38 +02:00
|
|
|
end
|