Class: Rlang::Parser::WGenerator

Inherits:
Object
  • Object
show all
Includes:
Log
Defined in:
lib/rlang/parser/wgenerator.rb

Overview

Generate the wasm nodes and tree structure ***IMPORTANT NOTE*** Unless otherwise stated all methods receive the parent wnode as their first argument and must generate child nodes of this parent Child node created is returned

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Log

included, logger, #logger, logger=

Constructor Details

#initialize(parser) ⇒ WGenerator

Returns a new instance of WGenerator.



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
# File 'lib/rlang/parser/wgenerator.rb', line 146

def initialize(parser)
  @parser = parser
  @root = WTree.new().root
  @new_count = 0
  @static_count = 0

  # Create section wnodes
  @wn_imports = WNode.new(:imports, @root)
  @wn_memory  = WNode.new(:memory, @root)
  @wn_exports = WNode.new(:exports, @root)
  @wn_globals = WNode.new(:globals, @root)
  @wn_data = WNode.new(:data, @root)

  # Module code generation
  @root.c(:module, module: parser.config[:module])

  # Memory code generation
  WNode.new(:insn, @wn_memory). \
    c(:memory, min: parser.config[:memory_min], max: parser.config[:memory_max])

  # define Object class and Kernel modules
  # and include Kernel in Object
  wn_object_class = self.klass(@root, [:Object], [])
  @object_class = wn_object_class.klass
  @root.klass = @object_class
  wn_kernel_module = self.module(@root, [:Kernel])
  self.include(wn_object_class, [:Kernel])

  # Create Class and Module classes
  # And Class inherits from module
  self.klass(@root, [:Module], [:Object])
  self.klass(@root, [:Class], [:Module])
end

Instance Attribute Details

#parserObject

Returns the value of attribute parser.



143
144
145
# File 'lib/rlang/parser/wgenerator.rb', line 143

def parser
  @parser
end

#rootObject (readonly)

Returns the value of attribute root.



144
145
146
# File 'lib/rlang/parser/wgenerator.rb', line 144

def root
  @root
end

#wn_codeObject (readonly)

Returns the value of attribute wn_code.



144
145
146
# File 'lib/rlang/parser/wgenerator.rb', line 144

def wn_code
  @wn_code
end

#wn_dataObject (readonly)

Returns the value of attribute wn_data.



144
145
146
# File 'lib/rlang/parser/wgenerator.rb', line 144

def wn_data
  @wn_data
end

#wn_exportsObject (readonly)

Returns the value of attribute wn_exports.



144
145
146
# File 'lib/rlang/parser/wgenerator.rb', line 144

def wn_exports
  @wn_exports
end

#wn_globalsObject (readonly)

Returns the value of attribute wn_globals.



144
145
146
# File 'lib/rlang/parser/wgenerator.rb', line 144

def wn_globals
  @wn_globals
end

#wn_importsObject (readonly)

Returns the value of attribute wn_imports.



144
145
146
# File 'lib/rlang/parser/wgenerator.rb', line 144

def wn_imports
  @wn_imports
end

Instance Method Details

#_self_(wnode) ⇒ Object

self in an instance context is passed as the first argument of a method call



952
953
954
955
956
# File 'lib/rlang/parser/wgenerator.rb', line 952

def _self_(wnode)
  (wns = WNode.new(:insn, wnode)).c(:local_get, var_name: '$_self_')
  wns.wtype = WType.new(wnode.class_name)
  wns
end

#allocate_array_static_data(array, data_label) ⇒ Object

Static array data allocation



629
630
631
632
633
634
635
636
# File 'lib/rlang/parser/wgenerator.rb', line 629

def allocate_array_static_data(array, data_label)
  # Append each array element to the same data section
  label = data_label.to_sym
  data_arr = nil
  # Do not allocate memory space if array is empty
  array.each { |elt| data_arr = DAta.append(label, elt) }
  data_arr
end

#allocate_string_static_data(string, data_label) ⇒ Object

Static string data allocation



590
591
592
593
# File 'lib/rlang/parser/wgenerator.rb', line 590

def allocate_string_static_data(string, data_label)
  # if string is empty do not allocate any memory space
  DAta.append(data_label.to_sym, string) unless string.empty?
end

#array_dynamic_new(wnode, array) ⇒ Object

Dynamic new array object



658
659
660
661
662
663
664
665
666
667
668
669
670
# File 'lib/rlang/parser/wgenerator.rb', line 658

def array_dynamic_new(wnode, array)
  klass = wnode.find_current_class_or_module()
  data_label = "#{klass.name}_array_#{@static_count += 1}"
  # Note : data_arr is nil if string is empty
  data_arr = self.allocate_array_static_data(array, data_label)
  array_new_source = ARRAY_NEW_TMPL % {
    elt_size_in_bits: WTYPE::DEFAULT.size * 8,
    ptr: data_arr ? data_arr.address : 0,
    count: array.length
  }
  #puts array_new_source;exit
  self.parser.parse(array_new_source, wnode)
end

#array_static_new(wnode, array) ⇒ Object

Static new array object



639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
# File 'lib/rlang/parser/wgenerator.rb', line 639

