Class: Verneuil::Process
- Inherits:
-
Object
- Object
- Verneuil::Process
- Defined in:
- lib/verneuil/process.rb,
lib/verneuil/process/kernel_methods.rb
Overview
Processor state of one process.
Defined Under Namespace
Classes: KernelMethod
Instance Attribute Summary collapse
-
#children ⇒ Object
readonly
A process is also a process group, containing its children.
-
#ip ⇒ Object
Instruction pointer.
-
#joining ⇒ Object
readonly
The list of processes that this process waits for currently.
Class Method Summary collapse
-
.kernel_method(klass_name, method_name, &method_definition) ⇒ Object
Registers a kernel method.
-
.symbols ⇒ Object
The VMs own kernel method table.
Instance Method Summary collapse
-
#call(adr, context = nil) ⇒ Object
Calls the given address.
-
#current_block ⇒ Object
Returns the currently active block or nil if no such block is available.
-
#current_scope ⇒ Object
Returns the currently active scope.
-
#dispatch(instruction) ⇒ Object
Decodes the instruction into opcode and arguments and calls a method on this instance called instr_OPCODE giving the arguments as method arguments.
-
#dispatch_to_verneuil(receiver, name) ⇒ Object
Try to dispatch a method call to a method defined inside the Verneuil VM.
-
#enter_block(args, adr, scope) ⇒ Object
Enters a block.
-
#exception(message) ⇒ Object
Raises an exception that contains useful information about where the process stopped.
-
#fetch_and_advance ⇒ Object
Fetches the next instruction and advances @ip.
-
#fork_child(block) ⇒ Object
Forks a new process that starts its execution at address and that halts when encountering a ‘return’ instruction.
-
#group ⇒ Object
Returns the process group having this process as root node.
-
#halted? ⇒ Boolean
Returns true if the process has halted because it has reached its end.
-
#initialize(program, context) ⇒ Process
constructor
Create a process, giving it a program to run and a context to run in.
-
#inspect ⇒ Object
Inspection of processes.
-
#instr_dup(stack_idx) ⇒ Object
Duplicates the value given by stack_idx (from the top) and pushes it to the stack.
-
#instr_halt ⇒ Object
Halts the processor and returns the last value on the stack.
-
#instr_jump(adr) ⇒ Object
Unconditional jump.
-
#instr_jump_if_false(adr) ⇒ Object
Jumps to the given address if the top of the stack contains a false value.
-
#instr_load(val) ⇒ Object
Loads a literal value to the stack.
-
#instr_load_block ⇒ Object
Loads the currently set implicit block to the stack.
-
#instr_load_self ⇒ Object
Loads the self on the stack.
-
#instr_lvar_set(name) ⇒ Object
Sets the local variable given by name.
-
#instr_pop(n) ⇒ Object
Pops n elements off the internal stack.
-
#instr_pop_block ⇒ Object
Unloads a block.
-
#instr_push_block(block_adr) ⇒ Object
Pushes a block context to the block stack.
-
#instr_return ⇒ Object
Returning from a method (pops the call_stack.).
-
#instr_ruby_call(name, argc) ⇒ Object
A call to an explicit receiver.
-
#instr_ruby_call_implicit(name, argc) ⇒ Object
A call to an implicit target (self).
-
#instr_test_defined(name) ⇒ Object
Tests if the local variable exists.
-
#instruction_pointer_valid? ⇒ Boolean
True if the current instruction pointer is valid.
-
#jump(adr) ⇒ Object
Jumps to the given address.
-
#lookup_method(receiver, name) ⇒ Object
Looks up a method in internal tables.
-
#run ⇒ Object
Runs the program until it completes and returns the last expression in the program.
-
#run_block(block) ⇒ Object
Confines execution to a single method.
-
#scope(context) ⇒ Object
Produces a new scope that links to the given context.
-
#step ⇒ Object
Runs one instruction.
-
#value ⇒ Object
Once the process has halted?, this returns the top of the stack.
-
#verify_wait_conditions ⇒ Object
Checks if the conditions that make this process wait still apply.
-
#waiting? ⇒ Boolean
Returns true if the process waits for something to happen.
Constructor Details
#initialize(program, context) ⇒ Process
Create a process, giving it a program to run and a context to run in.
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/verneuil/process.rb', line 7 def initialize(program, context) # p program # The program that is being executed. @program = program # Keeps the current scope and the ones before it. @scopes = [scope(context)] # Keeps implicit blocks when executing iteration code. @blocks = [] # Value stack @stack = [] # Return address stack @call_stack = [] # Instruction pointer @ip = 0 # Should we stop immediately? Cannot restart after setting this. @halted = false # This process' children @children = [] # The list of processes that this process waits for currently. @joining = [] end |
Instance Attribute Details
#children ⇒ Object (readonly)
A process is also a process group, containing its children.
34 35 36 |
# File 'lib/verneuil/process.rb', line 34 def children @children end |
#ip ⇒ Object
Instruction pointer
31 32 33 |
# File 'lib/verneuil/process.rb', line 31 def ip @ip end |
#joining ⇒ Object (readonly)
The list of processes that this process waits for currently.
37 38 39 |
# File 'lib/verneuil/process.rb', line 37 def joining @joining end |
Class Method Details
.kernel_method(klass_name, method_name, &method_definition) ⇒ Object
Registers a kernel method. These methods have precedence over the Ruby bridge, but can still be overridden by user methods.
22 23 24 25 26 27 28 |
# File 'lib/verneuil/process/kernel_methods.rb', line 22 def kernel_method(klass_name, method_name, &method_definition) symbols.add( KernelMethod.new( klass_name, method_name, method_definition)) end |
.symbols ⇒ Object
The VMs own kernel method table.
15 16 17 |
# File 'lib/verneuil/process/kernel_methods.rb', line 15 def symbols @symbols ||= Verneuil::SymbolTable.new end |
Instance Method Details
#call(adr, context = nil) ⇒ Object
Calls the given address. (like a jump, but puts something on the return stack) If context is left empty, it will call inside the current context.
182 183 184 185 186 |
# File 'lib/verneuil/process.rb', line 182 def call(adr, context=nil) @scopes.push scope(context || current_scope.context) @call_stack.push @ip jump adr end |
#current_block ⇒ Object
Returns the currently active block or nil if no such block is available.
203 204 205 |
# File 'lib/verneuil/process.rb', line 203 def current_block @blocks.last end |
#current_scope ⇒ Object
Returns the currently active scope.
157 158 159 |
# File 'lib/verneuil/process.rb', line 157 def current_scope @scopes.last end |
#dispatch(instruction) ⇒ Object
Decodes the instruction into opcode and arguments and calls a method on this instance called instr_OPCODE giving the arguments as method arguments.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/verneuil/process.rb', line 124 def dispatch(instruction) opcode, *rest = instruction sym = "instr_#{opcode}" begin self.send(sym, *rest) rescue NoMethodError => ex # Catch our own method error, but not those that happen inside an # instruction that exists.. if ex..match(/#{sym}/) warn @program exception "Unknown opcode #{opcode} (missing ##{sym})." else raise end end end |
#dispatch_to_verneuil(receiver, name) ⇒ Object
Try to dispatch a method call to a method defined inside the Verneuil VM. There are currently two kinds of methods in this category:
* Kernel methods
* Methods implemented in V
254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/verneuil/process.rb', line 254 def dispatch_to_verneuil(receiver, name) if v_method = lookup_method(receiver, name) catch(:verneuil_code) { retval = v_method.invoke(self, receiver) @stack.push retval } return true end return false end |
#enter_block(args, adr, scope) ⇒ Object
Enters a block. This pushes args to the stack, installs the scope given and jumps to adr.
164 165 166 167 168 169 170 171 |
# File 'lib/verneuil/process.rb', line 164 def enter_block(args, adr, scope) args.each { |arg| @stack.push arg } @scopes.push scope @call_stack.push @ip jump adr end |
#exception(message) ⇒ Object
Raises an exception that contains useful information about where the process stopped.
145 146 147 |
# File 'lib/verneuil/process.rb', line 145 def exception() fail end |
#fetch_and_advance ⇒ Object
Fetches the next instruction and advances @ip.
109 110 111 112 113 114 115 116 117 118 |
# File 'lib/verneuil/process.rb', line 109 def fetch_and_advance # Pretends that the memory beyond the current space is filled with :halt # instructions. return :halt unless instruction_pointer_valid? instruction = @program[@ip] @ip += 1 instruction end |
#fork_child(block) ⇒ Object
Forks a new process that starts its execution at address and that halts when encountering a ‘return’ instruction. Returns that new process instance.
The newly created child process is also stored in this process’ children array. You can run the process and all of its children by calling
process.group.run
216 217 218 219 220 221 222 223 |
# File 'lib/verneuil/process.rb', line 216 def fork_child(block) child = Verneuil::Process.new(@program, nil) child.run_block(block) @children << child return child end |
#group ⇒ Object
Returns the process group having this process as root node.
103 104 105 |
# File 'lib/verneuil/process.rb', line 103 def group @group ||= Verneuil::ProcessGroup.new(self) end |
#halted? ⇒ Boolean
Returns true if the process has halted because it has reached its end.
75 76 77 |
# File 'lib/verneuil/process.rb', line 75 def halted? @halted end |
#inspect ⇒ Object
Inspection of processes
244 245 246 |
# File 'lib/verneuil/process.rb', line 244 def inspect "process(#{object_id}, #{@ip}, #{@call_stack}, w:#{@joining.size}, c:#{children.size}, h:#{halted?})" end |
#instr_dup(stack_idx) ⇒ Object
Duplicates the value given by stack_idx (from the top) and pushes it to the stack.
325 326 327 |
# File 'lib/verneuil/process.rb', line 325 def instr_dup(stack_idx) @stack.push @stack[-stack_idx-1] end |
#instr_halt ⇒ Object
Halts the processor and returns the last value on the stack.
394 395 396 |
# File 'lib/verneuil/process.rb', line 394 def instr_halt @halted = true end |
#instr_jump(adr) ⇒ Object
Unconditional jump
340 341 342 |
# File 'lib/verneuil/process.rb', line 340 def instr_jump(adr) jump adr end |
#instr_jump_if_false(adr) ⇒ Object
Jumps to the given address if the top of the stack contains a false value.
333 334 335 336 |
# File 'lib/verneuil/process.rb', line 333 def instr_jump_if_false(adr) val = @stack.pop @ip = adr.ip unless val end |
#instr_load(val) ⇒ Object
Loads a literal value to the stack.
318 319 320 |
# File 'lib/verneuil/process.rb', line 318 def instr_load(val) @stack.push val end |
#instr_load_block ⇒ Object
Loads the currently set implicit block to the stack. This is used when turning an implicit block into an explicit block by storing it to a local variable.
381 382 383 384 |
# File 'lib/verneuil/process.rb', line 381 def instr_load_block fail "BUG: No implicit block!" if @blocks.empty? @stack.push current_block end |
#instr_load_self ⇒ Object
Loads the self on the stack. Toplevel self is the context you give, later on this may change to the class we’re masking a method of.
403 404 405 |
# File 'lib/verneuil/process.rb', line 403 def instr_load_self @stack.push current_scope.context end |
#instr_lvar_set(name) ⇒ Object
Sets the local variable given by name.
362 363 364 |
# File 'lib/verneuil/process.rb', line 362 def instr_lvar_set(name) current_scope.lvar_set(name, @stack.pop) end |
#instr_pop(n) ⇒ Object
Pops n elements off the internal stack
312 313 314 |
# File 'lib/verneuil/process.rb', line 312 def instr_pop(n) @stack.pop(n) end |
#instr_pop_block ⇒ Object
Unloads a block
388 389 390 |
# File 'lib/verneuil/process.rb', line 388 def instr_pop_block @blocks.pop end |
#instr_push_block(block_adr) ⇒ Object
Pushes a block context to the block stack.
370 371 372 373 374 375 |
# File 'lib/verneuil/process.rb', line 370 def instr_push_block(block_adr) @blocks.push Verneuil::Block.new( block_adr, self, current_scope.child) end |
#instr_return ⇒ Object
Returning from a method (pops the call_stack.)
346 347 348 349 350 |
# File 'lib/verneuil/process.rb', line 346 def instr_return exception "Nothing to return to on the call stack." if @call_stack.empty? @ip = @call_stack.pop @scopes.pop end |
#instr_ruby_call(name, argc) ⇒ Object
A call to an explicit receiver. The receiver should be on top of the stack.
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/verneuil/process.rb', line 288 def instr_ruby_call(name, argc) receiver = @stack.pop # TODO Fix argument count handling # Currently the caller decides with how many arguments he calls the # block and the callee pops off the stack what he wants. This is not a # good situation. # Verneuil method? (class method mask) return if dispatch_to_verneuil(receiver, name) # Must be a Ruby method then. The catch allows internal classes like # Verneuil::Block to skip the stack.push. args = @stack.pop(argc) catch(:verneuil_code) { retval = receiver.send(name, *args) @stack.push retval } end |
#instr_ruby_call_implicit(name, argc) ⇒ Object
A call to an implicit target (self).
271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/verneuil/process.rb', line 271 def instr_ruby_call_implicit(name, argc) # Local variable if argc==0 && current_scope.lvar_exist?(name) @stack.push current_scope.lvar_get(name) return end # Verneuil method? return if dispatch_to_verneuil(nil, name) # Ruby method! (or else) args = @stack.pop(argc) @stack.push current_scope.method_call(name, *args) end |
#instr_test_defined(name) ⇒ Object
Tests if the local variable exists. Puts true/false to the stack.
356 357 358 |
# File 'lib/verneuil/process.rb', line 356 def instr_test_defined(name) @stack.push current_scope.defined?(name) end |
#instruction_pointer_valid? ⇒ Boolean
True if the current instruction pointer is valid.
237 238 239 240 |
# File 'lib/verneuil/process.rb', line 237 def instruction_pointer_valid? @ip >= 0 && @ip < @program.size end |
#jump(adr) ⇒ Object
Jumps to the given address.
175 176 177 |
# File 'lib/verneuil/process.rb', line 175 def jump(adr) @ip = adr.ip end |
#lookup_method(receiver, name) ⇒ Object
Looks up a method in internal tables.
190 191 192 193 194 195 196 197 198 199 |
# File 'lib/verneuil/process.rb', line 190 def lookup_method(receiver, name) [ @program.symbols, self.class.symbols ].each do |table| method = table.lookup_method(receiver, name) return method if method end return nil end |
#run ⇒ Object
Runs the program until it completes and returns the last expression in the program.
42 43 44 45 46 47 48 |
# File 'lib/verneuil/process.rb', line 42 def run until halted? step end value end |
#run_block(block) ⇒ Object
Confines execution to a single method. This means setting up the return stack to return into nirvana once the VM reaches a ‘return’ instruction.
228 229 230 231 232 233 |
# File 'lib/verneuil/process.rb', line 228 def run_block(block) @call_stack.push(-1) jump block.address @scopes = [block.scope] end |
#scope(context) ⇒ Object
Produces a new scope that links to the given context.
151 152 153 |
# File 'lib/verneuil/process.rb', line 151 def scope(context) Verneuil::Scope.new(context) end |
#step ⇒ Object
Runs one instruction. If the current process waits on another process (joining not empty), it will run an instruction in the other process instead.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/verneuil/process.rb', line 54 def step verify_wait_conditions if waiting? # If we're still waiting for someone, let's give them a little shove. if waiting? joining.first.step return end instruction = fetch_and_advance dispatch(instruction) # p [self, instruction, @stack, @call_stack, current_scope] # If we just ran into uncharted memory - and we're not still waiting # for someone - we'll just stop the machine. instr_halt if !waiting? && !instruction_pointer_valid? end |
#value ⇒ Object
Once the process has halted?, this returns the top of the stack. This is like the return value of the process.
88 89 90 |
# File 'lib/verneuil/process.rb', line 88 def value @stack.last end |
#verify_wait_conditions ⇒ Object
Checks if the conditions that make this process wait still apply. This may clear up the wait state so that waiting? changes value after this method.
97 98 99 |
# File 'lib/verneuil/process.rb', line 97 def verify_wait_conditions @joining.delete_if { |process| process.halted? } end |
#waiting? ⇒ Boolean
Returns true if the process waits for something to happen.
81 82 83 |
# File 'lib/verneuil/process.rb', line 81 def waiting? not @joining.empty? end |