Class: Verneuil::Process

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

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

#childrenObject (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

#ipObject

Instruction pointer



31
32
33
# File 'lib/verneuil/process.rb', line 31

def ip
  @ip
end

#joiningObject (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

.symbolsObject

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_blockObject

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_scopeObject

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.message.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(message)
  fail message
end

#fetch_and_advanceObject

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

#groupObject

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.

Returns:

  • (Boolean)


75
76
77
# File 'lib/verneuil/process.rb', line 75

def halted?
  @halted
end

#inspectObject

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_haltObject

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_blockObject

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_selfObject

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_blockObject

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_returnObject

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.

Returns:

  • (Boolean)


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

#runObject

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

#stepObject

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

#valueObject

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_conditionsObject

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.

Returns:

  • (Boolean)


81
82
83
# File 'lib/verneuil/process.rb', line 81

def waiting?
  not @joining.empty?
end