def array_static_new(wnode, array)
  klass = wnode.find_current_class_or_module()
  data_label = "#{klass.name}_array_#{@static_count += 1}"
  # Allocate array data statically
  # Note : data_arr is nil if string is empty
  data_arr = self.allocate_array_static_data(array, data_label)
  # align on :I32 boundary
  # then allocate the Array object attributes
  # and set them up
  DAta.align(4)
  data_count = DAta.append("#{data_label}_count".to_sym, array.length, WType::DEFAULT)
  data_ptr   = DAta.append("#{data_label}_ptr".to_sym, data_arr ? data_arr.address : 0, WType::DEFAULT)
  # Generate address wnode
  (wn_object_addr = WNode.new(:insn, wnode)).c(:addr, value: data_count.address)
  wn_object_addr.wtype = WType.new(:Array32)
  wn_object_addr
end

#attr_getter(wnode, attr) ⇒ Object

Generate attribute getter method wnode



362
363
364
365
366
367
368
# File 'lib/rlang/parser/wgenerator.rb', line 362

def attr_getter(wnode, attr)
  wnc = wnode.class_wnode
  wn_get = WNode.new(:insn, wnc, true)
  wn_get.c(:attr_getter, func_name: attr.getter.wasm_name, 
        wasm_type: attr.wasm_type, offset: attr.offset)
  wn_get
end

#attr_setter(wnode, attr) ⇒ Object

Generate attribute setter method wnode



352
353
354
355
356
357
358
359
# File 'lib/rlang/parser/wgenerator.rb', line 352

def attr_setter(wnode, attr)
  wnc = wnode.class_wnode
  wn_set = WNode.new(:insn, wnc, true)
  wn_set.c(:attr_setter, func_name: attr.setter.wasm_name, 
        attr_name: attr.wasm_name, wasm_type: attr.wasm_type,
        offset: attr.offset)
  wn_set
end

#break(wnode) ⇒ Object



1002
1003
1004
1005
1006
1007
# File 'lib/rlang/parser/wgenerator.rb', line 1002

def break(wnode)
  # look for block wnode upper in the tree
  # and branch to that label
  (wn = WNode.new(:insn, wnode)).c(:br, label: wnode.block_wnode.label)
  wn
end

#call_getter(wnode, wnode_recv, attr) ⇒ Object

Call getter (on attr or instance variable) This is the same as calling the corresponding getter



496
497
498
499
500
501
# File 'lib/rlang/parser/wgenerator.rb', line 496

def call_getter(wnode, wnode_recv, attr)
  wn = self.send_method(wnode, wnode_recv.wtype, attr.getter_name, :instance)
  # First argument of the getter must always be the receiver
  wnode_recv.reparent_to(wn)
  wn
end

#call_setter(wnode, wnode_recv, attr) ⇒ Object

Call setter (on attr or instance variable) This is the same as calling the corresponding setter



487
488
489
490
491
492
# File 'lib/rlang/parser/wgenerator.rb', line 487

def call_setter(wnode, wnode_recv, attr)
  wn = self.send_method(wnode, wnode_recv.wtype, attr.setter_name, :instance)
  # First argument of the setter must be the receiver
  wnode_recv.reparent_to(wn)
  wn
end

#casgn(wnode, const) ⇒ Object

Set constant



449
450
451
452
453
454
# File 'lib/rlang/parser/wgenerator.rb', line 449

def casgn(wnode, const)
  (wn = WNode.new(:insn, wnode)).wtype = const.wtype
  wn.c(:store, wasm_type: const.wtype)
  WNode.new(:insn, wn).c(:addr, value: const.address)
  wn
end

#cast(wnode, wtype, signed = false) ⇒ Object

cast an expression to a different type if same type do nothing

  • wnode: the wnode to type cast

  • wtype: the wtype to cast wnode to

  • signed: whether the cast wnode must be interpreted as a signed value

TODO: simplify this complex method (possibly by using a conversion table source type -> destination type)



746
747
748
749
750
751
752
753
754
755
# File 'lib/rlang/parser/wgenerator.rb', line 746

def cast(wnode, wtype, signed=false)
  logger.debug "Casting wnode: #{wnode}, wtype: #{wtype}, wnode ID: #{wnode.object_id}"
  src_type  = (wnode.wtype.native? ? wnode.wtype.name : :Class)
  dest_type = (wtype.native? ? wtype.name : :Class)
  cast_method = CAST_OPS[src_type] && CAST_OPS[src_type][dest_type] || :cast_error
  logger.debug "Calling cast method: #{cast_method}"
  wn_cast_op = self.send(cast_method, wnode, wtype, signed)
  logger.debug "After type cast: wnode: #{wn_cast_op}, wtype: #{wtype}, wnode ID: #{wn_cast_op.object_id}"
  wn_cast_op
end

#cast_error(wnode, wtype, signed) ⇒ Object

Cast operation is invalid



734
735
736
# File 'lib/rlang/parser/wgenerator.rb', line 734

def cast_error(wnode, wtype, signed)
  raise "Cannot cast type #{wnode.wtype} to #{wtype}. Time to fix your code :-)"
end

#cast_extend(wnode, wtype, signed) ⇒ Object

Cast by extending to a wtype of larger bit size (e.g. I32 to I64)



685
686
687
688
689
690
691
692
693
694
695
696
# File 'lib/rlang/parser/wgenerator.rb', line 685

def cast_extend(wnode, wtype, signed)
  if (wnode.template == :const)
    # it's a WASM const, simply change the wtype
    wnode.wtype = wtype
    wn_cast_op = wnode
  else
    wn_cast_op = wnode.insert(:insn)
    wn_cast_op.wtype = wtype
    wn_cast_op.c(signed ? :extend_i32_s : :extend_i32_u , wasm_type: wn_cast_op.wasm_type)
  end
  wn_cast_op
