rlang 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7fd26f4ab304200f0319e23e1e8529e538ea058a58fe4571e0e3525c2c5ce14
4
- data.tar.gz: 88bb4ec22b5a2571944693da8ea21d0e1379fb41b3c9ae85d91851a2f8569d9b
3
+ metadata.gz: 38b11b7ed747a6a72f0a46a2c498efef419039a9bf85b5ba6442b7fd8f5300fc
4
+ data.tar.gz: 2e1c220542d7b7356c6b963edb2d34e45cdbd0cd556dec6a718a4f663b5651b5
5
5
  SHA512:
6
- metadata.gz: f6c92dcfec61a4df573d2c911882f346fb242315596220e25aa547998f137b8578f18ca62467c77af2b75e92ea73d87a21cbc37eae1bd56e89038bc62fca2264
7
- data.tar.gz: b9e09724905b3175904258174c834b8d52b9f7ae99c7046e81e13f97e699d94b53f110b70763283992e71c7d6a2f3d0eb9d9cba60a30a1ef319338f1ec580919
6
+ metadata.gz: 71fc2a6a83c414edbc2dbfca1b1e465a1d265ba80350101e4536178e1959ccf4a823478c7c139acde6d3e88d56d25d0a2fd9d4a781b2c1da919b4de0f5a4268a
7
+ data.tar.gz: 65969682077beb48030e5f9ffff0f1d97aa2ee2ea48e2142feac74e3a687661aa5b7afcbb8db26b8b1da8936e7dbeca4d09e9a8be5108dda8dd6325fb15a3c99
@@ -1,9 +1,12 @@
1
+ ## 0.5.1
2
+ * Imported WASM methods can now be defined
3
+ * Preliminary version of WASI interface and IO class added to Rlang library
4
+
1
5
  ## 0.5.0
2
- * Class attributes syntac now identical to plain Ruby
3
- * Very preliminary version of Rlang String class
6
+ * Class attributes syntax now identical to plain Ruby
4
7
  * Class in class definition and module supported
5
8
  * Class inheritance
6
- * Basic array class
9
+ * Basic Array and String class in Rlang library
7
10
 
8
11
  ## 0.4.0
9
12
  * Object instances, instance methods and instance variables now supported
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rlang (0.5.0)
4
+ rlang (0.5.1)
5
5
  parser
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Rlang : a native Ruby to WebAssembly compiler
1
+ # Rlang : a Ruby-like language compiled to WebAssembly
2
2
 
3
3
  Rlang is meant to generate fast and uncluttered [WebAssembly](https://webassembly.org) code from the comfort of the Ruby language.
4
4
 
@@ -6,14 +6,12 @@ Rlang is actually two things: 1) a subset of the Ruby language and 2) a **compil
6
6
 
7
7
  So in summary what Rlang does is:
8
8
  <p align="center"><b>
9
- Ruby source code &rarr; Rlang compiler &rarr; WebAssembly bytecode
9
+ Rlang source code &rarr; Rlang compiler &rarr; WebAssembly bytecode
10
10
  </b></p>
11
11
 
