Class: Duby::Compiler::JVM

Inherits:
Object show all
Includes:
JVMLogger, JVM::MethodLookup
Defined in:
lib/duby/jvm/compiler.rb

Defined Under Namespace

Modules: JVMLogger Classes: ImplicitSelf

Constant Summary collapse

Types =
Duby::JVM::Types

Constants included from JVM::MethodLookup

JVM::MethodLookup::BOOLEAN, JVM::MethodLookup::BYTE, JVM::MethodLookup::CHAR, JVM::MethodLookup::DOUBLE, JVM::MethodLookup::FLOAT, JVM::MethodLookup::INT, JVM::MethodLookup::LONG, JVM::MethodLookup::PrimitiveConversions, JVM::MethodLookup::SHORT

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from JVMLogger

#log

Methods included from JVM::MethodLookup

#each_is_exact, #each_is_exact_or_subtype_or_convertible, #find_jls, #find_method, #is_more_specific?, #log, #phase1, #phase2, #phase3, #primitive_convertible?

Constructor Details

#initialize(filename) ⇒ JVM

Returns a new instance of JVM.



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/duby/jvm/compiler.rb', line 51

def initialize(filename)
  @filename = File.basename(filename)
  @src = ""
  @static = true
  classname = File.basename(filename, '.duby')

  @file = BiteScript::FileBuilder.new(@filename)
  AST.type_factory.define_types(@file)
  @type = AST::type(classname)
  @class = @type.define(@file)
end

Class Attribute Details

.verboseObject

Returns the value of attribute verbose.



23
24
25
# File 'lib/duby/jvm/compiler.rb', line 23

def verbose
  @verbose
end

Instance Attribute Details

#classObject

Returns the value of attribute class.



49
50
51
# File 'lib/duby/jvm/compiler.rb', line 49

def class
  @class
end

#filenameObject

Returns the value of attribute filename.



49
50
51
# File 'lib/duby/jvm/compiler.rb', line 49

def filename
  @filename
end

#methodObject

Returns the value of attribute method.



49
50
51
# File 'lib/duby/jvm/compiler.rb', line 49

def method
  @method
end

#srcObject

Returns the value of attribute src.



49
50
51
# File 'lib/duby/jvm/compiler.rb', line 49

def src
  @src
end

#staticObject

Returns the value of attribute static.



49
50
51
# File 'lib/duby/jvm/compiler.rb', line 49

def static
  @static
end

Class Method Details

.log(message) ⇒ Object



25
26
27
# File 'lib/duby/jvm/compiler.rb', line 25

def log(message)
  puts "* [#{name}] #{message}" if JVM.verbose
end

Instance Method Details

#_raise(exception) ⇒ Object



594
595
596
597
# File 'lib/duby/jvm/compiler.rb', line 594

def _raise(exception)
  exception.compile(self, true)
  @method.athrow
end

#array_foreach(loop, expression) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/duby/jvm/compiler.rb', line 284

def array_foreach(loop, expression)
  array = "tmp$#{loop.name}$array"
  i = "tmp$#{loop.name}$i"
  length = "tmp$#{loop.name}$length"
  array_type = loop.iter.inferred_type
  item_type = array_type.component_type
  local_assign(array, array_type, true, loop.iter)
  @method.arraylength
  @method.istore(@method.local(length, Types::Int))
  @method.push_int(0)
  @method.istore(@method.local(i, Types::Int))
  with(:break_label => @method.label,
       :redo_label => @method.label,
       :next_label => @method.label) do
    @next_label.set!
    local(i, Types::Int)
    local(length, Types::Int)
    @method.if_icmpge(@break_label)
    local(array, array_type)
    local(i, Types::Int)
    item_type.aload(@method)
    item_type.store(@method, @method.local(loop.name, item_type))
    @method.iinc(@method.local(i, Types::Int), 1)

    @redo_label.set!
    loop.body.compile(self, false)
    @method.goto @next_label

    @break_label.set!

    # loops always evaluate to null
    @method.aconst_null if expression
  end
end

#body(body, expression) ⇒ Object



433
434
435
436
437
438
439
440
441
442
# File 'lib/duby/jvm/compiler.rb', line 433

def body(body, expression)
  # all except the last element in a body of code is treated as a statement
  i, last = 0, body.children.size - 1
  while i < last
    body.children[i].compile(self, false)
    i += 1
  end
  # last element is an expression only if the body is an expression
  body.children[last].compile(self, expression)
