rlang 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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