12
- Rlang must not be confused with other projects claiming to "compile" Ruby to WebAssembly. What they really do is to actually compile a mruby VM written in C in Webassembly (using tools like emscripten) and then run that VM in a Webassembly runtime.
13
-
14
12
  Rlang can be seen as a foundational language that can help you quickly develop and debug high performance WebAssembly modules. For the rationale behind the creation of Rlang see [below](#why-rlang).
15
13
 
16
- This is still a young project but Rlang has already been successfully tested with some real code such as the dynamic memory allocator provided with the Rlang library. It will keep mproving over time, always with the goal of generating crisp and uncluttered WebAssembly code.
14
+ This is still a young project but Rlang has already been successfully tested with some real code such as the dynamic memory allocator provided with the Rlang library. It will keep improving over time, always with the goal of generating crisp and uncluttered WebAssembly code.
17
15
 
18
16
  If you want to help with Rlang see [How you can help](#how-you-can-help).
19
17
 
@@ -22,6 +20,8 @@ If you want to help with Rlang see [How you can help](#how-you-can-help).
22
20
  * **WABT toolkit**: the rlang compiler can generate both WebAssembly source code (WAT file) and WebAssembly bytecode (WASM file). To generate WASM bytecode the rlang compiler uses wat2wasm. This utility is part of the [WABT toolkit](https://github.com/WebAssembly/wabt)
23
21
  * **wasmer runtime** (optional): [Wasmer](https://wasmer.io/) is a fast WebAssembly runtime. You'll need it if you want to run the test suite from the source repo. You can also use it to run the compiled WASM code generated by the rlang compiler. You can get Wasmer at [wasmer.io](https://wasmer.io/)
24
22
 
23
+ Rlang has also been successfully tested with the [Wasmtime](https://wasmtime.dev/) WebAssembly runtime.
24
+
25
25
 
26
26
  ## Installing Rlang
27
27
  Rlang is available as a gem from rubygems.org. So simply run the following command to install it:
@@ -78,7 +78,9 @@ This project was created out of the need to develop a Virtual Machine written in
78
78
 
79
79
  After a first proof of concept written directly by hand in WebAssembly (WAT source code) it became clear that writing a full fledged VM directly in WebAssembly was going to be tedious, complex and unnecessarily painful.
80
80
 
81
- Sure I could have written this VM in any of the language that can already be compiled directly to WebAssembly (C, C++, Rust, Go,...) but being fond of Ruby since 2000 I decided that I would go for a compiler capable of transforming a subset of the Ruby language directly into WebAssembly with a minimum overhead. So in a nutshell: the goal of Rlang is to let you develop efficient WebAssembly code with a reasonably high level of abstraction while keeping the generated WebAssembly code straightforward and human readable.
81
+ Sure I could have written this VM in any of the language that can already be compiled directly to WebAssembly (C, C++, Rust, Go,...) but being fond of Ruby since 2000 I decided that I would go for a compiler capable of transforming a subset of the Ruby language directly into WebAssembly with a minimum overhead.
82
+
83
+ So in a nutshell: the goal of Rlang is to let you develop efficient WebAssembly code with a reasonably high level of abstraction while keeping the generated WebAssembly code straightforward, human readable and slim.
82
84
 
83
85
  ## Why the name Rlang?
84
86
  Yes I hear you: Rlang is already the name of the R language so why use that name and aren't you introducing some confusion? Well for one I couldn't resist using that name to honor software engineering history (see below) and because, after all, the intersection between the Ruby/WebAssembly community and the R language community focused on data processing and machine learning must be quite small to say the least.
data/bin/rlang CHANGED
@@ -13,13 +13,16 @@
13
13
 
14
14
  require 'optparse'
15
15
  require 'fileutils'
16
- require 'rlang'
16
+ require 'rlang' # setup RLANG_BASE_DIR
17
17
  require 'builder'
18
18
 
19
+ RLANG_LIB_DIR = File.expand_path('./rlang/lib', RLANG_BASE_DIR)
20
+
19
21
  include Log
20
22
  logger.level = Logger::INFO
21
23
 
22
24
  options = {}
25
+ custom_load_path = []
23
26
  OptionParser.new do |opts|
24
27
  opts.banner = %q{Usage: rlang [options] filename
25
28
  read a Rlang file, check it for errors, and convert it
@@ -39,8 +42,8 @@ options:
39
42
  }
40
43
 
41
44
  opts.on("-I DIR", "--load_path DIRECTORY", "specify $LOAD_PATH directory (may be used more than once)") do |dir|
42
- options[:LOAD_PATH] ||= []
43
- options[:LOAD_PATH] << dir
45
+ custom_load_path ||= []
46
+ custom_load_path << dir
44
47
  end
45
48
 
46
49
  opts.on("-M", "--module NAME", "WASM module name") do |name|
@@ -67,6 +70,10 @@ options:
67
70
  options[:wasm] = v
68
71
  end
69
72
 
73
+ opts.on("-S", "--start FUNCTION", "Function name where execution starts (default '_start')") do |function|
74
+ options[:start] = function
75
+ end
76
+
70
77
  opts.on("-o", "--output FILE", "Write output to file") do |file|
71
78
  options[:output] = file
72
79
  end
@@ -87,9 +94,10 @@ options:
87
94
  end
88
95
  end.parse!(ARGV)
89
96
 
90
- options[:module] ||= ''
97
+ options[:module] ||= ''
91
98
  options[:memory_min] ||= 4
92
- options[:LOAD_PATH] ||= []
99
+ options[:LOAD_PATH] = custom_load_path << RLANG_LIB_DIR
100
+ options[:start] ||= '_start'
93
101
 
94
102
  fh_out = options[:output] ? File.open(options[:output], 'w') : STDOUT
95
103
 
@@ -19,6 +19,7 @@ Usage: rlang [options] rlang_file.rb
19
19
  -w, --wat Generate WAT source file
20
20
  -a, --ast Generate Ruby AST file
21
21
  -s, --wasm Generate WASM bytecode file
22
+ -S, --start FUNCTION Function name where execution starts (default '_start')
22
23
  -o, --output FILE Write output to file
23
24
  -v, --verbose [LEVEL] Verbosity level (fatal, error, warn, info, debug)
24
25
  -V, --version Displays Rlang version
@@ -30,7 +31,8 @@ Usage: rlang [options] rlang_file.rb
30
31
  * **-m, --memory MIN[,MAX]**: size of the WASM memory allocated at run time. The first argument is the initial amount of memory allocated and the second one (optional) is the maximum memory that your WASM module can allocate while running. The unit of both arguments is in number of WASM pages (4 KBytes)
31
32
  * **-w, --wat**: parse Rlang file and generate WAT source code
32
33
  * **-a, --ast**: parse Rlang file and generate the Ruby abstract syntax tree
33
- * **-w, --wat**: parse Rlang file, generate WAT source code and compile it to WASM bytecode
34
+ * **-s, --wasm**: parse Rlang file, generate WAT source code and compile it to WASM bytecode
35
+ * **-S, --start**: function to use as the execution starting point of the WASM module (default is `_start`)
34
36
  * **-o, --output FILE**: send rlang output to FILE
35
37
  * **-v, --verbose [LEVEL]**: verbosity level (fatal, error, warn, info, debug). Default is warn
36
38
  * **-V, --version**: Displays Rlang version
@@ -6,10 +6,6 @@ Ruby programmers will feel at home with Rlang and non Ruby programmers will find
6
6
 
7
7
  Still, to make this Ruby to WebAssembly compilation possible a number of trade-offs had to be made. The goal of this document is to explain the features of Rlang are how it differs from plain Ruby.
8
8
 
9
- ## The Rlang object model
10
-
11
- In Rlang you can define classes and those classes can be instantiated either statically at compile time or dynamically at runtime. There is no inheritance mechanism today between classes in the current version. Classes cannot be nested.
12
-
13
9
  ## What Rlang does
14
10
 
15
11
  Rlang provides:
@@ -84,6 +80,12 @@ end
84
80
  ```
85
81
  A Class in RLang can also inherit from another class as in Ruby. Whan a superclass is not specified, a newly defined class automatically inherits from the Object class.
86
82
 
83
+ If you need to use a class name in your code before the class is actually processed by the compiler you can use the following empty class notation to declare that the corresponding constant is actually a class. You can later reopen the class definition and define methods.
84
+
85
+ ```ruby
86
+ class String; end
87
+ ```
88
+
87
89
 
88
90
  ### Class attributes and instance variables
89
91
  Rlang support both the use of class attributes and instance variables. Class attribute declaration is happening through the `attr_accessor`, `attr_reader` or `attr_writer` directives as in plain Ruby. It actually defines a couple of things for you:
@@ -147,6 +149,7 @@ class Test
147
149
  # ...
148
150
  end
149
151
  ```
152
+ The address in memory of both class variables and constants can be accessed by using the `addr` method as in `SQUARE.addr` for instance.
150
153
 
151
154
  **IMPORTANT NOTE**: in the current version of Rlang the new method call used to allocate static objects doesn't do any initialization. That's why the new method in this context (class body or top level) doesn't accept any parameter.
152
155
 
@@ -180,7 +183,7 @@ class Main
180
183
  cube = Cube.new(10, 20, 30)
181
184
  v = cube.volume
182
185
  # ... Do what ever you have to do...
183
- Object.free(cube)
186
+ cube.free
184
187
  end
185
188
  end
186
189
  ```
@@ -246,7 +249,9 @@ end
246
249
  The `local` directive above instructs the compiler that `lvar` is of type `:I64` and the local variable mysquare is of type `Square`. Without it `lvar` would have been auto-vivified with the Wasm default type or `:I32`.
247
250
 
248
251
  ### Exporting a method
249
- In WebAssembly, you can make functions visible to the outside world by declaring them in the export section. To achieve a similar result in Rlang, you can use the `export` keyword right before a method definition.
252
+ In WebAssembly, you can make functions visible to the outside world by declaring them in the export section. To achieve a similar result in Rlang, you can use the `export` keyword right before a method definition with an optional export name of your choice.
253
+
254
+ If no function name is specified, Rlang will build it for you. WASM exported functions are named after the class name (in lower case), the method type (class or instance) and the method name. As an example the exported method in the example above will be known to the WASM runtime as the `myclass_c_visible` function (where the `_c_` means it's a class function and `_i_` an instance method).
250
255
 
251
256
  ```ruby
252
257
  class MyClass
@@ -256,18 +261,40 @@ class MyClass
256
261
  # ...
257
262
  end
258
263
 
264
+ export :seeable
265
+ def self.visible_too(arg1)
266
+ # ...
267
+ end
268
+
259
269
  def self.not_visible
260
270
  # ...
261
271
  end
262
272
  end
263
273
  ```
264
274
 
265
- Note that the `export` keyword only applies to the method definition that immediately follows. In the example above `MyClass::m_visible` will be exported by the generated WASM module whereas `MyClass::m_not_visible` will not
275
+ Note that the `export` keyword only applies to the method definition that immediately follows. In the example above `MyClass::visible` and `MyClass::visible_too` will be exported by the generated WASM module whereas `MyClass::not_visible` will not.
276
+
277
+ ### Importing a method
278
+ An import statement in WebAssembly is a way to declare the signature of a method defined outside of the current WebAssembly module and then call it from your code.
279
+
280
+ Rlang has an equivalent import statement as shown in the example below:
281
+ ```ruby
282
+ import :wasi_unstable, :proc_exit
283
+ def self.proc_exit(exitcode)
284
+ arg exitcode: :I32
285
+ result :none
286
+ end
287
+ ```
288
+
289
+ The first 2 arguments of import are the imported module name and function name as in WebAssembly and then follows a regular method definition with arguments and possibly the `arg` directive to specify argument types and a `result` directive to indicate the nature of the returned value. Two points worth highlighting here:
290
+ 1. The Rlang method name doesn't have to be the same as the function name in the import statement.
291
+ 2. As imported function are external to your Rlang module they must be declared as class methods. This is bacause defining them as instance method would automatically pass `self` as the first argument in the method call therefore changing the signature of the method.
292
+
293
+ The example above is taken from the Rlang WASI class that defines the interface with [WASI (WebAssembly System Interface)](https::wasi.dev).
266
294
 
267
- WASM exported functions are named after the class name (in lower case) followed by an underscore and the method name. So the exported method in the example above is known to the WASM runtime as the `myclass_c_visible` function (where the `_c_` means it's a class function and `_i_` an instance method)
268
295
 
269
296
  ## Rlang types
270
- The types currently supported by Rlang are integers either long (`:I32`) or double (`:I64`) or a class type. Float types (:F32, :F64) may follow in a future version. By default Rlang assumes that any integer literal, variable, argument,... is of type `:I32`. If you need it to be of a different type you must state it explicitely in the method body (see above).
297
+ The types currently supported by Rlang are integers either long (`:I32`) or double (`:I64`) or a class type. Float types (`:F32`, `:F64`) may follow in a future version. By default Rlang assumes that any integer literal, variable, argument,... is of type `:I32`. If you need it to be of a different type you must state it explicitely in the method body (see above).
271
298
 
272
299
  ### Implicit type cast
273
300
  Only in rare cases will you use the `local` directive in methods as Rlang does its best to infer the type of a variable from its first assigned value. As an example, in the code below, the fact that `arg1` is known to be an `:I64` type of argument is enough to auto-magically create lvar as in `:I64` local variable too.
@@ -8,4 +8,8 @@ class Math
8
8
  end
9
9
  return f
10
10
  end
11
- end
11
+ end
12
+
13
+ def self.main
14
+ Math.fib(12)
15
+ end
@@ -17,23 +17,10 @@ module Builder::Rlang
17
17
  # WAT source frame
18
18
  WAT_FRAME = %q{
19
19
  ;; Generated by Rlang compiler version %{version} on %{time}\n"
20
- (module %{module}
21
- (memory $0 %{memory_min} %{memory_max})
22
20
 
23
- ;; ======= EXPORTS =======
24
- (export "memory" (memory $0))
25
- %{exports}
21
+ %{code}
26
22
 
27
- ;; ======= GLOBAL VARIABLES =======
28
- %{globals}
29
-
30
- ;; ======= STATIC DATA =======
31
- %{data}
32
-
33
- ;; ======= CODE =======
34
- %{code}
35
- )
36
- }
23
+ }
37
24
 
38
25
  # source: Path to Rlang file (.rb)
39
26
  # target: Path to Wat file (.wat)
@@ -64,12 +51,6 @@ module Builder::Rlang
64
51
  end
65
52
  @tf.write(WAT_FRAME % {version: Rlang::VERSION,
66
53
  time: Time.now,
67
- module: @options[:module],
68
- memory_min: @options[:memory_min],
69
- memory_max: @options[:memory_max],
70
- exports: Rlang::Parser::Export.transpile,
71
- globals: Rlang::Parser::Global.transpile,
72
- data: Rlang::Parser::DAta.transpile,
73
54
  code: @wgenerator.root.transpile
74
55
  })
75
56
  @tf.close
@@ -2,3 +2,6 @@ require_relative "./rlang/version"
2
2
  require_relative "./utils/log"
3
3
  require_relative "./rlang/parser"
4
4
  require_relative "./builder"
5
+
6
+ # Set up path to this file
7
+ RLANG_BASE_DIR = File.expand_path(File.dirname(__FILE__))
@@ -5,13 +5,14 @@
5
5
  # 4 bytes object array class
6
6
  require_relative '../memory'
7
7
  require_relative '../kernel'
8
- require_relative '../object'
9
- require_relative '../string'
8
+ #require_relative '../string'
10
9
 
11
10
 
12
11
  class Array32
13
12
  attr_reader :count, :ptr
14
13
 
14
+ result :Kernel, :raise, :none
15
+
15
16
  # count: number of elements in Array
16
17
  # Array elements are native types or
17
18
  # pointers to objects
@@ -40,4 +41,10 @@ class Array32
40
41
  value
41
42
  end
42
43
 
44
+ def free
45
+ result :none
46
+ Object.free(@ptr)
47
+ Object.free(self)
48
+ end
49
+
43
50
  end
@@ -4,7 +4,6 @@
4
4
  #
5
5
  # 4 bytes object array class
6
6
  require_relative '../memory'
7
- require_relative '../kernel'
8
7
  require_relative '../object'
9
8
  require_relative '../string'
10
9
 
@@ -40,4 +39,10 @@ class Array64
40
39
  value
41
40
  end
42
41
 
42
+ def free
43
+ result :none
44
+ Object.free(@ptr)
45
+ Object.free(self)
46
+ end
47
+
43
48
  end
@@ -0,0 +1,70 @@
1
+ # Rubinius WebAssembly VM
2
+ # Copyright (c) 2019-2020, Laurent Julliard and contributors
3
+ # All rights reserved.
4
+ #
5
+ # IO class for all basic input and output operations
6
+
7
+ require_relative './wasi'
8
+
9
+ class IO
10
+ attr_accessor :fd
11
+
12
+ @@num_bytes_written = 0
13
+ @@num_bytes_read = 0
14
+
15
+ def initialize(fd)
16
+ @fd = fd
17
+ end
18
+
19
+ def write(stg)
20
+ arg stg: :String
21
+ ciovec = WASI::CIOVec.new(1)
22
+ ciovec << stg
23
+ errno = WASI.fd_write(@fd, ciovec.ciovs.ptr, 1, @@num_bytes_written.addr)
24
+ ciovec.free
25
+ errno
26
+ end
27
+
28
+ def print(stg)
29
+ self.write(stg)
30
+ end
31
+
32
+ def puts(stg)
33
+ arg stg: :String
34
+ result :none
35
+ ciovec = WASI::CIOVec.new(2)
36
+ ciovec << stg
37
+ ciovec << "\n"
38
+ errno = WASI.fd_write(@fd, ciovec.ciovs.ptr, 2, @@num_bytes_written.addr)
39
+ ciovec.free
40
+ end
41
+
42
+ def read
43
+ result :I32 #:String
44
+ iovec = WASI::IOVec.new(1)
45
+ errno = WASI.fd_read(@fd, iovec.iovs.ptr, 1, @@num_bytes_read.addr)
46
+ # -1 below because of \0 terminated string
47
+ String.new(iovec.iovs[0], @@num_bytes_read-1)
48
+ end
49
+
50
+ end
51
+
52
+ STDIN = IO.new
53
+ STDOUT = IO.new
54
+ STDERR = IO.new
55
+
56
+ module Kernel
57
+
58
+ def puts(stg)
59
+ arg stg: :String
60
+ result :none
61
+ STDOUT.puts(stg)
62
+ end
63
+
64
+ def print(stg)
65
+ arg stg: :String
66
+ result :none
67
+ STDOUT.print(stg)
68
+ end
69
+
70
+ end
@@ -3,15 +3,20 @@
3
3
  # All rights reserved.
4
4
  #
5
5
  # Kernel methods
6
+ #
7
+ # Most of the Kernel methods are defined in other files
8
+ # to avoid requiring classes that rely on Kernel methods
9
+ # themselves.
6
10
 
7
- $! = 0.cast_to(:String)
11
+ class String; end
8
12
 
9
13
  module Kernel
10
14
 
11
15
  def raise(msg)
12
16
  arg msg: :String
13
17
  result :none
14
- $! = msg
18
+ #$! = msg
19
+ #STDERR.puts msg
15
20
  inline wat: '(unreachable)', wtype: :none
16
21
  end
17
22
 
@@ -8,8 +8,8 @@
8
8
  # For a detailed explanation of the allocator code see
9
9
  # https://gnuchops.wordpress.com/2013/02/26/memory-allocator-for-embedded-system-k-r-ritchie-book/
10
10
 
11
- require 'rlang/lib/memory'
12
- require 'rlang/lib/unistd'
11
+ require_relative './memory'
12
+ require_relative './unistd'
13
13
 
14
14
  # minimum number of units to request
15
15
  $NALLOC = 1024