end

#boolean(value) ⇒ Object



538
539
540
# File 'lib/duby/jvm/compiler.rb', line 538

def boolean(value)
  value ? @method.iconst_1 : @method.iconst_0
end

#branch(iff, expression) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/duby/jvm/compiler.rb', line 203

def branch(iff, expression)
  elselabel = @method.label
  donelabel = @method.label
  
  # this is ugly...need a better way to abstract the idea of compiling a
  # conditional branch while still fitting into JVM opcodes
  predicate = iff.condition.predicate
  if iff.body || expression
    jump_if_not(predicate, elselabel)

    if iff.body
      iff.body.compile(self, expression)
    elsif expression
      iff.inferred_type.init_value(@method)
    end

    @method.goto(donelabel)
  else
    jump_if(predicate, donelabel)
  end

  elselabel.set!

  if iff.else
    iff.else.compile(self, expression)
  elsif expression
    iff.inferred_type.init_value(@method)
  end

  donelabel.set!
end

#break(node) ⇒ Object



319
320
321
322
# File 'lib/duby/jvm/compiler.rb', line 319

def break(node)
  handle_ensures(node)
  @method.goto(@break_label)
end

#call(call, expression) ⇒ Object



346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/duby/jvm/compiler.rb', line 346

def call(call, expression)

  target = call.target.inferred_type
  params = call.parameters.map do |param|
    param.inferred_type
  end
  method = target.get_method(call.name, params)
  if method
    method.call(self, call, expression)
  else
    raise "Missing method #{target}.#{call.name}(#{params.join ', '})"
  end
end

#cast(fcall, expression) ⇒ Object



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/duby/jvm/compiler.rb', line 379

def cast(fcall, expression)
  # casting operation, not a call
  castee = fcall.parameters[0]
  
  # TODO move errors to inference phase
  source_type_name = castee.inferred_type.name
  target_type_name = fcall.inferred_type.name
  case source_type_name
  when "byte", "short", "char", "int", "long", "float", "double"
    case target_type_name
    when "byte", "short", "char", "int", "long", "float", "double"
      # ok
      primitive = true
    else
      raise TypeError.new "not a reference type: #{castee.inferred_type}"
    end
  when "boolean"
    if target_type_name != "boolean"
      raise TypeError.new "not a boolean type: #{castee.inferred_type}"
    end
    primitive = true
  else
    case target_type_name
    when "byte", "short", "char", "int", "long", "float", "double"
      raise TypeError.new "not a primitive type: #{castee.inferred_type}"
    else
      # ok
      primitive = false
    end
  end

  castee.compile(self, expression)
  if expression
    if primitive
      source_type_name = 'int' if %w[byte short char].include? source_type_name
      if (source_type_name != 'int') && (%w[byte short char].include? target_type_name)
        target_type_name = 'int'
      end

      if source_type_name != target_type_name
        if RUBY_VERSION == "1.9"
          @method.send "#{source_type_name[0]}2#{target_type_name[0]}"
        else
          @method.send "#{source_type_name[0].chr}2#{target_type_name[0].chr}"
        end
      end
    else
      if source_type_name != target_type_name
        @method.checkcast fcall.inferred_type
      end
    end
  end
end

#compile(ast, expression = false) ⇒ Object



63
64
65
66
# File 'lib/duby/jvm/compiler.rb', line 63

def compile(ast, expression = false)
  ast.compile(self, expression)
  log "Compilation successful!"
end

#declare_argument(name, type) ⇒ Object



199
200
201
# File 'lib/duby/jvm/compiler.rb', line 199

def declare_argument(name, type)
  # declare local vars for arguments here
end

#declare_field(name, type) ⇒ Object



494
495
496
497
498
499
500
501
502
503
504
# File 'lib/duby/jvm/compiler.rb', line 494

def declare_field(name, type)
  # TODO confirm types are compatible
  unless declared_fields[name]
    declared_fields[name] = type
    if static
      @class.private_static_field name, type
    else
      @class.private_field name, type
    end
  end
end

#declare_local(name, type) ⇒ Object



463
464
465
466
467
468
469
# File 'lib/duby/jvm/compiler.rb', line 463

def declare_local(name, type)
  # TODO confirm types are compatible
  unless declared_locals[name]
    declared_locals[name] = type
    index = @method.local(name, type)
  end
