rlang 0.5.0 → 0.5.1

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.
@@ -6,6 +6,7 @@
6
6
 
7
7
  require_relative './malloc'
8
8
  require_relative './kernel'
9
+ require_relative './string'
9
10
 
10
11
  class Object
11
12
 
@@ -20,4 +21,9 @@ class Object
20
21
  result :none
21
22
  Malloc.free(object_ptr)
22
23
  end
24
+
25
+ def to_s
26
+ result :String
27
+ "Object <addr>"
28
+ end
23
29
  end
@@ -0,0 +1,29 @@
1
+ # Rubinius WebAssembly VM
2
+ # Copyright (c) 2019-2020, Laurent Julliard and contributors
3
+ # All rights reserved.
4
+ #
5
+ # Rlang standard library classes and modules
6
+ # and runtime initialization
7
+ #
8
+ require_relative './rlang_core'
9
+ require_relative './wasi'
10
+ require_relative './io'
11
+
12
+ class Rlang
13
+ def self.init
14
+ # WASI init: setup ARGC, ARGV, etc...
15
+ errno = WASI.init
16
+
17
+ # IO init: setup fd of stdin, out and err
18
+ # This code cannot be executed within io.rb
19
+ # as STDxxx can only be used after io.rb is
20
+ # compiled
21
+ STDIN.fd = WASI::STDIN_FD
22
+ STDOUT.fd = WASI::STDOUT_FD
23
+ STDERR.fd = WASI::STDERR_FD
24
+ $stdin = STDIN
25
+ $stdout = STDOUT
26
+ $stderr = STDERR
27
+ errno
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ # Rubinius WebAssembly VM
2
+ # Copyright (c) 2019-2020, Laurent Julliard and contributors
3
+ # All rights reserved.
4
+ #
5
+ # Rlang core library classes and modules
6
+ #
7
+ require_relative './type'
8
+ require_relative './memory'
9
+ require_relative './unistd'
10
+ require_relative './malloc'
11
+ require_relative './object'
12
+ require_relative './kernel'
13
+ require_relative './string'
14
+ require_relative './array'
@@ -11,8 +11,18 @@ class String
11
11
  # ptr is a simple memory address of type
12
12
  # :I32 (see it as the equivalent of a
13
13
  # char * in C)
14
+
15
+ # There are 3 ways to initialize a new String object
16
+ # * with a string literal (e.g. mystring = "Hello World!")
17
+ # * by pointing at an existing memory location (e.g. String.new(ptr, length))
18
+ # * by asking Rlang to allocate the String space when ptr is NULL (e.g. String.new(0, length))
14
19
  def initialize(ptr, length)
15
- @ptr = ptr
20
+ result :none
21
+ if ptr == 0
22
+ @ptr = Malloc.malloc(length)
23
+ else
24
+ @ptr = ptr
25
+ end
16
26
  @length = length
17
27
  end
18
28
 
@@ -28,4 +38,7 @@ class String
28
38
  String.new(new_ptr, new_length)
29
39
  end
30
40
 
41
+ def size; @length; end
42
+ def to_s; self; end
43
+
31
44
  end
@@ -2,6 +2,31 @@
2
2
  # Copyright (c) 2019, Laurent Julliard and contributors
3
3
  # All rights reserved.
4
4
  #
5
+ # Integer 32 methods
6
+
7
+ class String; end
8
+
5
9
  class I32
10
+ ConvertString = "0123456789ABCDEF"
11
+
6
12
  def self.size; 4; end