end

#cast_nope(wnode, wtype, signed) ⇒ Object

No casting. Return node as is.



679
680
681
# File 'lib/rlang/parser/wgenerator.rb', line 679

def cast_nope(wnode, wtype, signed)
  wnode
end

#cast_notyet(wnode, wtype, signed) ⇒ Object

Cast operation not yet supported



729
730
731
# File 'lib/rlang/parser/wgenerator.rb', line 729

def cast_notyet(wnode, wtype, signed)
  raise "Type cast from #{wnode.wtype} to #{wtype} not supported yet"
end

#cast_wrap(wnode, wtype, signed) ⇒ Object

Cast by wraping a wtype to a smaller size (e.g. I64 to I32)



715
716
717
718
719
720
721
722
723
724
725
726
# File 'lib/rlang/parser/wgenerator.rb', line 715

def cast_wrap(wnode, wtype, signed)
  if (wnode.template == :const)
    # it's a WASM const, simply change the wtype
    wnode.wtype = wtype
    wn_cast_op = wnode
  else
    wn_cast_op = wnode.insert(:insn)
    wn_cast_op.wtype = wtype
    wn_cast_op.c(:wrap_i64, wasm_type: wn_cast_op.wasm_type)
  end
  wn_cast_op
end

#cast_wtype(wnode, wtype, signed) ⇒ Object

Cast by simply changing the node wtype No change in native WASM type (e.g. casting an object pointer to I32)



701
702
703
704
705
706
707
708
709
710
711
# File 'lib/rlang/parser/wgenerator.rb', line 701

def cast_wtype(wnode, wtype, signed)
  # Don't cast blindly. Check that source and target
  # have the same bit size (e.g. Object pointers, I32, UI32
  # are the same size, )
  if wnode.wtype.size == wtype.size
    wnode.wtype = wtype
  else
    cast_error(wnode, wtype, signed)
  end
  wnode
end

#comments(wnode, comments) ⇒ Object



222
223
224
225
226
227
# File 'lib/rlang/parser/wgenerator.rb', line 222