end

#declared_fieldsObject



490
491
492
# File 'lib/duby/jvm/compiler.rb', line 490

def declared_fields
  @declared_fields ||= {}
end

#declared_localsObject



459
460
461
# File 'lib/duby/jvm/compiler.rb', line 459

def declared_locals
  @declared_locals ||= {}
end

#define_class(class_def, expression) ⇒ Object



189
190
191
192
193
194
195
196
197
# File 'lib/duby/jvm/compiler.rb', line 189

def define_class(class_def, expression)
  with(:type => class_def.inferred_type,
       :class => class_def.inferred_type.define(@file),
       :static => false) do
    class_def.body.compile(self, false) if class_def.body
  
    @class.stop
  end
end

#define_main(body) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/duby/jvm/compiler.rb', line 68

def define_main(body)
  with :method => @class.main do
    log "Starting main method"

    @method.start

    # declare argv variable
    @method.local('argv', AST.type('string', true))

    body.compile(self, false)

    @method.returnvoid
    @method.stop
  end

  log "Main method complete!"
end

#define_method(name, signature, args, body, force_static) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/duby/jvm/compiler.rb', line 86

def define_method(name, signature, args, body, force_static)
  arg_types = if args.args
    args.args.map { |arg| arg.inferred_type }
  else
    []
  end
  return_type = signature[:return]
  exceptions = signature[:throws]
  started = false
  if @static || force_static
    method = @class.public_static_method(name.to_s, exceptions, return_type, *arg_types)
  else
    if name == "initialize"
      method = @class.public_constructor(exceptions, *arg_types)
      method.start
      method.aload 0
      method.invokespecial @class.superclass, "<init>", [@method.void]
      started = true
    else
      method = @class.public_method(name.to_s, exceptions, return_type, *arg_types)
    end
  end

  return if @class.interface?

  with :method => method, :static => @static || force_static do
    log "Starting new method #{name}(#{arg_types})"

    @method.start unless started

    # declare all args so they get their values
    if args.args
      args.args.each {|arg| @method.local(arg.name, arg.inferred_type)}
    end
  
    expression = signature[:return] != Types::Void
    body.compile(self, expression) if body

    if name == "initialize"
      @method.returnvoid
    else
      signature[:return].return(@method)
    end
  
    @method.stop
  end

  arg_types_for_opt = []
  args_for_opt = []
  if args.args
    args.args.each do |arg|
      if AST::OptionalArgument === arg
        if @static || force_static
          method = @class.public_static_method(name.to_s, exceptions, return_type, *arg_types_for_opt)
        else
          if name == "initialize"
            method = @class.public_constructor(exceptions, *arg_types_for_opt)
            method.start
            method.aload 0
            method.invokespecial @class.superclass, "<init>", [@method.void]
            started = true
          else
            method = @class.public_method(name.to_s, exceptions, return_type, *arg_types_for_opt)
          end
        end

        with :method => method, :static => @static || force_static do
          log "Starting new method #{name}(#{arg_types_for_opt})"

          @method.start unless started

          # declare all args so they get their values

          expression = signature[:return] != Types::Void

          @method.aload(0) unless @static
          args_for_opt.each {|req_arg| @method.local(req_arg.name, req_arg.inferred_type)}
          arg.children[0].compile(self, true)

          # invoke the next one in the chain
          if @static
            @method.invokestatic(@class, name.to_s, [return_type] + arg_types_for_opt + [arg.inferred_type])
          else
            @method.invokevirtual(@class, name.to_s, [return_type] + arg_types_for_opt + [arg.inferred_type])
          end

          if name == "initialize"
            @method.returnvoid
          else
            signature[:return].return(@method)
          end

          @method.stop
        end
      end
      arg_types_for_opt << arg.inferred_type
      args_for_opt << arg
    end
  end

  log "Method #{name}(#{arg_types}) complete!"
end

#empty_array(type, size) ⇒ Object



646
647
648
649
# File 'lib/duby/jvm/compiler.rb', line 646

def empty_array(type, size)
  size.compile(self, true)
  type.newarray(@method)
end

#ensure(node, expression) ⇒ Object



630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
# File 'lib/duby/jvm/compiler.rb', line 630