13
+
14
+ def to_str(base)
15
+ result :String
16
+ "0"
17
+ =begin
18
+ # TODO
19
+ if n < base
20
+ return convertString[n]
21
+ else
22
+ return toStr(n//base,base) + convertString[n%base]
23
+ end
24
+ =end
25
+ end
26
+
27
+ def to_s
28
+ result :String
29
+ self.to_str(10)
30
+ end
31
+
7
32
  end
@@ -2,6 +2,10 @@
2
2
  # Copyright (c) 2019, Laurent Julliard and contributors
3
3
  # All rights reserved.
4
4
  #
5
+ # Integer 64 methods
6
+
7
+ class String; end
8
+
5
9
  class I64
6
10
  def self.size; 8; end
7
11
  end
@@ -0,0 +1,133 @@
1
+ # Rubinius WebAssembly VM
2
+ # Copyright (c) 2019-2020, Laurent Julliard and contributors
3
+ # All rights reserved.
4
+ #
5
+ # WASI Interface to WASM runtime
6
+
7
+ require_relative './array'
8
+ require_relative './string'
9
+
10
+ ARGC = 0
11
+ ARGV = 0.cast_to(:Array32)
12
+
13
+ class WASI
14
+ STDIN_FD = 0
15
+ STDOUT_FD = 1
16
+ STDERR_FD = 2
17
+ @@argv_buf_size = 0
18
+
19
+ class CIOVec
20
+ attr_reader :ciovs
21
+ attr_type ciovs: :Array32
22
+
23
+ def initialize(n)
24
+ result :none
25
+ # a ciov is list of n (address to buffer, length of buffer)
26
+ @n = n
27
+ @index = 0
28
+ @max_index = 2 * @n
29
+ @ciovs = Array32.new(2*n)
30
+ end
31
+
32
+ def << (string)
33
+ arg string: :String
34
+ result :CIOVec
35
+ raise "CIOVec full !" if @index >= @max_index
36
+ @ciovs[@index] = string.ptr
37
+ @ciovs[@index+1] = string.length
38
+ @index += 2
39
+ self
40
+ end
41
+
42
+ def free
43
+ result :none
44
+ @ciovs.free
45
+ Object.free(self)
46
+ end
47
+ end
48
+
49
+ # An IOVec is an array of (address to buffer, length of buffer)
50
+ # where WASM runtime can read data coming from the WASM module
51
+ class IOVec
52
+ attr_reader :iovs
53
+ attr_type iovs: :Array32
54
+
55
+ IOV_SIZE = 1024
56
+
57
+ def initialize(n)
58
+ result :none
59
+ @iovs = Array32.new(2*n)
60
+ @n = n
61
+ i = 0
62
+ while i < n;
63
+ @iovs[i] = Malloc.malloc(IOV_SIZE)
64
+ @iovs[i+1] = IOV_SIZE
65
+ i += 2
66
+ end
67
+ end
68
+
69
+ def free
70
+ result :none
71
+ i = 0
72
+ while i < @n
73
+ Malloc.free(@iovs[i])
74
+ i += 2
75
+ end
76
+ @iovs.free
77
+ Object.free(self)
78
+ end
79
+ end
80
+
81
+ # Import WASI functions
82
+ import :wasi_unstable, :args_sizes_get
83
+ def self.args_sizes_get(argc, args_size); end
84
+
85
+ import :wasi_unstable, :args_get
86
+ def self.args_get(argv, argv_buf); end
87
+
88
+ import :wasi_unstable, :fd_write
89
+ def self.fd_write(fd, iovs, iovs_count, nwritten_ptr); end
90
+
91
+ import :wasi_unstable, :fd_read
92
+ def self.fd_read(fd, iovs, iovs_count, nread_ptr); end
93
+
94
+ import :wasi_unstable, :proc_exit
95
+ def self.proc_exit(exitcode); result :none; end
96
+
97
+ # Initialize WASI environment and related Rlang
98
+ # objects (ARGC, ARGV,...)
99
+ def self.init
100
+ local argv: :Array32
101
+
102
+ # Get number of arguments and their total size
103
+ errno = WASI.args_sizes_get(ARGC.addr, @@argv_buf_size.addr)
104
+ raise "Errno args_sizes_get" if errno != 0
105
+
106
+ # Allocate memory areas to receive the argument pointers
107
+ # (argv) and the argument strings (argv_buf)
108
+ #
109
+ # Setup an extra slot in argv array to simplify the
110
+ # loop below
111
+ argv = Array32.new(ARGC+1) #Malloc.malloc((ARGC+1) * 4) # Assuming I32 for pointers
112
+ argv_buf = Malloc.malloc(@@argv_buf_size)
113
+ errno = WASI.args_get(argv.ptr, argv_buf)
114
+
115
+ raise "Errno args_get" if errno != 0
116
+ argv[ARGC] = argv[0] + @@argv_buf_size
117
+
118
+ # Workaround to avoid dynamic constant assignment error
119
+ Memory.store32(ARGV.addr, Array32.new(ARGC))
120
+
121
+ # Now scan through arguments and turn them into a Rlang
122
+ # Array of Strings (like ARGV in Ruby)
123
+ i = 0
124
+ while i < ARGC
125
+ length = argv[i+1] - argv[i] - 1 # -1 because of null terminated
126
+ ARGV[i] = String.new(argv[i], length)
127
+ i += 1
128
+ end
129
+ return errno
130
+ end
131
+
132
+ end
133
+
@@ -29,23 +29,19 @@ module Rlang::Parser
29
29
 
30
30
  include Log
31
31
 
32
- ARITHMETIC_OPS = [:+, :-, :*, :/, :%, :&, :|, :^, :>>, :<<]
33
- RELATIONAL_OPS = [:==, :!=, :>, :<, :>=, :<=, :'>s', :'<s', :'>=s', :'>=s']
34
- UNARY_OPS = [:'!']
35
-
36
- # Type cast order in decreading order of precedence
37
- TYPE_CAST_PRECEDENCE = [Type::F64, Type::F32, Type::I64, Type::I32]
38
-
39
32
  # WARNING!! THIS IS A **VERY** NASTY HACK PRETENDING
40
33
  # THAT THIS int VALUE means NIL. It's totally unsafe
41
34
  # of course as an expression could end up evaluating
42
35
  # to this value and not be nil at all. But I'm using
43
36
  # it for now in the xxxx_with_result_type variants of
44
37
  # some parsing methods (if, while,...)
38
+ # NOTE: those variants with result type are **NOT** the
39
+ # ones used by Rlang right now
45
40
  NIL = 999999999
46
41
 
47
- # export toggle for method declaration
48
- @@export = false
42
+ # export and import toggle for method declaration
43
+ @@export, @@export_name = false, nil
44
+ @@import, @@import_module_name, @@import_function_name = false, nil, nil
49
45
 
50
46
 
51
47
  attr_accessor :wgenerator, :source, :config
@@ -121,6 +117,8 @@ module Rlang::Parser
121
117
  raise "wnode type is incorrect (got #{wnode})" unless wnode.is_a?(WNode) || wnode.nil?
122
118
  logger.debug "\n---------------------->>\n" +
123
119
  "Parsing node: #{node}, wnode: #{wnode.head}, keep_eval: #{keep_eval}"
120
+ # Nothing to parse
121
+ return if node.nil?
124
122
 
125
123
  case node.type
126
124
  when :self
@@ -274,8 +272,12 @@ module Rlang::Parser
274
272
  super_class_path = _build_const_path(super_class_const_node)
275
273
  wn_class = @wgenerator.klass(wnode, class_path, super_class_path)
276
274
 
275
+ # If body node is nil then this must be interpreted as
276
+ # a class declaration (no implementation yet)
277
+ return wn_class unless body_node
278
+
277
279
  # Parse the body of the class
278
- parse_node(body_node, wn_class) if body_node
280
+ parse_node(body_node, wn_class)
279
281
 
280
282
  # We finished parsing the class body so
281
283
  # 1) postprocess instance variables
@@ -962,7 +964,9 @@ module Rlang::Parser
962
964
 
963
965
  # create corresponding func node
964
966
  wn_method = @wgenerator.def_method(wnode, method_name, :class)
965
- wn_method.method.export! if (@@export || self.config[:export_all])
967
+ if @@import
968
+ wn_import = @wgenerator.import_method(wn_method, @@import_module_name, @@import_function_name)
969
+ end
966
970
 
967
971
  # collect method arguments
968
972
  parse_args(arg_nodes, wn_method)
@@ -970,7 +974,7 @@ module Rlang::Parser
970
974
  # that we know what the return type is in advance
971
975
  # If :nil for instance then it may change the way
972
976
  # we generate code in the body of the method
973
- if (result_node = body_node.children.find {|n| n.respond_to?(:type) && n.type == :send && n.children[1] == :result})
977
+ if body_node && (result_node = body_node.children.find {|n| n.respond_to?(:type) && n.type == :send && n.children[1] == :result})
974
978
  logger.debug "result directive found: #{result_node}"
975
979
  parse_node(result_node, wn_method, keep_eval)
976
980
  end
@@ -987,9 +991,12 @@ module Rlang::Parser
987
991
  @wgenerator.locals(wn_method)
988
992
  @wgenerator.result(wn_method)
989
993
  @wgenerator.params(wn_method)
994
+ @wgenerator.export_method(wn_method, @@export_name) if (@@export || self.config[:export_all])
990
995
  logger.debug "Full method wnode: #{wn_method}"
991
- # reset export toggle
992
- @@export = false
996
+
997
+ # reset method toggles
998
+ self.class._reset_toggles
999
+
993
1000
  return wn_method
994
1001
  end
995
1002
 
@@ -1008,18 +1015,19 @@ module Rlang::Parser
1008
1015
  logger.debug "Defining instance method: #{method_name}"
1009
1016
 
1010
1017
  # create corresponding func node
1011
- # Note: because module inclusion generate both instance
1012
- # and class methods we may get two methods wnode
1013
1018
  wn_method = @wgenerator.def_method(wnode, method_name, :instance)
1014
- wn_method.method.export! if (@@export || self.config[:export_all])
1019
+ if @@import
1020
+ wn_import = @wgenerator.import_method(wn_method, @@import_module_name, @@import_function_name)
1021
+ end
1015
1022
 
1016
1023
  # collect method arguments
1017
1024
  wn_args = parse_args(arg_nodes, wn_method)
1025
+
1018
1026
  # Look for any result directive and parse it so
1019
1027
  # that we know what the return type is in advance
1020
1028
  # If :nil for instance then it may change the way
1021
1029
  # we generate code in the body of the method
1022
- if (result_node = body_node.children.find {|n| n.respond_to?(:type) && n.type == :send && n.children[1] == :result})
1030
+ if body_node && (result_node = body_node.children.find {|n| n.respond_to?(:type) && n.type == :send && n.children[1] == :result})
1023
1031
  logger.debug "result directive found: #{result_node}"
1024
1032
  parse_node(result_node, wn_method, keep_eval)
1025
1033
  end
@@ -1036,9 +1044,11 @@ module Rlang::Parser
1036
1044
  @wgenerator.locals(wn_method)
1037
1045
  @wgenerator.result(wn_method)
1038
1046
  @wgenerator.params(wn_method)
1047
+ @wgenerator.export_method(wn_method, @@export_name) if (@@export || self.config[:export_all])
1039
1048
  logger.debug "Full method wnode: #{wn_method}"
1040
- # reset export toggle
1041
- @@export = false
1049
+
1050
+ # reset method toggles
1051
+ self.class._reset_toggles
1042
1052
 
1043
1053
  # if we are in a module then also define
1044
1054
  # the class method because we don't know
@@ -1067,10 +1077,12 @@ module Rlang::Parser
1067
1077
  when /^\./
1068
1078
  # If file starts with . then look for file in pwd
1069
1079
  load_path = [Dir.pwd]
1080
+ =begin
1070
1081
  when /^rlang/
1071
1082
  # If it starts with rlang then look for it in the
1072
1083
  # installed rlang gem in addition to load path
1073
1084
  load_path = self.config[:LOAD_PATH] + $LOAD_PATH
1085
+ =end
1074
1086
  else
1075
1087
  load_path = self.config[:LOAD_PATH]
1076
1088
  load_path = [Dir.pwd] if self.config[:LOAD_PATH].empty?
@@ -1090,7 +1102,7 @@ module Rlang::Parser
1090
1102
  end
1091
1103
  end
1092
1104
  end
1093
- raise LoadError, "no such file to load: #{full_path_file}" unless full_path_file
1105
+ raise LoadError, "no such file to load: #{file}" unless full_path_file
1094
1106
 
1095
1107
  # Now load the file
1096
1108
  if File.extname(full_path_file) == '.wat'
@@ -1274,6 +1286,49 @@ module Rlang::Parser
1274
1286
  return wn_cast
1275
1287
  end
1276
1288
 
1289
+ # addr method applied to statically allocated variables
1290
+ # only constant and class variables returns their address
1291
+ # in memory
1292
+ #
1293
+ # Example
1294
+ # @@argv_bu_size.addr
1295
+ # ---
1296
+ # (send (cvar :@@argv_buf_size) :addr)
1297
+ #
1298
+ if method_name == :addr
1299
+ if recv_node.type == :const
1300
+ # Build constant path from embedded const sexp
1301
+ const_path = _build_const_path(recv_node)
1302
+ full_const_name = const_path.join('::')
1303
+
1304
+ # See if constant exists. It should at this point
1305
+ unless (const = wnode.find_const(const_path))
1306
+ raise "unknown constant #{full_const_name}"
1307
+ end
1308
+ wn_const_addr = @wgenerator.const_addr(wnode, const)
1309
+
1310
+ # Drop last evaluated result if asked to
1311
+ @wgenerator.drop(wnode) unless keep_eval
1312
+ return wn_const_addr
1313
+
1314
+ elsif recv_node.type == :cvar
1315
+ raise "Class variable can only be accessed in method scope" \
1316
+ unless wnode.in_method_scope?
1317
+ cv_name = recv_node.children.first
1318
+ if (cvar = wnode.find_cvar(cv_name))
1319
+ wn_cvar_addr = @wgenerator.cvar_addr(wnode, cvar)
1320
+ else
1321
+ raise "unknown class variable #{cv_name}"
1322
+ end
1323
+ # Drop last evaluated result if asked to
1324
+ @wgenerator.drop(wnode) unless keep_eval
1325
+ return wn_cvar_addr
1326
+
1327
+ else
1328
+ # Do nothing. This will be treated as a regular method call
1329
+ end
1330
+ end
1331
+
1277
1332
  # A that stage it's a method call of some sort
1278
1333
  # (call on class or instance)
1279
1334
  return parse_send_method_lookup(node, wnode, keep_eval)
@@ -1310,6 +1365,10 @@ module Rlang::Parser
1310
1365
  return parse_send_export(node, wnode, keep_eval)
1311
1366
  end
1312
1367
 
1368
+ if recv_node.nil? && method_name == :import
1369
+ return parse_send_import(node, wnode, keep_eval)
1370
+ end
1371
+
1313
1372
  if recv_node.nil? && method_name == :local
1314
1373
  return parse_send_local(node, wnode, keep_eval)
1315
1374
  end
@@ -1420,9 +1479,57 @@ module Rlang::Parser
1420
1479
 
1421
1480
  # Directive to declare the current method
1422
1481
  # in the WASM exports
1482
+ # Example
1483
+ #
1484
+ # export
1485
+ # ---
1486
+ # (send nil :export)
1487
+ # OR
1488
+ # export :function_name
1489
+ # ---
1490
+ # (send nil :export
1491
+ # (sym :function_name))
1492
+ #
1493
+ # With out an explicit function name, the export name
1494
+ # will be automatically built from the class/method names
1423
1495
  def parse_send_export(node, wnode, keep_eval)
1424
- raise "export must be used in class scope" unless wnode.in_class_scope?
1496
+ logger.debug "Export directive found for..."
1497
+ raise "export must be used in class scope" unless wnode.in_class_or_module_scope?
1425
1498
  @@export = true
1499
+ if (function_node = node.children[2])
1500
+ raise "export function name must be a symbol (got #{function_node})" \
1501
+ unless function_node.type == :sym
1502
+ @@export_name = function_node.children.last
1503
+ end
1504
+ logger.debug "... #{@@export_name}"
1505
+ return
1506
+ end
1507
+
1508
+ # Directive to declare the current method
1509
+ # in the WASM imports
1510
+ # Example
1511
+ #
1512
+ # import :module_name, :function_name
1513
+ # ---
1514
+ # (send nil :import
1515
+ # (sym :mod)
1516
+ # (sym :func))
1517
+ #
1518
+ def parse_send_import(node, wnode, keep_eval)
1519
+ logger.debug "Import directive found for..."
1520
+ raise "export must be used in class scope" unless wnode.in_class_or_module_scope?
1521
+ raise "import expects 2 arguments (got #{node.children.count - 2})" \
1522
+ unless node.children.count == 4
1523
+
1524
+ module_node, function_node = node.children[2..-1]
1525
+ raise "import module name must be a symbol (got #{module_node})" \
1526
+ unless module_node.type == :sym
1527
+ raise "import function name must be a symbol (got #{function_node})" \
1528
+ unless function_node.type == :sym
1529
+ @@import = true
1530
+ @@import_module_name = module_node.children.last
1531
+ @@import_function_name = function_node.children.last
1532
+ logger.debug "... #{@@import_module_name}, #{@@import_function_name}"
1426
1533
  return
1427
1534
  end
1428
1535
 
@@ -2145,6 +2252,11 @@ module Rlang::Parser
2145
2252
  const_path
2146
2253
  end
2147
2254
 
2255
+ def self._reset_toggles
2256
+ @@export, @@export_name = false, nil
2257
+ @@import, @@import_module_name, @@import_function_name = false, nil, nil
2258
+ end
2259
+
2148
2260
  def dump
2149
2261
  @ast
2150
2262
  end