def comments(wnode, comments)
  # The gsub below is to handle =begin...=end block comments
  comments.each do |c|
    WNode.new(:comment, wnode).c(:comment, text: c.text.sub(/^\s*#/,'').gsub("\n", "\n;;"))
  end
end

#const(wnode, const) ⇒ Object

Get constant



457
458
459
460
461
462
# File 'lib/rlang/parser/wgenerator.rb', line 457

def const(wnode, const)
  (wn = WNode.new(:insn, wnode)).wtype = const.wtype
  wn.c(:load, wasm_type: const.wasm_type)
  WNode.new(:insn, wn).c(:addr, value: const.address)
  wn
end

#const_addr(wnode, const) ⇒ Object

Get constant addres



465
466
467
468
469
# File 'lib/rlang/parser/wgenerator.rb', line 465

def const_addr(wnode, const)
  (wn = WNode.new(:insn, wnode)).wtype = const.wtype
  wn.c(:addr, value: const.address)
  wn
end

#cvar(wnode, cvar) ⇒ Object

Get class variable



530
531
532
533
534
535
# File 'lib/rlang/parser/wgenerator.rb', line 530

def cvar(wnode, cvar)
  (wn = WNode.new(:insn, wnode)).wtype = cvar.wtype
  wn.c(:load, wasm_type: cvar.wasm_type)
  WNode.new(:insn, wn).c(:addr, value: cvar.address)
  wn
end

#cvar_addr(wnode, cvar) ⇒ Object

Get class variable address



539
540
541
542
543
# File 'lib/rlang/parser/wgenerator.rb', line 539

def cvar_addr(wnode, cvar)
  (wn = WNode.new(:insn, wnode)).wtype = cvar.wtype
  wn.c(:addr, value: cvar.address)
  wn
end

#cvasgn(wnode, cvar) ⇒ Object

Set class variable Create the class variable storage node and an empty expression node to populate later



522
523
524
525
526
527
# File 'lib/rlang/parser/wgenerator.rb', line 522

def cvasgn(wnode, cvar)
  (wn = WNode.new(:insn, wnode)).wtype = cvar.wtype
  wn.c(:store, wasm_type: cvar.wasm_type)
  WNode.new(:insn, wn).c(:addr, value: cvar.address)
  wn
end

#declare_method(wnode, wtype, method_name, result_type) ⇒ Object

Ahead of time method declaration and return type Create corresponding classes and method objects as we known we’ll be calling them later on



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/rlang/parser/wgenerator.rb', line 272

def declare_method(wnode, wtype, method_name, result_type)
  class_path = wtype.class_path
  logger.debug "Declaring method #{method_name} in class #{class_path}"
  klass = WNode.root.find_class_or_module(class_path)
  raise "Can't find class or module #{class_path} in method declaration" unless klass
  method_types = []
  if method_name[0] == '#'
    method_types << :instance
    method_types << :class if klass.const.module?
    mth_name = method_name[1..-1].to_sym
  else
    method_types << :class
    method_types << :instance if klass.const.module?
    mth_name = method_name.to_sym
  end
  mth = method_types.each do |mt|
    (m = wnode.find_or_create_method(klass, mth_name, mt, nil)).wtype = WType.new(result_type)
    logger.debug "Declared #{mt} method #{m.name} in class #{m.klass.name} with wtype #{m.wtype.name}"
    m
  end
  mth
end

#def_attr(wnode) ⇒ Object

generate code for class attributes (called at end of class parsing)



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/rlang/parser/wgenerator.rb', line 312

def def_attr(wnode)
  klass = wnode.find_current_class_or_module()
  wnc = klass.wnode
  raise "Cannot find class for attributes definition!!" unless wnc
  # Process each declared class attribute
  klass.attrs.each do |attr|
    logger.debug("Generating accessors for attribute #{klass.name}\##{attr.name}")
    # Generate getter and setter methods wnode
    # unless method already implemented by user
    if attr.setter
      unless attr.setter.implemented?
        attr.setter.wnode = self.attr_setter(wnc, attr)
      else
        logger.debug "Attribute setter #{attr.setter.name} already defined. Skipping"
      end
    end
    if attr.getter
      unless attr.getter.implemented?
        attr.getter.wnode = self.attr_getter(wnc, attr)
      else
        logger.debug "Attribute getter #{attr.getter.name} already defined. Skipping"
      end
    end
  end

  # Also generate the Class::_size_ method
  # (needed for dynamic memory allocation
  #  by Object.allocate)
  size_method = wnc.find_or_create_method(klass, SIZE_METHOD, :class, WType::DEFAULT)
  unless size_method.wnode
    logger.debug("Generating #{size_method.klass.name}\##{size_method.name}")
    wns = WNode.new(:insn, wnc)
    wns.wtype = WType::DEFAULT
    wns.c(:class_size, func_name: size_method.wasm_name, 
          wasm_type: wns.wasm_type, size: wnc.class_size)
    size_method.wnode = wns
  end
end

#def_initialize(wnode_class) ⇒ Object

Define a dumb initialize method if not implemented already in user code



897
898
899
900
901
902
903
904
905
906
907
908
909
# File 'lib/rlang/parser/wgenerator.rb', line 897

def def_initialize(wnode_class)
  k = wnode_class.find_current_class_or_module()
  # no new/initialize method for native types
  return if WType.new(k.path_name).native? 
  # generate code for a dumb initialize method if not defined
  # in user code
  if (init_mth = wnode_class.find_method(k, :initialize, :instance, true))
    return if init_mth.wnode # already implemented
  end
  logger.debug "Creating MEthod and code for #{k.name}#initialize"
  init_source = DUMB_INIT_TMPL
  init_mth.wnode = self.parser.parse(init_source, wnode_class)
end

#def_method(wnode, method_name, method_type) ⇒ Object



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/rlang/parser/wgenerator.rb', line 370

def def_method(wnode, method_name, method_type)
  logger.debug("Defining #{method_type} method #{method_name}...")
  if (method = wnode.find_method(nil, method_name, method_type, true))
    logger.warn "Redefining #{method.klass.name},#{method_name}" if method.wnode
  else
    method = wnode.create_method(nil, method_name, method_type, nil, true)
  end

  # If it's the main method, give it the proper name in export if
  # specified on command line
  if method.klass.path_name == :Object && method.name == :main
    method.export_name = @parser.config[:start]
  end

  # Generate method definition wnode
  logger.debug("Generating wnode for #{method_type} method #{method_name}")
  wn = WNode.new(:method, wnode)
  method.wnode = wn
  wn.wtype = method.wtype
  wn.c(:func, func_name: wn.method.wasm_name)

  # Instance methods 1st argument is always self
  wn.create_marg(:_self_) if method.instance?
  logger.debug("Built #{method_type} method definition: wn.wtype #{wn.wtype}, wn.method #{wn.method}")
  wn
end

#def_new(wnode_class) ⇒ Object

Create the dynamic new method. It allocates memory for the object created and calls initialize



872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
# File 'lib/rlang/parser/wgenerator.rb', line 872

def def_new(wnode_class)
  k = wnode_class.find_current_class_or_module()
  logger.debug "Defining new method for #{k.name}"
  # no need to define new method for native types
  return if wnode_class.klass.wtype.native?
  if (new_mth = wnode_class.find_method(k, :new, :class, true))
    return if new_mth.implemented? # already implemented
  end
  
  logger.debug "Creating code for #{k.name}.new"
  # Find initialize method and use the same method args for new
  init_method = wnode_class.find_method(k, :initialize, :instance, true)
  new_tmpl = wnode_class.class_size.zero? ? NEW_ZERO_TMPL : NEW_TMPL
  new_source = new_tmpl % {
    default_wtype: WType::DEFAULT.name,
    class_name: k.path_name,
    # Do not pass _self_ argument to the new method of course !!
    margs: init_method.margs.reject {|ma| ma._self_?}.map(&:name).join(', '), 
    class_size: wnode_class.class_size
  }
  new_mth.wnode = self.parser.parse(new_source, wnode_class)
end

#drop(wnode) ⇒ Object



559
560
561
562
563
# File 'lib/rlang/parser/wgenerator.rb', line 559

def drop(wnode)
  logger.debug "dropping result of #{wnode}, caller: #{caller_locations}"
  (wn = WNode.new(:insn, wnode)).c(:drop)
  wn
end

#else(wnode) ⇒ Object



973
974
975
976
# File 'lib/rlang/parser/wgenerator.rb', line 973

def else(wnode)
  (wn = WNode.new(:insn, wnode)).c(:else)
  wn
end

#export_method(wnode, export_name) ⇒ Object



408
409
410
# File 'lib/rlang/parser/wgenerator.rb', line 408

def export_method(wnode, export_name)
  wnode.method.export!(export_name)
end

#extend(wnode, module_path) ⇒ Object



258
259
260
261
262
263
264
265
266
267
# File 'lib/rlang/parser/wgenerator.rb', line 258

def extend(wnode, module_path)
  m = wnode.find_module(module_path)
  raise "Cannot find module #{module_path}. Please require module first." \
    unless m
  wnc = wnode.class_or_module_wnode
  raise "Cannot find scope class/module for included module!!" unless wnc
  wnc.klass.extend(m)
  m.extended!
  wnc
end

#float(wnode, wtype, value) ⇒ Object



576
577
578
579
580
# File 'lib/rlang/parser/wgenerator.rb', line 576

def float(wnode, wtype, value)
  (wn = WNode.new(:insn, wnode)).wtype = wtype
  wn.c(:const, wasm_type: wn.wasm_type, value: value)
  wn
end

#gvar(wnode, gvar) ⇒ Object

Get Global variable



479
480
481
482
483
# File 'lib/rlang/parser/wgenerator.rb', line 479

def gvar(wnode, gvar)
  (wn = WNode.new(:insn, wnode)).wtype = gvar.wtype
  wn.c(:global_get, var_name: gvar.name)
  wn
end

#gvasgn(wnode, gvar) ⇒ Object

Set Global variable



472
473
474
475
476
# File 'lib/rlang/parser/wgenerator.rb', line 472

def gvasgn(wnode, gvar)
  (wn = WNode.new(:insn, wnode)).wtype = gvar.wtype
  wn.c(:global_set, var_name: gvar.name)
  wn
end

#if(wnode) ⇒ Object



963
964
965
966
# File 'lib/rlang/parser/wgenerator.rb', line 963

def if(wnode)
  (wn = WNode.new(:insn, wnode)).c(:if)
  wn
end

#import_method(wnode, module_name, function_name) ⇒ Object



397
398
399
400
401
402
403
404
405
406
# File 'lib/rlang/parser/wgenerator.rb', line 397

def import_method(wnode, module_name, function_name)
  # Create the import node
  (wn_import = WNode.new(:insn, self.wn_imports)).c(:import, module_name: module_name, function_name: function_name)
  wn_import.link = wnode
  wnode.method.imported!
  # now silence the method wnode so that
  # it doesn't generate WASM code in the code section
  wnode.silence!
  wn_import
end

#include(wnode, module_path) ⇒ Object



236
237
238
239
240
241
242
243
244
245
# File 'lib/rlang/parser/wgenerator.rb', line 236

def include(wnode, module_path)
  m = wnode.find_module(module_path)
  raise "Unknown module #{module_path}. Please require module first." \
    unless m
  wnc = wnode.class_or_module_wnode
  raise "Cannot find scope class/module for included module!!" unless wnc
  wnc.klass.include(m) 
  m.included!
  wnc
end

#inline(wnode, code, wtype = Type::I32) ⇒ Object



441
442
443
444
445
446
# File 'lib/rlang/parser/wgenerator.rb', line 441

def inline(wnode, code, wtype=Type::I32)
  wn = WNode.new(:insn, wnode)
  wn.wtype = wnode.wtype
  wn.c(:inline, code: code)
  wn    
end

#int(wnode, wtype, value) ⇒ Object



570
571
572
573
574
# File 'lib/rlang/parser/wgenerator.rb', line 570

def int(wnode, wtype, value)
  (wn = WNode.new(:insn, wnode)).wtype = wtype
  wn.c(:const, wasm_type: wn.wasm_type, value: value)
  wn
end

#ivar(wnode, ivar) ⇒ Object

Get instance variable.



512
513
514
515
516
517
# File 'lib/rlang/parser/wgenerator.rb', line 512

def ivar(wnode, ivar)
  (wn = WNode.new(:insn, wnode)).wtype = ivar.wtype
  wn.c(:load_offset, wasm_type: ivar.wasm_type, offset: lambda { ivar.offset })
  self._self_(wn)
  wn
end

#ivars_setup(wnode) ⇒ Object

Postprocess ivars (called at end of class parsing)



297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/rlang/parser/wgenerator.rb', line 297

def ivars_setup(wnode)
  wnc = wnode.class_wnode
  raise "Cannot find class for attributes definition!!" unless wnc
  klass = wnc.klass
  logger.debug "Postprocessing ivars for class #{klass.name}..."
  klass.ivars.each do |iv|
    iv.offset = klass.offset
    logger.debug "... ivar #{iv.name} has offset #{iv.offset}"
    # Update offset for next ivar
    klass.offset += iv.size
  end
end

#ivasgn(wnode, ivar) ⇒ Object

Set instance variable



504
505
506
507
508
509
# File 'lib/rlang/parser/wgenerator.rb', line 504

def ivasgn(wnode, ivar)
  (wn = WNode.new(:insn, wnode)).wtype = ivar.wtype
  wn.c(:store_offset, wasm_type: ivar.wasm_type, offset: lambda { ivar.offset })
  self._self_(wn)
  wn
end

#klass(wnode, class_path, super_class_path) ⇒ Object

Create class and its basic methods (new, initialize and size)



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/rlang/parser/wgenerator.rb', line 181

def klass(wnode, class_path, super_class_path)
  logger.debug "Defining klass #{class_path} < #{super_class_path}"
  # See if class already created
  if (k = wnode.find_class_or_module(class_path))
    return k.wnode
  end

  # Make sure super class is known like Ruby does
  if super_class_path.empty?
    # special case to bootstrap Object class
    if (class_path == [:Object] && wnode.in_root_scope?)
      sk = nil
    else
      sk = @object_class
      super_class_path << sk.path_name
    end
  else
    sk = wnode.find_class_or_module(super_class_path)
    raise "Unknown super class #{super_class_path}" unless sk
  end
  # Create class object and class wnode if it doesn't exist yet
  # only one level deep class for now so scope class is always
  # Object class
  if (class_path == [:Object] && wnode.in_root_scope?)
    k = wnode.create_class(class_path, super_class_path)
  else
    k = wnode.find_or_create_class(class_path, super_class_path)
  end
  # make sure the super class is correct in case class
  # was previously declared in a result directive where
  # no super class can be specified
  k.super_class = sk if sk
  # Create methods Class::new, Class#initialize and Class::_size_
  # (do not generate the code yet as the end user code may 
  # define its own implementation in the class body)
  k.wnode.find_or_create_method(k, :new, :class, k.wtype, true)
  k.wnode.find_or_create_method(k, :_size_, :class, WType::DEFAULT, true)    
  k.wnode.find_or_create_method(k, :initialize, :instance, WType.new(:none), true)    
  k.wnode
end

#locals(wnode) ⇒ Object



431
432
433
434
435
436
437
438
439
# File 'lib/rlang/parser/wgenerator.rb', line 431

def locals(wnode)
  wnm = wnode.method_wnode
  wnm.method.lvars.reverse.each do |lvar|
    logger.debug("Prepending local #{lvar.inspect}")
    wn = WNode.new(:insn, wnm, true)
    wn.wtype = lvar.wtype
    wn.c(:local, name: lvar.wasm_name, wasm_type: wn.wasm_type)
  end
end

#lvar(wnode, lvar) ⇒ Object

Read local variable



553
554
555
556
557
# File 'lib/rlang/parser/wgenerator.rb', line 553

def lvar(wnode, lvar)
  (wn = WNode.new(:insn, wnode)).wtype = lvar.wtype
  wn.c(:local_get, var_name: lvar.wasm_name)
  wn
end

#lvasgn(wnode, lvar) ⇒ Object

Create the local variable storage node



546
547
548
549
550
# File 'lib/rlang/parser/wgenerator.rb', line 546

def lvasgn(wnode, lvar)
  (wn = WNode.new(:insn, wnode)).wtype = lvar.wtype
  wn.c(:local_set, var_name: lvar.wasm_name)
  wn
end

#module(wnode, module_path) ⇒ Object

Create module object and module wnode if it doesn’t exist yet



231
232
233
234
# File 'lib/rlang/parser/wgenerator.rb', line 231

def module(wnode, module_path)
  m = wnode.find_or_create_module(module_path)
  m.wnode
end

#native_operator(wnode, operator, wtype = WType.new(:none)) ⇒ Object

just create a wnode for the WASM operator Do not set wtype or a code template yet, wait until operands type is known (see operands below)



761
762
763
764
765
766
767
768
769
770
771
772
# File 'lib/rlang/parser/wgenerator.rb', line 761

def native_operator(wnode, operator, wtype=WType.new(:none))
  if (op = ALL_OPS_MAP[operator])
    (wn_op = WNode.new(:insn, wnode)).wtype = wtype
    wn_op.c(:operator, wasm_type: wn_op.wasm_type, operator: op)
    logger.debug "Creating operator #{operator} wnode: #{wn_op}"
    # special case for - unary operator transformed into (0 - x)
    WNode.new(:insn, wn_op).c(:const, wasm_type: wn_op.wasm_type, value: 0) if operator == :-@
    wn_op
  else
    raise "operator '#{operator}' not supported"
  end
end

#next(wnode) ⇒ Object



1009
1010
1011
1012
1013
1014
# File 'lib/rlang/parser/wgenerator.rb', line 1009

def next(wnode)
  # look for loop wnode upper in the tree
  # branch to that label
  (wn = WNode.new(:insn, wnode)).c(:br, label: wnode.loop_wnode.label)
  wn
end

#nop(wnode) ⇒ Object



565
566
567
568
# File 'lib/rlang/parser/wgenerator.rb', line 565

def nop(wnode)
  (wn = WNode.new(:insn, wnode)).c(:nop)
  wn
end

#operands(wnode_op, wnode_recv, wnode_args) ⇒ Object

Finish the setting of the operator node, attach operands and see if they need implicit type casting



777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
# File 'lib/rlang/parser/wgenerator.rb', line 777

def operands(wnode_op, wnode_recv, wnode_args)
  logger.debug "Processing operands in operator wnode: #{wnode_op}..."
  # Do not post process operands if the operator
  # wnode is a call (= overloaded operator)
  # and not a native operand
  if wnode_op.template == :call
    logger.debug "Doing nothing because it's a func call..."
    return wnode_op
  end

  # A native operator only expects 0 (unary) or 1 (binary)
  # argument in addition to the receiver 
  raise "only 0 or 1 operand expected (got #{wnode_args.count})" if wnode_args.count > 1
  op = wnode_op.wargs[:operator]
  #wnode_recv = wnode_op.children[0]
  #wnode_args = wnode_op.children[1..-1]
  # First find out the wtype that has precedence
  leading_wtype = self.class.leading_wtype(wnode_recv, *wnode_args)
  
  wnode_op.wtype = leading_wtype
  logger.debug "leading type cast: #{leading_wtype}"

  # Attach receiver and argument to the operator wnode
  # type casting them if necessary
  # Note : normally for an operator there is only one argument
  # but process args as if they were many one day.
  logger.debug "Perform implicit type casting of receviver and operator arg(s)"
  self.cast(wnode_recv, leading_wtype).reparent_to(wnode_op)
  wnode_args.each do |wna|
    self.cast(wna, leading_wtype).reparent_to(wnode_op)
  end

  # Once operands casting is done, see if we need the signed or unsigned
  # version of the native operator
  # NOTE : At this stage, after the operands casting both of them
  # should either be signed or unsigned, hence the XNOR sanity check
  # below
  if ALL_SIGNED_OPS_MAP.values.include? op
    if !((signed = wnode_recv.wtype.signed?) ^ (wnode_args.empty? ? true : wnode_args.first.wtype.signed?))
      wnode_op.wargs[:operator] = SIGNED_OPS[signed ? :signed : :unsigned][op]
      logger.debug "Receiver has wtype #{wnode_recv.wtype} / Argument has wtype #{wnode_args.first.wtype}"
      logger.debug "Replacing #{op} operator with #{wnode_op.wargs[:operator]}"
      op = wnode_op.wargs[:operator]
    else
      raise "Type mismatch between operands. Receiver is #{wnode_recv.wtype} and argument is #{wnode_args.first.wtype}"
    end
  end

  # if the receiver is a class object and not
  # a native integer then pointer arithmetic
  # applies (like in C)
  if wnode_recv.wtype.class?
    raise "Only #{LEGAL_CLASS_WASM_OPS.join(', ')} operators are supported on objects (got #{op} in #{wnode_op})" \
      unless LEGAL_CLASS_WASM_OPS.include?(op)
    # if :add or :sub operator then multiply arg by size of object
    # like in C
    if [:add, :sub].include? op
      (wn_mulop = WNode.new(:insn, wnode_op)).wtype = WType::DEFAULT
      wn_mulop.c(:operator, wasm_type: wn_mulop.wasm_type, operator: :mul)
      WNode.new(:insn, wn_mulop).c(:call, func_name: "$#{wnode_recv.wtype.name}::#{SIZE_METHOD}")
      wnode_args.first.reparent_to(wn_mulop)
    else
      # It's a relational operator. In this case
      # the type of the operator node is always the
      # default type because a comparison between 
      # object pointers gives a boolean (0 or 1)
      wnode_op.wtype = WType::DEFAULT
    end
  end
  logger.debug "Operands in operator wnode after postprocessing: #{wnode_op}..."
  wnode_op
end

#params(wnode) ⇒ Object



412
413
414
415
416
417
418
419
420
421
# File 'lib/rlang/parser/wgenerator.rb', line 412

def params(wnode)
  wnm = wnode.method_wnode
  # use reverse to preserve proper param order
  wnm.method.margs.reverse.each do |marg|
    logger.debug("Prepending param #{marg}")
    wn = WNode.new(:insn, wnm, true)
    wn.wtype = marg.wtype
    wn.c(:param, name: marg.wasm_name, wasm_type: wn.wasm_type)
  end
end

#phony(wnode, type = :none) ⇒ Object

Generate a phony node (generally used to create a wnode subtree under the phony node and later reparent it to the proper place in the wtree)



585
586
587
# File 'lib/rlang/parser/wgenerator.rb', line 585

def phony(wnode, type=:none)
  WNode.new(type, wnode)
end

#prepend(wnode, module_path) ⇒ Object



247
248
249
250
251
252
253
254
255
256
# File 'lib/rlang/parser/wgenerator.rb', line 247

def prepend(wnode, module_path)
  m = wnode.find_module(module_path)
  raise "Unknown module #{module_path}. Please require module first." \
    unless m
  wnc = wnode.class_or_module_wnode
  raise "Cannot find scope class/module for prepended module!!" unless wnc
  wnc.klass.prepend(m) 
  m.prepended!
  wnc
end

#result(wnode) ⇒ Object



423
424
425
426
427
428
429
# File 'lib/rlang/parser/wgenerator.rb', line 423

def result(wnode)
  unless wnode.wtype.blank?
    wn = WNode.new(:insn, wnode, true)
    wn.wtype = wnode.wtype
    wn.c(:result, wasm_type: wn.wasm_type)      
  end
end

#return(wnode) ⇒ Object



958
959
960
961
# File 'lib/rlang/parser/wgenerator.rb', line 958

def return(wnode)
  (wn = WNode.new(:insn, wnode)).c(:return)
  wn
end

#send_method(wnode, class_path, method_name, method_type) ⇒ Object

generate code for method call



912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
# File 'lib/rlang/parser/wgenerator.rb', line 912

def send_method(wnode, class_path, method_name, method_type)
  logger.debug "In call generator for #{class_path}::#{method_name}"
  if k = wnode.find_class_or_module(class_path)
    method = wnode.find_method(k, method_name, method_type)
    if k.wtype.native? && ALL_OPS_MAP.has_key?(method_name)
      # An Rlang method exist for this class but methods corresponding to 
      # Webassembly native operators applied to native Webassembly types
      # (I32, I64, F32, F64) **cannot** be overriden by instance methods
      # in Rlang code
      if method
        logger.warn "Rlang #{class_path}::#{method_name} method ignored. Native operator has precedence"
      end
      logger.debug "Apply native operator #{method_name} to native wtype  #{class_path}"
      wn_call = self.native_operator(wnode, method_name, WType.new(class_path))
    elsif !k.wtype.native? && ALL_OPS_MAP.has_key?(method_name) && method.nil?
      # Similarly if the Class is not a native type (a regular class)
      # and the Class doesn't provide its own method implementation of the native
      # operator then apply the native operands
      logger.debug "Apply native operator #{method_name} to class found : #{class_path}"
      wn_call = self.native_operator(wnode, method_name, WType.new(class_path))
    elsif method
      logger.debug "Found method #{method.name} in class #{method.klass.name}"
      (wn_call = WNode.new(:insn, wnode)).c(:call, func_name: method.wasm_name)
      wn_call.wtype = method.wtype
      wn_call
    else
      raise "Unknown method '#{method_name}' in class #{class_path}"
    end
  elsif ALL_OPS_MAP.has_key? method_name
    # It is a native Wasm operator
    logger.debug "Native operator found : #{class_path}::#{method_name}"
    wn_call = self.native_operator(wnode, method_name, WType.new(class_path))
  else
    raise "Unknown method '#{method_name}' in class #{class_path}"
  end
  wn_call
end

#static_new(wnode, class_path) ⇒ Object

Statically allocate an object in data segment with the size of the class



852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
# File 'lib/rlang/parser/wgenerator.rb', line 852

def static_new(wnode, class_path)
  klass = wnode.find_class_or_module(class_path)
  if klass.size > 0
    data_label = "#{klass.path_name}_new_#{@new_count += 1}"
    data = DAta.new(data_label.to_sym, "\x00"*klass.wnode.class_size)
    address = data.address
  else
    # TODO: point to address 0. It is not safe but normally
    # this class is without attribute so the code will never
    # use memory address to access attribute
    address = 0
  end
  (wn_object_addr = WNode.new(:insn, wnode)).c(:addr, value: address)
  # VERY IMPORTANT the wtype of this node is the Class name !!!
  wn_object_addr.wtype = WType.new(klass.path_name)
  wn_object_addr
end

#string_dynamic_new(wnode, string) ⇒ Object

Dynamic new string object



615
616
617
618
619
620
621
622
623
624
625
626
# File 'lib/rlang/parser/wgenerator.rb', line 615

def string_dynamic_new(wnode, string)
  klass = wnode.find_current_class_or_module()
  data_label = "#{klass.name}_string_#{@static_count += 1}"
  # Note : data_stg is nil if string is empty
  data_stg = self.allocate_string_static_data(string, data_label)
  string_new_source = STRING_NEW_TMPL % {
    ptr: data_stg ? data_stg.address : 0,
    length: string.length
  }
  #puts string_new_source;exit
  self.parser.parse(string_new_source, wnode)
end

#string_static_new(wnode, string) ⇒ Object

Static new string object



596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
# File 'lib/rlang/parser/wgenerator.rb', line 596

def string_static_new(wnode, string)
  klass = wnode.find_current_class_or_module()
  data_label = "#{klass.name}_string_#{@static_count += 1}"
  # Allocate string data statically
  # Note : data_stg is nil if string is empty
  data_stg = self.allocate_string_static_data(string, data_label)
  # align on :I32 boundary
  # then allocate the String object attributes
  # and set them up
  DAta.align(4)
  data_len = DAta.append("#{data_label}_len".to_sym, string.length, WType::DEFAULT)
  data_ptr = DAta.append("#{data_label}_ptr".to_sym, data_stg ? data_stg.address : 0, WType::DEFAULT)
  # Generate address wnode
  (wn_object_addr = WNode.new(:insn, wnode)).c(:addr, value: data_len.address)
  wn_object_addr.wtype = WType.new(:String)
  wn_object_addr
end

#then(wnode) ⇒ Object



968
969
970
971
# File 'lib/rlang/parser/wgenerator.rb', line 968

def then(wnode)
  (wn = WNode.new(:insn, wnode)).c(:then)
  wn
end

#while(wnode) ⇒ Object



978
979
980
981
982
983
# File 'lib/rlang/parser/wgenerator.rb', line 978

def while(wnode)
  (wnb = WNode.new(:insn, wnode)).c(:block, label: wnb.set_label) 
  (wnl = WNode.new(:insn, wnb)).c(:loop, label: wnl.set_label) 
  (wnbi = WNode.new(:insn, wnl)).c(:br_if, label: wnb.label)
  return wnb,wnbi,wnl
end

#while_cond(wnode, wnode_cond_exp) ⇒ Object

This is a post processing of the while exp wnode because br_if requires to negate the original while condition



988
989
990
991
992
993
# File 'lib/rlang/parser/wgenerator.rb', line 988

def while_cond(wnode, wnode_cond_exp)
  wn_eqz = WNode.new(:insn, wnode)
  wn_eqz.c(:eqz, wasm_type: wnode_cond_exp.wasm_type)
  wnode_cond_exp.reparent_to(wn_eqz)
  wn_eqz
end

#while_end(wnode) ⇒ Object

add the unconditional looping branch at the end of the while



997
998
999
1000
# File 'lib/rlang/parser/wgenerator.rb', line 997

def while_end(wnode)
  (wnwe = WNode.new(:insn, wnode)).c(:br, label: wnode.label)
  wnwe
end