def ensure(node, expression)
  node.state = @method.label  # Save the ensure target for JumpNodes
  start = @method.label.set!
  body_end = @method.label
  done = @method.label
  node.body.compile(self, expression)  # First compile the body
  body_end.set!
  handle_ensures(node)  # run the ensure clause
  @method.goto(done)  # and continue on after the exception handler
  target = @method.label.set!  # Finally, create the exception handler
  @method.trycatch(start, body_end, target, nil)
  handle_ensures(node)
  @method.athrow
  done.set!
end

#field(name, type) ⇒ Object



477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/duby/jvm/compiler.rb', line 477

def field(name, type)
  name = name[1..-1]

  # load self object unless static
  method.aload 0 unless static
  
  if static
    @method.getstatic(@class, name, type)
  else
    @method.getfield(@class, name, type)
  end
end

#field_assign(name, type, expression, value) ⇒ Object



511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
# File 'lib/duby/jvm/compiler.rb', line 511

def field_assign(name, type, expression, value)
  name = name[1..-1]

  real_type = declared_fields[name] || type
  
  declare_field(name, real_type)

  method.aload 0 unless static
  value.compile(self, true)
  if expression
    instruction = 'dup'
    instruction << '2' if type.wide?
    instruction << '_x1' unless static
    method.send instruction
  end

  if static
    @method.putstatic(@class, name, real_type)
  else
    @method.putfield(@class, name, real_type)
  end
end

#field_declare(name, type) ⇒ Object



506
507
508
509
# File 'lib/duby/jvm/compiler.rb', line 506

def field_declare(name, type)
  name = name[1..-1]
  declare_field(name, type)
end

#for_loop(loop, expression) ⇒ Object



276
277
278
279
280
281
282
# File 'lib/duby/jvm/compiler.rb', line 276

def for_loop(loop, expression)
  if loop.iter.inferred_type.array?
    array_foreach(loop, expression)
  else
    iterator_foreach(loop, expression)
  end
end

#generateObject



554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/duby/jvm/compiler.rb', line 554

def generate
  @class.stop
  log "Generating classes..."
  @file.generate do |filename, builder|
    log "  #{builder.class_name}"
    if block_given?
      yield filename, builder
    else
      File.open(filename, 'w') {|f| f.write(builder.generate)}
    end
  end
  log "...done!"
end

#handle_ensures(node) ⇒ Object



623
624
625
626
627
628
# File 'lib/duby/jvm/compiler.rb', line 623

def handle_ensures(node)
  return unless node.ensures
  node.ensures.each do |ensure_node|
    ensure_node.clause.compile(self, false)
  end
end

#import(short, long) ⇒ Object



568
569
# File 'lib/duby/jvm/compiler.rb', line 568

def import(short, long)
end

#jump_if(predicate, target) ⇒ Object



334
335
336
337
338
# File 'lib/duby/jvm/compiler.rb', line 334

def jump_if(predicate, target)
  raise "Expected boolean, found #{predicate.inferred_type}" unless predicate.inferred_type == Types::Boolean
  predicate.compile(self, true)
  @method.ifne(target)
end

#jump_if_not(predicate, target) ⇒ Object



340
341
342
343
344
# File 'lib/duby/jvm/compiler.rb', line 340

def jump_if_not(predicate, target)
  raise "Expected boolean, found #{predicate.inferred_type}" unless predicate.inferred_type == Types::Boolean
  predicate.compile(self, true)
  @method.ifeq(target)
end

#line(num) ⇒ Object



550
551
552
# File 'lib/duby/jvm/compiler.rb', line 550

def line(num)
  @method.line(num) if @method
end

#local(name, type) ⇒ Object



444
445
446
# File 'lib/duby/jvm/compiler.rb', line 444

def local(name, type)
  type.load(@method, @method.local(name, type))
end

#local_assign(name, type, expression, value) ⇒ Object



448
449
450
451
452
453
454
455
456
457
# File 'lib/duby/jvm/compiler.rb', line 448

def local_assign(name, type, expression, value)
  declare_local(name, type)
  
  value.compile(self, true)
  
  # if expression, dup the value we're assigning
  @method.dup if expression
  
  type.store(@method, @method.local(name, type))
end

#local_declare(name, type) ⇒ Object



471
472
473
474
475
# File 'lib/duby/jvm/compiler.rb', line 471

def local_declare(name, type)
  declare_local(name, type)
  type.init_value(@method)
  type.store(@method, @method.local(name, type))
end

#newlineObject



546
547
548
# File 'lib/duby/jvm/compiler.rb', line 546

