rlang 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +3 -5
- data/README +11 -0
- data/bin/rlang +4 -0
- data/docs/RlangCompiler.md +2 -0
- data/docs/RlangManual.md +37 -8
- data/lib/rlang/lib/array/array32.rb +13 -4
- data/lib/rlang/lib/array/array64.rb +11 -3
- data/lib/rlang/lib/base64.rb +223 -0
- data/lib/rlang/lib/io.rb +7 -2
- data/lib/rlang/lib/malloc.rb +5 -3
- data/lib/rlang/lib/object.rb +23 -0
- data/lib/rlang/lib/rlang_core.rb +2 -1
- data/lib/rlang/lib/string.rb +95 -11
- data/lib/rlang/lib/type/i32.rb +30 -12
- data/lib/rlang/lib/type/i64.rb +0 -2
- data/lib/rlang/lib/wasi.rb +57 -6
- data/lib/rlang/parser/data.rb +5 -0
- data/lib/rlang/parser/ext/integer.rb +3 -1
- data/lib/rlang/parser/ext/type.rb +30 -1
- data/lib/rlang/parser/global.rb +7 -0
- data/lib/rlang/parser/wgenerator.rb +221 -83
- data/lib/rlang/parser/wnode.rb +41 -14
- data/lib/rlang/parser/wtype.rb +12 -6
- data/lib/rlang/parser.rb +182 -130
- data/lib/rlang/version.rb +1 -1
- data/lib/ruby/mirror/rstring.rb +16 -0
- data/lib/utils/exceptions.rb +12 -0
- data/rlang.gemspec +4 -4
- metadata +16 -12
@@ -22,26 +22,29 @@ module Rlang::Parser
|
|
22
22
|
:+ => :add,
|
23
23
|
:- => :sub,
|
24
24
|
:* => :mul,
|
25
|
-
:/ => :div_u,
|
26
|
-
:% => :rem_u,
|
27
25
|
:& => :and,
|
28
26
|
:| => :or,
|
29
27
|
:^ => :xor,
|
30
|
-
:>> => :shr_u,
|
31
28
|
:<< => :shl
|
32
29
|
}
|
33
30
|
|
31
|
+
ARITHMETIC_SIGNED_OPS_MAP = {
|
32
|
+
:/ => :div_x,
|
33
|
+
:% => :rem_x,
|
34
|
+
:>> => :shr_x
|
35
|
+
}
|
36
|
+
|
34
37
|
RELATIONAL_OPS_MAP = {
|
35
38
|
:== => :eq,
|
36
39
|
:!= => :ne,
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
:>= => :
|
40
|
+
|
41
|
+
}
|
42
|
+
|
43
|
+
RELATIONAL_SIGNED_OPS_MAP = {
|
44
|
+
:< => :lt_x,
|
45
|
+
:> => :gt_x,
|
46
|
+
:<= => :le_x,
|
47
|
+
:>= => :ge_x
|
45
48
|
}
|
46
49
|
|
47
50
|
BOOLEAN_OPS_MAP = {
|
@@ -50,19 +53,40 @@ module Rlang::Parser
|
|
50
53
|
}
|
51
54
|
|
52
55
|
UNARY_OPS_MAP = {
|
53
|
-
:'!' => :eqz
|
56
|
+
:'!' => :eqz,
|
57
|
+
:'-@' => :sub # special case for unary - turned into (sub 0 x)
|
54
58
|
}
|
55
59
|
|
56
|
-
|
60
|
+
SIGNED_OPS = {
|
61
|
+
signed: {div_x: :div_s, rem_x: :rem_s, shr_x: :shr_s, lt_x: :lt_s, gt_x: :gt_s,
|
62
|
+
le_x: :le_s, ge_x: :ge_s},
|
63
|
+
unsigned: {div_x: :div_u, rem_x: :rem_u, shr_x: :shr_u, lt_x: :lt_u, gt_x: :gt_u,
|
64
|
+
le_x: :le_u, ge_x: :ge_u},
|
65
|
+
}
|
66
|
+
|
67
|
+
# Operators that can be legally used when doing
|
68
|
+
# arithmetic on class instance (= object) pointers
|
69
|
+
# Only unsigned relational operators make sense as pointers
|
70
|
+
# are by nature unsigned integers
|
71
|
+
LEGAL_CLASS_WASM_OPS = [:eq, :ne, :lt_u, :gt_u, :le_u, :ge_u, :add, :sub]
|
72
|
+
|
73
|
+
# All operators with signed / unsigned variants
|
74
|
+
ALL_SIGNED_OPS_MAP = [*ARITHMETIC_SIGNED_OPS_MAP, *RELATIONAL_SIGNED_OPS_MAP].to_h
|
75
|
+
|
76
|
+
# All operators in on hash
|
77
|
+
ALL_OPS_MAP = [*ARITHMETIC_OPS_MAP, *ARITHMETIC_SIGNED_OPS_MAP, *RELATIONAL_OPS_MAP, *RELATIONAL_SIGNED_OPS_MAP,
|
78
|
+
*BOOLEAN_OPS_MAP, *UNARY_OPS_MAP].to_h
|
57
79
|
|
58
80
|
# Matrix of how to cast a WASM type to another
|
59
81
|
CAST_OPS = {
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
82
|
+
UI32: { UI32: :cast_nope, I32: :cast_wtype, UI64: :cast_extend, I64: :cast_extend, F32: :cast_notyet, F64: :cast_notyet, Class: :cast_wtype, none: :cast_error},
|
83
|
+
I32: { UI32: :cast_wtype, I32: :cast_nope, UI64: :cast_extend, I64: :cast_extend, F32: :cast_notyet, F64: :cast_notyet, Class: :cast_wtype, none: :cast_error},
|
84
|
+
UI64: { UI32: :cast_wrap, I32: :cast_wrap, UI64: :cast_nope, I64: :cast_wtype, F32: :cast_notyet, F64: :cast_notyet, Class: :cast_error, none: :cast_error},
|
85
|
+
I64: { UI32: :cast_wrap, I32: :cast_wrap, UI64: :cast_wtype, I64: :cast_nope, F32: :cast_notyet, F64: :cast_notyet, Class: :cast_error, none: :cast_error},
|
86
|
+
F32: { UI32: :cast_notyet, I32: :cast_notyet, UI64: :cast_notyet, I64: :cast_notyet, F32: :cast_nope, F64: :cast_notyet, Class: :cast_error, none: :cast_error},
|
87
|
+
F64: { UI32: :cast_notyet, I32: :cast_notyet, UI64: :cast_notyet, I64: :cast_notyet, F32: :cast_notyet, F64: :cast_nope, Class: :cast_error, none: :cast_error},
|
88
|
+
Class: { UI32: :cast_wtype, I32: :cast_wtype, UI64: :cast_extend, I64: :cast_extend, F32: :cast_error, F64: :cast_error, Class: :cast_wtype, none: :cast_error},
|
89
|
+
none: { UI32: :cast_error, I32: :cast_error, UI64: :cast_error, I64: :cast_error, F32: :cast_error, F64: :cast_error, Class: :cast_error, none: :cast_error},
|
66
90
|
}
|
67
91
|
|
68
92
|
# Rlang class size method name
|
@@ -103,6 +127,11 @@ module Rlang::Parser
|
|
103
127
|
String.new(%{ptr}, %{length})
|
104
128
|
}
|
105
129
|
|
130
|
+
# Dynamically allocate a string object
|
131
|
+
ARRAY_NEW_TMPL = %q{
|
132
|
+
Array%{elt_size_in_bits}.new(%{ptr}, %{length})
|
133
|
+
}
|
134
|
+
|
106
135
|
# Generate the wasm nodes and tree structure
|
107
136
|
# ***IMPORTANT NOTE***
|
108
137
|
# Unless otherwise stated all methods receive
|
@@ -190,6 +219,13 @@ module Rlang::Parser
|
|
190
219
|
k.wnode
|
191
220
|
end
|
192
221
|
|
222
|
+
def comments(wnode, comments)
|
223
|
+
# The gsub below is to handle =begin...=end block comments
|
224
|
+
comments.each do |c|
|
225
|
+
WNode.new(:comment, wnode).c(:comment, text: c.text.sub(/^\s*#/,'').gsub("\n", "\n;;"))
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
193
229
|
# Create module object and module wnode
|
194
230
|
# if it doesn't exist yet
|
195
231
|
def module(wnode, module_path)
|
@@ -305,9 +341,9 @@ module Rlang::Parser
|
|
305
341
|
unless size_method.wnode
|
306
342
|
logger.debug("Generating #{size_method.klass.name}\##{size_method.name}")
|
307
343
|
wns = WNode.new(:insn, wnc)
|
308
|
-
wns.wtype = WType::DEFAULT
|
344
|
+
wns.wtype = WType::DEFAULT
|
309
345
|
wns.c(:class_size, func_name: size_method.wasm_name,
|
310
|
-
|
346
|
+
wasm_type: wns.wasm_type, size: wnc.class_size)
|
311
347
|
size_method.wnode = wns
|
312
348
|
end
|
313
349
|
end
|
@@ -317,7 +353,7 @@ module Rlang::Parser
|
|
317
353
|
wnc = wnode.class_wnode
|
318
354
|
wn_set = WNode.new(:insn, wnc, true)
|
319
355
|
wn_set.c(:attr_setter, func_name: attr.setter.wasm_name,
|
320
|
-
attr_name: attr.wasm_name,
|
356
|
+
attr_name: attr.wasm_name, wasm_type: attr.wasm_type,
|
321
357
|
offset: attr.offset)
|
322
358
|
wn_set
|
323
359
|
end
|
@@ -327,8 +363,7 @@ module Rlang::Parser
|
|
327
363
|
wnc = wnode.class_wnode
|
328
364
|
wn_get = WNode.new(:insn, wnc, true)
|
329
365
|
wn_get.c(:attr_getter, func_name: attr.getter.wasm_name,
|
330
|
-
|
331
|
-
offset: attr.offset)
|
366
|
+
wasm_type: attr.wasm_type, offset: attr.offset)
|
332
367
|
wn_get
|
333
368
|
end
|
334
369
|
|
@@ -381,7 +416,7 @@ module Rlang::Parser
|
|
381
416
|
logger.debug("Prepending param #{marg}")
|
382
417
|
wn = WNode.new(:insn, wnm, true)
|
383
418
|
wn.wtype = marg.wtype
|
384
|
-
wn.c(:param, name: marg.wasm_name)
|
419
|
+
wn.c(:param, name: marg.wasm_name, wasm_type: wn.wasm_type)
|
385
420
|
end
|
386
421
|
end
|
387
422
|
|
@@ -389,7 +424,7 @@ module Rlang::Parser
|
|
389
424
|
unless wnode.wtype.blank?
|
390
425
|
wn = WNode.new(:insn, wnode, true)
|
391
426
|
wn.wtype = wnode.wtype
|
392
|
-
wn.c(:result)
|
427
|
+
wn.c(:result, wasm_type: wn.wasm_type)
|
393
428
|
end
|
394
429
|
end
|
395
430
|
|
@@ -399,7 +434,7 @@ module Rlang::Parser
|
|
399
434
|
logger.debug("Prepending local #{lvar.inspect}")
|
400
435
|
wn = WNode.new(:insn, wnm, true)
|
401
436
|
wn.wtype = lvar.wtype
|
402
|
-
wn.c(:local, name: lvar.wasm_name)
|
437
|
+
wn.c(:local, name: lvar.wasm_name, wasm_type: wn.wasm_type)
|
403
438
|
end
|
404
439
|
end
|
405
440
|
|
@@ -413,7 +448,7 @@ module Rlang::Parser
|
|
413
448
|
# Set constant
|
414
449
|
def casgn(wnode, const)
|
415
450
|
(wn = WNode.new(:insn, wnode)).wtype = const.wtype
|
416
|
-
wn.c(:store,
|
451
|
+
wn.c(:store, wasm_type: const.wtype)
|
417
452
|
WNode.new(:insn, wn).c(:addr, value: const.address)
|
418
453
|
wn
|
419
454
|
end
|
@@ -421,7 +456,7 @@ module Rlang::Parser
|
|
421
456
|
# Get constant
|
422
457
|
def const(wnode, const)
|
423
458
|
(wn = WNode.new(:insn, wnode)).wtype = const.wtype
|
424
|
-
wn.c(:load,
|
459
|
+
wn.c(:load, wasm_type: const.wasm_type)
|
425
460
|
WNode.new(:insn, wn).c(:addr, value: const.address)
|
426
461
|
wn
|
427
462
|
end
|
@@ -468,7 +503,7 @@ module Rlang::Parser
|
|
468
503
|
# Set instance variable
|
469
504
|
def ivasgn(wnode, ivar)
|
470
505
|
(wn = WNode.new(:insn, wnode)).wtype = ivar.wtype
|
471
|
-
wn.c(:store_offset,
|
506
|
+
wn.c(:store_offset, wasm_type: ivar.wasm_type, offset: lambda { ivar.offset })
|
472
507
|
self._self_(wn)
|
473
508
|
wn
|
474
509
|
end
|
@@ -476,7 +511,7 @@ module Rlang::Parser
|
|
476
511
|
# Get instance variable.
|
477
512
|
def ivar(wnode, ivar)
|
478
513
|
(wn = WNode.new(:insn, wnode)).wtype = ivar.wtype
|
479
|
-
wn.c(:load_offset,
|
514
|
+
wn.c(:load_offset, wasm_type: ivar.wasm_type, offset: lambda { ivar.offset })
|
480
515
|
self._self_(wn)
|
481
516
|
wn
|
482
517
|
end
|
@@ -486,7 +521,7 @@ module Rlang::Parser
|
|
486
521
|
# an empty expression node to populate later
|
487
522
|
def cvasgn(wnode, cvar)
|
488
523
|
(wn = WNode.new(:insn, wnode)).wtype = cvar.wtype
|
489
|
-
wn.c(:store,
|
524
|
+
wn.c(:store, wasm_type: cvar.wasm_type)
|
490
525
|
WNode.new(:insn, wn).c(:addr, value: cvar.address)
|
491
526
|
wn
|
492
527
|
end
|
@@ -494,7 +529,7 @@ module Rlang::Parser
|
|
494
529
|
# Get class variable
|
495
530
|
def cvar(wnode, cvar)
|
496
531
|
(wn = WNode.new(:insn, wnode)).wtype = cvar.wtype
|
497
|
-
wn.c(:load,
|
532
|
+
wn.c(:load, wasm_type: cvar.wasm_type)
|
498
533
|
WNode.new(:insn, wn).c(:addr, value: cvar.address)
|
499
534
|
wn
|
500
535
|
end
|
@@ -510,14 +545,14 @@ module Rlang::Parser
|
|
510
545
|
# Create the local variable storage node
|
511
546
|
def lvasgn(wnode, lvar)
|
512
547
|
(wn = WNode.new(:insn, wnode)).wtype = lvar.wtype
|
513
|
-
wn.c(:local_set,
|
548
|
+
wn.c(:local_set, var_name: lvar.wasm_name)
|
514
549
|
wn
|
515
550
|
end
|
516
551
|
|
517
552
|
# Read local variable
|
518
553
|
def lvar(wnode, lvar)
|
519
554
|
(wn = WNode.new(:insn, wnode)).wtype = lvar.wtype
|
520
|
-
wn.c(:local_get,
|
555
|
+
wn.c(:local_get, var_name: lvar.wasm_name)
|
521
556
|
wn
|
522
557
|
end
|
523
558
|
|
@@ -534,13 +569,13 @@ module Rlang::Parser
|
|
534
569
|
|
535
570
|
def int(wnode, wtype, value)
|
536
571
|
(wn = WNode.new(:insn, wnode)).wtype = wtype
|
537
|
-
wn.c(:const,
|
572
|
+
wn.c(:const, wasm_type: wn.wasm_type, value: value)
|
538
573
|
wn
|
539
574
|
end
|
540
575
|
|
541
576
|
def float(wnode, wtype, value)
|
542
577
|
(wn = WNode.new(:insn, wnode)).wtype = wtype
|
543
|
-
wn.c(:const,
|
578
|
+
wn.c(:const, wasm_type: wn.wasm_type, value: value)
|
544
579
|
wn
|
545
580
|
end
|
546
581
|
|
@@ -551,24 +586,25 @@ module Rlang::Parser
|
|
551
586
|
WNode.new(type, wnode)
|
552
587
|
end
|
553
588
|
|
554
|
-
# Static string allocation
|
555
|
-
def
|
556
|
-
#
|
557
|
-
|
558
|
-
data_stg = DAta.append(data_label.to_sym, string)
|
559
|
-
data_stg
|
589
|
+
# Static string data allocation
|
590
|
+
def allocate_string_static_data(string, data_label)
|
591
|
+
# if string is empty do not allocate any memory space
|
592
|
+
DAta.append(data_label.to_sym, string) unless string.empty?
|
560
593
|
end
|
561
594
|
|
562
595
|
# Static new string object
|
563
596
|
def string_static_new(wnode, string)
|
564
597
|
klass = wnode.find_current_class_or_module()
|
565
598
|
data_label = "#{klass.name}_string_#{@static_count += 1}"
|
566
|
-
#
|
567
|
-
data_stg
|
599
|
+
# Allocate string data statically
|
600
|
+
# Note : data_stg is nil if string is empty
|
601
|
+
data_stg = self.allocate_string_static_data(string, data_label)
|
568
602
|
# align on :I32 boundary
|
603
|
+
# then allocate the String object attributes
|
604
|
+
# and set them up
|
569
605
|
DAta.align(4)
|
570
606
|
data_len = DAta.append("#{data_label}_len".to_sym, string.length, WType::DEFAULT)
|
571
|
-
data_ptr = DAta.append("#{data_label}_ptr".to_sym, data_stg.address, WType::DEFAULT)
|
607
|
+
data_ptr = DAta.append("#{data_label}_ptr".to_sym, data_stg ? data_stg.address : 0, WType::DEFAULT)
|
572
608
|
# Generate address wnode
|
573
609
|
(wn_object_addr = WNode.new(:insn, wnode)).c(:addr, value: data_len.address)
|
574
610
|
wn_object_addr.wtype = WType.new(:String)
|
@@ -579,25 +615,73 @@ module Rlang::Parser
|
|
579
615
|
def string_dynamic_new(wnode, string)
|
580
616
|
klass = wnode.find_current_class_or_module()
|
581
617
|
data_label = "#{klass.name}_string_#{@static_count += 1}"
|
582
|
-
data_stg
|
618
|
+
# Note : data_stg is nil if string is empty
|
619
|
+
data_stg = self.allocate_string_static_data(string, data_label)
|
583
620
|
string_new_source = STRING_NEW_TMPL % {
|
584
|
-
ptr: data_stg.address,
|
621
|
+
ptr: data_stg ? data_stg.address : 0,
|
585
622
|
length: string.length
|
586
623
|
}
|
587
624
|
#puts string_new_source;exit
|
588
|
-
|
589
|
-
|
625
|
+
self.parser.parse(string_new_source, wnode)
|
626
|
+
end
|
627
|
+
|
628
|
+
# Static array data allocation
|
629
|
+
def allocate_array_static_data(array, data_label)
|
630
|
+
# Append each array element to the same data section
|
631
|
+
label = data_label.to_sym
|
632
|
+
data_arr = nil
|
633
|
+
# Do not allocate memory space if array is empty
|
634
|
+
array.each { |elt| data_arr = DAta.append(label, elt) }
|
635
|
+
data_arr
|
636
|
+
end
|
637
|
+
|
638
|
+
# Static new array object
|
639
|
+
def array_static_new(wnode, array)
|
640
|
+
klass = wnode.find_current_class_or_module()
|
641
|
+
data_label = "#{klass.name}_array_#{@static_count += 1}"
|
642
|
+
# Allocate array data statically
|
643
|
+
# Note : data_arr is nil if string is empty
|
644
|
+
data_arr = self.allocate_array_static_data(array, data_label)
|
645
|
+
# align on :I32 boundary
|
646
|
+
# then allocate the Array object attributes
|
647
|
+
# and set them up
|
648
|
+
DAta.align(4)
|
649
|
+
data_count = DAta.append("#{data_label}_count".to_sym, array.length, WType::DEFAULT)
|
650
|
+
data_ptr = DAta.append("#{data_label}_ptr".to_sym, data_arr ? data_arr.address : 0, WType::DEFAULT)
|
651
|
+
# Generate address wnode
|
652
|
+
(wn_object_addr = WNode.new(:insn, wnode)).c(:addr, value: data_count.address)
|
653
|
+
wn_object_addr.wtype = WType.new(:Array32)
|
654
|
+
wn_object_addr
|
655
|
+
end
|
656
|
+
|
657
|
+
# Dynamic new array object
|
658
|
+
def array_dynamic_new(wnode, array)
|
659
|
+
klass = wnode.find_current_class_or_module()
|
660
|
+
data_label = "#{klass.name}_array_#{@static_count += 1}"
|
661
|
+
# Note : data_arr is nil if string is empty
|
662
|
+
data_arr = self.allocate_array_static_data(array, data_label)
|
663
|
+
array_new_source = ARRAY_NEW_TMPL % {
|
664
|
+
elt_size_in_bits: WTYPE::DEFAULT.size * 8,
|
665
|
+
ptr: data_arr ? data_arr.address : 0,
|
666
|
+
count: array.length
|
667
|
+
}
|
668
|
+
#puts array_new_source;exit
|
669
|
+
self.parser.parse(array_new_source, wnode)
|
590
670
|
end
|
591
671
|
|
672
|
+
# TYPE CASTING methods
|
592
673
|
# All the cast_xxxx methods below returns
|
593
674
|
# the new wnode doing the cast operation
|
594
675
|
# or the same wnode if there is no additional code
|
595
676
|
# for the cast operation
|
677
|
+
|
678
|
+
# No casting. Return node as is.
|
596
679
|
def cast_nope(wnode, wtype, signed)
|
597
|
-
# Do nothing
|
598
680
|
wnode
|
599
681
|
end
|
600
682
|
|
683
|
+
# Cast by extending to a wtype of larger bit size
|
684
|
+
# (e.g. I32 to I64)
|
601
685
|
def cast_extend(wnode, wtype, signed)
|
602
686
|
if (wnode.template == :const)
|
603
687
|
# it's a WASM const, simply change the wtype
|
@@ -606,15 +690,19 @@ module Rlang::Parser
|
|
606
690
|
else
|
607
691
|
wn_cast_op = wnode.insert(:insn)
|
608
692
|
wn_cast_op.wtype = wtype
|
609
|
-
wn_cast_op.c(signed ? :extend_i32_s : :extend_i32_u ,
|
693
|
+
wn_cast_op.c(signed ? :extend_i32_s : :extend_i32_u , wasm_type: wn_cast_op.wasm_type)
|
610
694
|
end
|
611
695
|
wn_cast_op
|
612
696
|
end
|
613
697
|
|
698
|
+
# Cast by simply changing the node wtype
|
699
|
+
# No change in native WASM type
|
700
|
+
# (e.g. casting an object pointer to I32)
|
614
701
|
def cast_wtype(wnode, wtype, signed)
|
615
|
-
|
616
|
-
|
617
|
-
|
702
|
+
# Don't cast blindly. Check that source and target
|
703
|
+
# have the same bit size (e.g. Object pointers, I32, UI32
|
704
|
+
# are the same size, )
|
705
|
+
if wnode.wtype.size == wtype.size
|
618
706
|
wnode.wtype = wtype
|
619
707
|
else
|
620
708
|
cast_error(wnode, wtype, signed)
|
@@ -622,6 +710,8 @@ module Rlang::Parser
|
|
622
710
|
wnode
|
623
711
|
end
|
624
712
|
|
713
|
+
# Cast by wraping a wtype to a smaller size
|
714
|
+
# (e.g. I64 to I32)
|
625
715
|
def cast_wrap(wnode, wtype, signed)
|
626
716
|
if (wnode.template == :const)
|
627
717
|
# it's a WASM const, simply change the wtype
|
@@ -630,17 +720,19 @@ module Rlang::Parser
|
|
630
720
|
else
|
631
721
|
wn_cast_op = wnode.insert(:insn)
|
632
722
|
wn_cast_op.wtype = wtype
|
633
|
-
wn_cast_op.c(:wrap_i64,
|
723
|
+
wn_cast_op.c(:wrap_i64, wasm_type: wn_cast_op.wasm_type)
|
634
724
|
end
|
635
725
|
wn_cast_op
|
636
726
|
end
|
637
727
|
|
728
|
+
# Cast operation not yet supported
|
638
729
|
def cast_notyet(wnode, wtype, signed)
|
639
730
|
raise "Type cast from #{wnode.wtype} to #{wtype} not supported yet"
|
640
731
|
end
|
641
732
|
|
733
|
+
# Cast operation is invalid
|
642
734
|
def cast_error(wnode, wtype, signed)
|
643
|
-
raise "Cannot cast type #{
|
735
|
+
raise "Cannot cast type #{wnode.wtype} to #{wtype}. Time to fix your code :-)"
|
644
736
|
end
|
645
737
|
|
646
738
|
# cast an expression to a different type
|
@@ -668,17 +760,20 @@ module Rlang::Parser
|
|
668
760
|
# operands below)
|
669
761
|
def native_operator(wnode, operator, wtype=WType.new(:none))
|
670
762
|
if (op = ALL_OPS_MAP[operator])
|
671
|
-
(wn_op = WNode.new(:insn, wnode)).
|
672
|
-
wn_op.
|
763
|
+
(wn_op = WNode.new(:insn, wnode)).wtype = wtype
|
764
|
+
wn_op.c(:operator, wasm_type: wn_op.wasm_type, operator: op)
|
673
765
|
logger.debug "Creating operator #{operator} wnode: #{wn_op}"
|
766
|
+
# special case for - unary operator transformed into (0 - x)
|
767
|
+
WNode.new(:insn, wn_op).c(:const, wasm_type: wn_op.wasm_type, value: 0) if operator == :-@
|
674
768
|
wn_op
|
675
769
|
else
|
676
770
|
raise "operator '#{operator}' not supported"
|
677
771
|
end
|
678
772
|
end
|
679
773
|
|
680
|
-
#
|
681
|
-
# attach operands
|
774
|
+
# Finish the setting of the operator node,
|
775
|
+
# attach operands and see if they need implicit
|
776
|
+
# type casting
|
682
777
|
def operands(wnode_op, wnode_recv, wnode_args)
|
683
778
|
logger.debug "Processing operands in operator wnode: #{wnode_op}..."
|
684
779
|
# Do not post process operands if the operator
|
@@ -696,26 +791,48 @@ module Rlang::Parser
|
|
696
791
|
#wnode_recv = wnode_op.children[0]
|
697
792
|
#wnode_args = wnode_op.children[1..-1]
|
698
793
|
# First find out the wtype that has precedence
|
699
|
-
|
794
|
+
leading_wtype = self.class.leading_wtype(wnode_recv, *wnode_args)
|
700
795
|
|
701
|
-
wnode_op.wtype =
|
702
|
-
logger.debug "leading type cast: #{
|
796
|
+
wnode_op.wtype = leading_wtype
|
797
|
+
logger.debug "leading type cast: #{leading_wtype}"
|
703
798
|
|
704
799
|
# Attach receiver and argument to the operator wnode
|
705
|
-
# type casting them if necessary
|
706
|
-
|
707
|
-
|
800
|
+
# type casting them if necessary
|
801
|
+
# Note : normally for an operator there is only one argument
|
802
|
+
# but process args as if they were many one day.
|
803
|
+
logger.debug "Perform implicit type casting of receviver and operator arg(s)"
|
804
|
+
self.cast(wnode_recv, leading_wtype).reparent_to(wnode_op)
|
805
|
+
wnode_args.each do |wna|
|
806
|
+
self.cast(wna, leading_wtype).reparent_to(wnode_op)
|
807
|
+
end
|
808
|
+
|
809
|
+
# Once operands casting is done, see if we need the signed or unsigned
|
810
|
+
# version of the native operator
|
811
|
+
# NOTE : At this stage, after the operands casting both of them
|
812
|
+
# should either be signed or unsigned, hence the XNOR sanity check
|
813
|
+
# below
|
814
|
+
if ALL_SIGNED_OPS_MAP.values.include? op
|
815
|
+
if !((signed = wnode_recv.wtype.signed?) ^ (wnode_args.empty? ? true : wnode_args.first.wtype.signed?))
|
816
|
+
wnode_op.wargs[:operator] = SIGNED_OPS[signed ? :signed : :unsigned][op]
|
817
|
+
logger.debug "Receiver has wtype #{wnode_recv.wtype} / Argument has wtype #{wnode_args.first.wtype}"
|
818
|
+
logger.debug "Replacing #{op} operator with #{wnode_op.wargs[:operator]}"
|
819
|
+
op = wnode_op.wargs[:operator]
|
820
|
+
else
|
821
|
+
raise "Type mismatch between operands. Receiver is #{wnode_recv.wtype} and argument is #{wnode_args.first.wtype}"
|
822
|
+
end
|
823
|
+
end
|
708
824
|
|
709
825
|
# if the receiver is a class object and not
|
710
826
|
# a native integer then pointer arithmetic
|
711
827
|
# applies (like in C)
|
712
828
|
if wnode_recv.wtype.class?
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
#
|
717
|
-
if [:add, :sub].include?
|
718
|
-
(wn_mulop = WNode.new(:insn, wnode_op)).
|
829
|
+
raise "Only #{LEGAL_CLASS_WASM_OPS.join(', ')} operators are supported on objects (got #{op} in #{wnode_op})" \
|
830
|
+
unless LEGAL_CLASS_WASM_OPS.include?(op)
|
831
|
+
# if :add or :sub operator then multiply arg by size of object
|
832
|
+
# like in C
|
833
|
+
if [:add, :sub].include? op
|
834
|
+
(wn_mulop = WNode.new(:insn, wnode_op)).wtype = WType::DEFAULT
|
835
|
+
wn_mulop.c(:operator, wasm_type: wn_mulop.wasm_type, operator: :mul)
|
719
836
|
WNode.new(:insn, wn_mulop).c(:call, func_name: "$#{wnode_recv.wtype.name}::#{SIZE_METHOD}")
|
720
837
|
wnode_args.first.reparent_to(wn_mulop)
|
721
838
|
else
|
@@ -794,14 +911,35 @@ module Rlang::Parser
|
|
794
911
|
# generate code for method call
|
795
912
|
def send_method(wnode, class_path, method_name, method_type)
|
796
913
|
logger.debug "In call generator for #{class_path}::#{method_name}"
|
797
|
-
k = wnode.find_class_or_module(class_path)
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
914
|
+
if k = wnode.find_class_or_module(class_path)
|
915
|
+
method = wnode.find_method(k, method_name, method_type)
|
916
|
+
if k.wtype.native? && ALL_OPS_MAP.has_key?(method_name)
|
917
|
+
# An Rlang method exist for this class but methods corresponding to
|
918
|
+
# Webassembly native operators applied to native Webassembly types
|
919
|
+
# (I32, I64, F32, F64) **cannot** be overriden by instance methods
|
920
|
+
# in Rlang code
|
921
|
+
if method
|
922
|
+
logger.warn "Rlang #{class_path}::#{method_name} method ignored. Native operator has precedence"
|
923
|
+
end
|
924
|
+
logger.debug "Apply native operator #{method_name} to native wtype #{class_path}"
|
925
|
+
wn_call = self.native_operator(wnode, method_name, WType.new(class_path))
|
926
|
+
elsif !k.wtype.native? && ALL_OPS_MAP.has_key?(method_name) && method.nil?
|
927
|
+
# Similarly if the Class is not a native type (a regular class)
|
928
|
+
# and the Class doesn't provide its own method implementation of the native
|
929
|
+
# operator then apply the native operands
|
930
|
+
logger.debug "Apply native operator #{method_name} to class found : #{class_path}"
|
931
|
+
wn_call = self.native_operator(wnode, method_name, WType.new(class_path))
|
932
|
+
elsif method
|
933
|
+
logger.debug "Found method #{method.name} in class #{method.klass.name}"
|
934
|
+
(wn_call = WNode.new(:insn, wnode)).c(:call, func_name: method.wasm_name)
|
935
|
+
wn_call.wtype = method.wtype
|
936
|
+
wn_call
|
937
|
+
else
|
938
|
+
raise "Unknown method '#{method_name}' in class #{class_path}"
|
939
|
+
end
|
803
940
|
elsif ALL_OPS_MAP.has_key? method_name
|
804
|
-
#
|
941
|
+
# It is a native Wasm operator
|
942
|
+
logger.debug "Native operator found : #{class_path}::#{method_name}"
|
805
943
|
wn_call = self.native_operator(wnode, method_name, WType.new(class_path))
|
806
944
|
else
|
807
945
|
raise "Unknown method '#{method_name}' in class #{class_path}"
|
@@ -838,8 +976,8 @@ module Rlang::Parser
|
|
838
976
|
end
|
839
977
|
|
840
978
|
def while(wnode)
|
841
|
-
(wnb = WNode.new(:insn, wnode)).c(:block)
|
842
|
-
(wnl = WNode.new(:insn, wnb)).c(:loop)
|
979
|
+
(wnb = WNode.new(:insn, wnode)).c(:block, label: wnb.set_label)
|
980
|
+
(wnl = WNode.new(:insn, wnb)).c(:loop, label: wnl.set_label)
|
843
981
|
(wnbi = WNode.new(:insn, wnl)).c(:br_if, label: wnb.label)
|
844
982
|
return wnb,wnbi,wnl
|
845
983
|
end
|
@@ -849,7 +987,7 @@ module Rlang::Parser
|
|
849
987
|
# negate the original while condition
|
850
988
|
def while_cond(wnode, wnode_cond_exp)
|
851
989
|
wn_eqz = WNode.new(:insn, wnode)
|
852
|
-
wn_eqz.c(:eqz,
|
990
|
+
wn_eqz.c(:eqz, wasm_type: wnode_cond_exp.wasm_type)
|
853
991
|
wnode_cond_exp.reparent_to(wn_eqz)
|
854
992
|
wn_eqz
|
855
993
|
end
|
data/lib/rlang/parser/wnode.rb
CHANGED
@@ -52,14 +52,14 @@ module Rlang::Parser
|
|
52
52
|
br_if: 'br_if %{label}',
|
53
53
|
br: 'br %{label}',
|
54
54
|
inline: '%{code}',
|
55
|
-
attr_getter: %q{func %{func_name} (param $_self_ i32) (result %{
|
56
|
-
(%{
|
57
|
-
attr_setter: %q{func %{func_name} (param $_self_ i32) (param %{attr_name} %{
|
55
|
+
attr_getter: %q{func %{func_name} (param $_self_ i32) (result %{wasm_type})
|
56
|
+
(%{wasm_type}.load offset=%{offset} (local.get $_self_))},
|
57
|
+
attr_setter: %q{func %{func_name} (param $_self_ i32) (param %{attr_name} %{wasm_type}) (result %{wasm_type})
|
58
58
|
(local.get %{attr_name})
|
59
|
-
(%{
|
60
|
-
class_size: %q{func %{func_name} (result %{
|
61
|
-
(%{
|
62
|
-
comment: ';; %{
|
59
|
+
(%{wasm_type}.store offset=%{offset} (local.get $_self_) (local.get %{attr_name}))},
|
60
|
+
class_size: %q{func %{func_name} (result %{wasm_type})
|
61
|
+
(%{wasm_type}.const %{size})},
|
62
|
+
comment: ';; %{text}',
|
63
63
|
memory: 'memory $0 %{min} %{max}',
|
64
64
|
module: 'module %{module}'
|
65
65
|
}
|
@@ -126,17 +126,37 @@ module Rlang::Parser
|
|
126
126
|
def c(template, wargs = {})
|
127
127
|
raise "Error: unknown WASM code template (#{template})" unless T.has_key? template
|
128
128
|
raise "Error: this WNode is already populated with instruction #{@template}" if @template
|
129
|
-
if [:loop, :block].include? template
|
130
|
-
|
131
|
-
end
|
129
|
+
#if [:loop, :block].include? template
|
130
|
+
# wargs[:label] = self.set_label
|
131
|
+
#end
|
132
132
|
@template = template
|
133
133
|
@wargs = wargs
|
134
134
|
end
|
135
135
|
|
136
136
|
def wasm_code
|
137
|
-
@
|
137
|
+
return '' unless T[@template]
|
138
138
|
wargs = {}
|
139
139
|
@wargs.each { |k, v| wargs[k] = (v.is_a?(Proc) ? v.call : v) }
|
140
|
+
# Because WNode#to_s generate wasm code from sometimes incomplete
|
141
|
+
# information, we must add the wasm_type key if needed by the template
|
142
|
+
if T[@template].index("%{wasm_type}")
|
143
|
+
puts "*** adding wasm_type to #{T[@template]}" unless wargs.has_key? :wasm_type
|
144
|
+
wargs[:wasm_type] ||= self.wasm_type
|
145
|
+
end
|
146
|
+
|
147
|
+
# debug code to activate if you get
|
148
|
+
# a Ruby warning on too many or too few arguments
|
149
|
+
# wargs.each do |k, v|
|
150
|
+
# if T[@template].index("%{#{k.to_s}}").nil?
|
151
|
+
# puts "**** Error wargs missing keys in wargs : template: #{@template} / wargs: #{wargs.inspect}"
|
152
|
+
# end
|
153
|
+
# end
|
154
|
+
#T[@template].scan(/%{([^}]+)}/).flatten.each do |k|
|
155
|
+
# unless wargs.has_key? k.to_sym
|
156
|
+
# puts "**** Error wargs missing keys in template: template: #{@template} / wargs: #{wargs.inspect}"
|
157
|
+
# end
|
158
|
+
# end
|
159
|
+
#logger.debug "code template : #{@template} / T : #{T[@template]} / wargs : #{wargs.inspect}" if @template
|
140
160
|
T[@template] ? T[@template] % wargs : ''
|
141
161
|
end
|
142
162
|
|
@@ -154,8 +174,13 @@ module Rlang::Parser
|
|
154
174
|
logger.debug "Setting wtype #{wtype} for wnode #{self}"
|
155
175
|
@wtype = wtype
|
156
176
|
@method.wtype = @wtype if self.method?
|
157
|
-
# update wasm_type template arg accordingly
|
158
|
-
|
177
|
+
# update wasm_type template arg accordingly if ti is already set
|
178
|
+
# (this is needed in some cases - e.g. operands - where node wtype
|
179
|
+
# is known once the wtypes of the children nodes are known )
|
180
|
+
if @wtype && @wargs.has_key?(:wasm_type)
|
181
|
+
logger.debug "Updating @wargs[:wasm_type] from '#{@wargs[:wasm_type]}' to '#{@wtype.wasm_type}'"
|
182
|
+
@wargs[:wasm_type] = @wtype.wasm_type
|
183
|
+
end
|
159
184
|
logger.debug "type #{self.type} wargs #{self.wargs} wtype #{@wtype}"
|
160
185
|
@wtype
|
161
186
|
end
|
@@ -715,7 +740,9 @@ module Rlang::Parser
|
|
715
740
|
logger.debug "children: #{self} / #{children.map(&:head)}" if self.link
|
716
741
|
|
717
742
|
case @type
|
718
|
-
# Section nodes
|
743
|
+
# Section nodes
|
744
|
+
when :comment
|
745
|
+
"\n%s%s" % [indent, self.wasm_code]
|
719
746
|
when :imports
|
720
747
|
"\n%s;;============= %s SECTION ===============\n" % [indent, @type.to_s.upcase] +
|
721
748
|
children.map { |wn| wn.transpile(depth) }.join('')
|