def newline
  # TODO: line numbering
end

#next(node) ⇒ Object



324
325
326
327
# File 'lib/duby/jvm/compiler.rb', line 324

def next(node)
  handle_ensures(node)
  @method.goto(@next_label)
end

#nullObject



542
543
544
# File 'lib/duby/jvm/compiler.rb', line 542

def null
  @method.aconst_null
end


571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
# File 'lib/duby/jvm/compiler.rb', line 571

def print(print_node)
  @method.getstatic System, "out", PrintStream
  print_node.parameters.each {|param| param.compile(self, true)}
  params = print_node.parameters.map {|param| param.inferred_type.jvm_type}
  method_name = print_node.println ? "println" : "print"
  method = find_method(PrintStream.java_class, method_name, params, false)
  if (method)
    @method.invokevirtual(
      PrintStream,
      method_name,
      [method.return_type, *method.parameter_types])
  else
    log "Could not find a match for #{PrintStream}.#{method_name}(#{params})"
    fail "Could not compile"
  end
end

#redo(node) ⇒ Object



329
330
331
332
# File 'lib/duby/jvm/compiler.rb', line 329

def redo(node)
  handle_ensures(node)
  @method.goto(@redo_label)
end

#rescue(rescue_node, expression) ⇒ Object



599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
# File 'lib/duby/jvm/compiler.rb', line 599

def rescue(rescue_node, expression)
  start = @method.label.set!
  body_end = @method.label
  done = @method.label
  rescue_node.body.compile(self, expression)
  body_end.set!
  @method.goto(done)
  rescue_node.clauses.each do |clause|
    target = @method.label.set!
    if clause.name
      @method.astore(@method.push_local(clause.name, clause.type))
    else
      @method.pop
    end
    clause.body.compile(self, expression)
    @method.pop_local(clause.name) if clause.name
    @method.goto(done)
    clause.types.each do |type|
      @method.trycatch(start, body_end, target, type)
    end
  end
  done.set!
end

#return(return_node) ⇒ Object



588
589
590
591
592
# File 'lib/duby/jvm/compiler.rb', line 588

def return(return_node)
  return_node.value.compile(self, true)
  handle_ensures(return_node)
  return_node.inferred_type.return(@method)
end

#self_call(fcall, expression) ⇒ Object



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/duby/jvm/compiler.rb', line 360

def self_call(fcall, expression)
  return cast(fcall, expression) if fcall.cast?
  type = @type
  type = type.meta if @static
  fcall.target = ImplicitSelf.new(type)

  params = fcall.parameters.map do |param|
    param.inferred_type
  end
  method = type.get_method(fcall.name, params)
  unless method
    target = static ? @class.name : 'self'
  
    raise NameError, "No method %s.%s(%s)" %
        [target, fcall.name, params.join(', ')]
  end
  method.call(self, fcall, expression)
end

#string(value) ⇒ Object



534
535
536
# File 'lib/duby/jvm/compiler.rb', line 534

def string(value)
  @method.ldc(value)
end

#while_loop(loop, expression) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/duby/jvm/compiler.rb', line 235

def while_loop(loop, expression)
  with(:break_label => @method.label,
       :redo_label => @method.label,
       :next_label => @method.label) do
    # TODO: not checking "check first" or "negative"
    predicate = loop.condition.predicate

    if loop.check_first
      @next_label.set!
      if loop.negative
        # if condition, exit
        jump_if(predicate, @break_label)
      else
        # if not condition, exit
        jump_if_not(predicate, @break_label)
      end
    end
  
    @redo_label.set!
    loop.body.compile(self, false)
  
    unless loop.check_first
      @next_label.set!
      if loop.negative
        # if not condition, continue
        jump_if_not(predicate, @redo_label)
      else
        # if condition, continue
        jump_if(predicate, @redo_label)
      end
    else
      @method.goto(@next_label)
    end
  
    @break_label.set!
  
    # loops always evaluate to null
    @method.aconst_null if expression
  end
end

#with(vars) ⇒ Object



651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
# File 'lib/duby/jvm/compiler.rb', line 651

def with(vars)
  orig_values = {}
  begin
    vars.each do |name, new_value|
      name = "@#{name}"
      orig_values[name] = instance_variable_get name
      instance_variable_set name, new_value
    end
    yield
  ensure
    orig_values.each do |name, value|
      instance_variable_set name, value
    end
  end
end