rlang 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rake_tasks~ +0 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE +373 -0
- data/README.md +61 -0
- data/Rakefile +10 -0
- data/bin/rlang +164 -0
- data/docs/RlangCompiler.md +37 -0
- data/docs/RlangManual.md +391 -0
- data/lib/builder/ext/tempfile.rb +7 -0
- data/lib/builder/ext.rb +5 -0
- data/lib/builder/rlang/builder.rb +31 -0
- data/lib/builder/rlang.rb +2 -0
- data/lib/builder/wat/builder.rb +52 -0
- data/lib/builder/wat/renderer.rb +28 -0
- data/lib/builder/wat.rb +3 -0
- data/lib/builder.rb +7 -0
- data/lib/rlang/lib/malloc.c +97 -0
- data/lib/rlang/lib/malloc.rb +169 -0
- data/lib/rlang/lib/memory.rb +11 -0
- data/lib/rlang/lib/type/i32.rb +7 -0
- data/lib/rlang/lib/type/i64.rb +7 -0
- data/lib/rlang/lib/type.rb +6 -0
- data/lib/rlang/lib/unistd.rb +47 -0
- data/lib/rlang/lib.rb +10 -0
- data/lib/rlang/parser/const.rb +15 -0
- data/lib/rlang/parser/cvar.rb +44 -0
- data/lib/rlang/parser/data.rb +105 -0
- data/lib/rlang/parser/export.rb +22 -0
- data/lib/rlang/parser/ext/integer.rb +5 -0
- data/lib/rlang/parser/ext/string.rb +5 -0
- data/lib/rlang/parser/ext/type.rb +64 -0
- data/lib/rlang/parser/global.rb +65 -0
- data/lib/rlang/parser/lvar.rb +29 -0
- data/lib/rlang/parser/marg.rb +30 -0
- data/lib/rlang/parser/method.rb +76 -0
- data/lib/rlang/parser/wattr.rb +65 -0
- data/lib/rlang/parser/wgenerator.rb +509 -0
- data/lib/rlang/parser/winstruction.rb +148 -0
- data/lib/rlang/parser/wnode.rb +455 -0
- data/lib/rlang/parser/wtree.rb +19 -0
- data/lib/rlang/parser/wtype.rb +116 -0
- data/lib/rlang/parser.rb +1842 -0
- data/lib/rlang/version.rb +3 -0
- data/lib/rlang.rb +4 -0
- data/lib/simul/classes/data.rb +80 -0
- data/lib/simul/classes/global.rb +38 -0
- data/lib/simul/classes/memory.rb +131 -0
- data/lib/utils/log.rb +32 -0
- data/rlang.gemspec +38 -0
- metadata +158 -0
data/bin/rlang
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Rubinius WebAssembly VM
|
3
|
+
# Copyright (c) 2019-2020, Laurent Julliard and contributors
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Rlang compiler
|
7
|
+
# Rlang is a subset of the Ruby language that can be transpiled
|
8
|
+
# to WAT and then compiled to WASM. The Rubinius WASM virtual
|
9
|
+
# machine is written in Rlang.
|
10
|
+
#
|
11
|
+
# Compile a Rlang source file to WAT file
|
12
|
+
|
13
|
+
|
14
|
+
require 'optparse'
|
15
|
+
require 'fileutils'
|
16
|
+
require 'rlang'
|
17
|
+
|
18
|
+
include Log
|
19
|
+
logger.level = Logger::INFO
|
20
|
+
|
21
|
+
# WAT source frame
|
22
|
+
WAT_FRAME = %q{
|
23
|
+
;; Generated by Rlang compiler version %{version} on %{time}\n"
|
24
|
+
(module %{module}
|
25
|
+
(memory $0 %{memory_min} %{memory_max})
|
26
|
+
|
27
|
+
;; ======= EXPORTS =======
|
28
|
+
(export "memory" (memory $0))
|
29
|
+
%{exports}
|
30
|
+
|
31
|
+
;; ======= GLOBAL VARIABLES =======
|
32
|
+
%{globals}
|
33
|
+
|
34
|
+
;; ======= STATIC DATA =======
|
35
|
+
%{data}
|
36
|
+
|
37
|
+
;; ======= CODE =======
|
38
|
+
%{code}
|
39
|
+
)
|
40
|
+
}
|
41
|
+
|
42
|
+
options = {}
|
43
|
+
OptionParser.new do |opts|
|
44
|
+
opts.banner = %q{Usage: rlang [options] filename
|
45
|
+
read a Rlang file, check it for errors, and convert it
|
46
|
+
to either Ruby AST, WAT source code or WASM bytecode
|
47
|
+
|
48
|
+
examples:
|
49
|
+
# Parse Rlang file and display the Ruby abstract syntax tree
|
50
|
+
rlang --ast test.rb
|
51
|
+
|
52
|
+
# Parse Rlang file and generate WAT code in test.wat file
|
53
|
+
rlang --wat -o /tmp/test.wat test.rb
|
54
|
+
|
55
|
+
# Parse Rlang file and generate WASM bytecodein test.wasm file
|
56
|
+
rlang --wasm -o /tmp/test.wasm test.rb
|
57
|
+
|
58
|
+
options:
|
59
|
+
}
|
60
|
+
|
61
|
+
opts.on("-I DIR", "--load_path DIRECTORY", "specify $LOAD_PATH directory (may be used more than once)") do |dir|
|
62
|
+
options[:load_path] ||= []
|
63
|
+
options[:load_path] << dir
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.on("-M", "--module NAME", "WASM module name") do |name|
|
67
|
+
options[:module] = name
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on("-x", "--export-all", "Export all Web Assembly functions") do |v|
|
71
|
+
options[:export_all] = true
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.on("-m", "--memory MIN[,MAX]", "WASM Memory size allocated in pages (MIN default is 4)") do |sizes|
|
75
|
+
options[:memory_min], options[:memory_max] = sizes.split(',')
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on("-w", "--wat", "Generate WAT source file") do |v|
|
79
|
+
options[:wat] = v
|
80
|
+
end
|
81
|
+
|
82
|
+
opts.on("-a", "--ast", "Generate Ruby AST file") do |v|
|
83
|
+
options[:ast] = v
|
84
|
+
end
|
85
|
+
|
86
|
+
opts.on("-s", "--wasm", "Generate WASM bytecode file") do |v|
|
87
|
+
options[:wasm] = v
|
88
|
+
end
|
89
|
+
|
90
|
+
opts.on("-o", "--output FILE", "Write output to file") do |file|
|
91
|
+
options[:output] = file
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on("-v", "--verbose [LEVEL]", "Verbosity level (fatal, error, warn, info, debug)") do |level|
|
95
|
+
options[:level] = level || 'INFO'
|
96
|
+
logger.level = Kernel.const_get("Logger::#{options[:level].upcase}")
|
97
|
+
end
|
98
|
+
|
99
|
+
opts.on("-V", "--version", "Displays Rlang version") do |v|
|
100
|
+
puts Rlang::VERSION
|
101
|
+
exit
|
102
|
+
end
|
103
|
+
|
104
|
+
opts.on("-h", "--help", "Prints this help") do
|
105
|
+
puts opts
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
end.parse!(ARGV)
|
109
|
+
|
110
|
+
options[:module] ||= ''
|
111
|
+
options[:memory_min] ||= 4
|
112
|
+
options[:load_path] ||= []
|
113
|
+
|
114
|
+
fh_out = options[:output] ? File.open(options[:output], 'w') : STDOUT
|
115
|
+
|
116
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
117
|
+
loc = caller_locations[3] # skip over the logger call itself
|
118
|
+
"#{severity[0]}: #{File.basename(loc.path)}:#{loc.lineno}##{loc.label} > #{msg}\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
if options[:ast]
|
122
|
+
fh_in = ARGV.empty? ? STDIN : File.open(ARGV[0])
|
123
|
+
fh_out.write(Parser::CurrentRuby.parse(File.read(fh_in)))
|
124
|
+
end
|
125
|
+
|
126
|
+
if options[:wat] || options[:wasm]
|
127
|
+
parser = Rlang::Parser::Parser.new(nil)
|
128
|
+
parser.config[:LOAD_PATH] = options[:load_path]
|
129
|
+
parser.config[:__FILE__] = File.expand_path(ARGV[0])
|
130
|
+
parser.config[:export_all] = options[:export_all]
|
131
|
+
wg = Rlang::Parser::WGenerator.new(parser)
|
132
|
+
parser.wgenerator = wg
|
133
|
+
parser.parse_file(File.expand_path(ARGV[0]))
|
134
|
+
|
135
|
+
# Write generated WAT code in a temp file
|
136
|
+
# Do not delete temp file when closing
|
137
|
+
tf = Tempfile.new([File.basename(ARGV[0]), '.wat'])
|
138
|
+
tf.persist!
|
139
|
+
tf << WAT_FRAME % {version: Rlang::VERSION,
|
140
|
+
time: Time.now,
|
141
|
+
module: options[:module],
|
142
|
+
memory_min: options[:memory_min],
|
143
|
+
memory_max: options[:memory_max],
|
144
|
+
exports: Rlang::Parser::Export.transpile,
|
145
|
+
globals: Rlang::Parser::Global.transpile,
|
146
|
+
data: Rlang::Parser::DAta.transpile,
|
147
|
+
code: wg.root.transpile
|
148
|
+
}
|
149
|
+
tf.close
|
150
|
+
|
151
|
+
if options[:wasm]
|
152
|
+
builder = Builder::Wat::Builder.new(tf.path, options[:output], options[:load_path])
|
153
|
+
builder.compile
|
154
|
+
elsif options[:wat]
|
155
|
+
if options[:output]
|
156
|
+
FileUtils.mv(tf.path, options[:output])
|
157
|
+
else
|
158
|
+
STDOUT.write(File.read(tf.path))
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
exit 0
|
164
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# The Rlang compiler
|
2
|
+
|
3
|
+
The Rlang compiler can be invoked with the `rlang` command. It takes a Rlang source file as an argument and can generate three types of output:
|
4
|
+
* Ruby AST: the `--ast` option generates an abstract syntax tree of your Rlang code
|
5
|
+
* WAT code: the `--wat` option turns your Rlang code into WebAssembly source code
|
6
|
+
* WASM bytecode: the `--wasm` first compile your Rlang code to WAT code and then turns it into WebAssembly bytecode that can be executed from within a WebAssembly runtime of your choice.
|
7
|
+
|
8
|
+
Make sure that the [WABT toolkit](https://github.com/WebAssembly/wabt) is installed before using the `--wasm` option.
|
9
|
+
|
10
|
+
### Rlang compiler options
|
11
|
+
Here is the output of `rlang --help` command:
|
12
|
+
|
13
|
+
```
|
14
|
+
Usage: rlang [options] rlang_file.rb
|
15
|
+
-I, --load_path DIRECTORY specify $LOAD_PATH directory (may be used more than once)
|
16
|
+
-x, --export-all Export all Web Assembly functions and globals
|
17
|
+
-M, --module NAME WASM module name
|
18
|
+
-m, --memory MIN[,MAX] WASM Memory size allocated in pages (MIN default is 4)
|
19
|
+
-w, --wat Generate WAT source file
|
20
|
+
-a, --ast Generate Ruby AST file
|
21
|
+
-s, --wasm Generate WASM bytecode file
|
22
|
+
-o, --output FILE Write output to file
|
23
|
+
-v, --verbose [LEVEL] Verbosity level (fatal, error, warn, info, debug)
|
24
|
+
-V, --version Displays Rlang version
|
25
|
+
-h, --help Prints this help
|
26
|
+
```
|
27
|
+
* **-I, --load_path DIRECTORY**: this option can be used several time to append several directories to the Rlang path. Please note that the Rlang load path doesn't inherit from the regular Ruby load path
|
28
|
+
* **-x, --export-all**: systematically export all Web Assembly functions. This option is useful for test and debug purposes as it allows you to call any functions from outside the Web Assembly runtime
|
29
|
+
* **-M, --module**: allows to specify a name for the generated WebAssembly module. By default it doesn't have any.
|
30
|
+
* **-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
|
+
* **-w, --wat**: parse Rlang file and generate WAT source code
|
32
|
+
* **-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
|
+
* **-o, --output FILE**: send rlang output to FILE
|
35
|
+
* **-v, --verbose [LEVEL]**: verbosity level (fatal, error, warn, info, debug). Default is warn
|
36
|
+
* **-V, --version**: Displays Rlang version
|
37
|
+
* **-h, --help**: Prints help message
|
data/docs/RlangManual.md
ADDED
@@ -0,0 +1,391 @@
|
|
1
|
+
# The Rlang language
|
2
|
+
|
3
|
+
Rlang is a subset of the Ruby language that is meant to provide a certain level of abstration and expressiveness while keeping its translation to WebAssembly straightforward.
|
4
|
+
|
5
|
+
Ruby programmers will feel at home with Rlang and non Ruby programmers will find it useful to generate efficient WebAssembly code from a language that is much easier to use.
|
6
|
+
|
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
|
+
|
9
|
+
## The Rlang object model
|
10
|
+
|
11
|
+
In Rlang you can define classes and those classes can be instantiated but *only* statically not dynamically. Supporting dynamic object allocation would more or less mean that Rlang becomes a Ruby virtual machine and this is not the intent. What the intent is with Rlang is to provide you with a language that can assist you in developing such a Virtual Machine for instance ;-)
|
12
|
+
|
13
|
+
One of the consequence of this for instance is that you can statically instantiate a new object in the body of a class not in a method. In other words objects can be instantiated at compile time not at runtime (note: this may change in a future version)
|
14
|
+
|
15
|
+
## What Rlang does
|
16
|
+
|
17
|
+
Rlang provides:
|
18
|
+
* Classes, class attributes and class variables
|
19
|
+
* Object instantiation (only at compile time)
|
20
|
+
* Method definition and method calls
|
21
|
+
* Integers and booleans
|
22
|
+
* Constants
|
23
|
+
* Global variables
|
24
|
+
* Control constructs (if, while, until, break, next,...)
|
25
|
+
* Arithmetic, relational and logical operators
|
26
|
+
* WebAssembly source code (WAT) inlining
|
27
|
+
* Requiring other Rlang or WAT files
|
28
|
+
* A Rlang library (written in Rlang of course) that you can reuse in your own WebAssembly module
|
29
|
+
|
30
|
+
Here is a sample of Rlang code to whet your appetite:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class Math
|
34
|
+
def self.fib(n)
|
35
|
+
if n <= 1
|
36
|
+
f = n
|
37
|
+
else
|
38
|
+
f = self.fib(n-1) + self.fib(n-2)
|
39
|
+
end
|
40
|
+
return f
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
calling that method later in your code is as simple as invoking `Math.fib(20)`
|
46
|
+
|
47
|
+
## Classes
|
48
|
+
Classes are core constructs of Rlang and are very similar to Ruby classes.
|
49
|
+
|
50
|
+
Within a class you define methods and class variables. Actually all methods must be defined within a class. In other words you can not define a method at the top level of your Rlang code (not a very good practice anyway, even in plain Ruby). Also there is no inheritance mechanism in Rlang in the current version.
|
51
|
+
|
52
|
+
Here is an example of a class definition and the initialization and use of a class variable written in Rlang:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class MyClass
|
56
|
+
@@cvar = 100
|
57
|
+
|
58
|
+
def self.take_one
|
59
|
+
self.refill if @@cvar == 0
|
60
|
+
@@cvar -= 1
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.refill
|
64
|
+
@@cvar = 100
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
This short piece of code shows several interesting points:
|
70
|
+
1. A class variable can be statically initialized at the class level. Concretely this means that at compile time the memory location corresponding to the `@@cvar` class variable initially receives the value 100.
|
71
|
+
1. Methods in this example are class methods, hence the use of `self.take_one` and `self.refill` in method definitions but instance methods are also supported (more on this later)
|
72
|
+
1. In `MyClass::take_one` you can see that Rlang also supports convenient syntactic sugar like `if` as a modifier or combined operation and assignment as in Ruby (here the `-=` operator)
|
73
|
+
|
74
|
+
### Class attributes
|
75
|
+
Since objects cannot be instantiated at runtime in Rlang, there is no such thing as instance variable. Rlang uses a special directive `wattr` to do 4 things at once:
|
76
|
+
1. Define the attributes of a class (see that as its instance variables somehow)
|
77
|
+
2. Define the type of the attributes (remember, Rlang is a compiler...)
|
78
|
+
2. Define the corresponding accessors both getter and setter (like `attr_accessor` does in Ruby)
|
79
|
+
3. Compute the memory footprint needed when objects of this class are instantiated.
|
80
|
+
|
81
|
+
That's a lot with a single statement and it makes your code easy to read. Here is an example
|
82
|
+
```ruby
|
83
|
+
class Square
|
84
|
+
wattr :side
|
85
|
+
|
86
|
+
def area
|
87
|
+
self.side * self.side
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
Later in your code you could use this class in your code as follows
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
class Test
|
95
|
+
@@square = Square.new
|
96
|
+
|
97
|
+
def self.my_method
|
98
|
+
@@square.side = 10
|
99
|
+
@@square.area
|
100
|
+
end
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
The code is pretty straightforward: a new square instance is created, its side is set to 10 and as you would expect the call to the Square#area method would return 100.
|
105
|
+
|
106
|
+
### Class attribute type
|
107
|
+
In the example above the `side` attribute is implicitely using the `:I32` (long integer) WebAssembly type. It's the default Rlang type. Assuming you want to manage big squares, you'd have to use `:I64` (double integer) like this for the `side` attribute and also instruct Rlang that the return value of area is also `:I64` (more on this later).
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class Square
|
111
|
+
wattr :side
|
112
|
+
wattr_type side: :I64
|
113
|
+
|
114
|
+
def area
|
115
|
+
result :I64
|
116
|
+
self.side * self.side
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
## Object instantiation
|
122
|
+
In the current version of Rlang objects can only be instantiated at compile time not at runtime. As a result of this, all object instantiation must happen in the body of a class not in the body of a method. You have already seen an example of such an object instantiation in the previous example with `Square.new` being instantiated and stored in the class variable `@@cvar`.
|
123
|
+
|
124
|
+
Similarly you can also instantiate and store an object in a global variable.
|
125
|
+
|
126
|
+
## Methods
|
127
|
+
Methods in Rlang are defined as you would normally do in Ruby by using. They can be either class or instance methods.
|
128
|
+
|
129
|
+
### Method arguments
|
130
|
+
Rlang method definition supports fixed name arguments in any number. The *args and **args notation are not supported.
|
131
|
+
|
132
|
+
By default all arguments in Rlang are considered as being type `:I32` (a 32 bit integer). See the Type section below for more details. If your argument is of a different type you **must** explicitely state it.
|
133
|
+
```ruby
|
134
|
+
def self.m_two_args(arg1, arg2, arg3)
|
135
|
+
arg arg1: :Square, arg2: :I64
|
136
|
+
# your code here...
|
137
|
+
end
|
138
|
+
```
|
139
|
+
In the example above arg1 is of type Square (the class we defined earlier), arg2 is of type `:I64` and arg3 not being mention in the arg list of of default type (`:I32`)
|
140
|
+
|
141
|
+
### Return result
|
142
|
+
Unless otherwise stated, a method must return a value of type `:I32` (the default type in Rlang). If your method returns nothing or a value of a different type you have to say so with the `result` directive.
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
def self.m_no_return_value(arg1, arg2)
|
146
|
+
result :none
|
147
|
+
# your code here
|
148
|
+
# ...
|
149
|
+
# and return nothing
|
150
|
+
return
|
151
|
+
end
|
152
|
+
```
|
153
|
+
Similarly you can use `return :I64` if your method is to return a double integer value or `return :Square` if you method returns an object.
|
154
|
+
|
155
|
+
With a few exceptions (see the Conditional and Iteration Structures sections below), each Rlang statements evaluate to a value. In the absence of an explicit `return some_expression` statement, a method returns the value of the last evaluated statement. In the example above the method `MyClass::take_one` returns the value of `@@cvar` after decreasing it by one and `MyClass::refill` returns 100.
|
156
|
+
|
157
|
+
Rlang also gives you the ability to declare the return type of a method like this
|
158
|
+
```ruby
|
159
|
+
result :class_name, :method_name, :wasm_type
|
160
|
+
```
|
161
|
+
|
162
|
+
This result directive must be used to instruct the compiler about the return type of a method if it has not seen it yet (e.g. the method definition is coming later in your source code). But keep in mind that this is only needed if the method returns something different than the default type (`:I32`).
|
163
|
+
|
164
|
+
If `:method_name` symbol starts with a `#` it refers to an instance method. Without it it refers to a class method.
|
165
|
+
|
166
|
+
For an example see the [test_def_result_type_declaration.rb](https://github.com/ljulliar/rlang/blob/master/test/rlang_test_files/test_def_result_type_declaration.rb), a Rlang file that is part of the Rlang test suite.
|
167
|
+
|
168
|
+
### Local variables
|
169
|
+
Local variable used in a method body doesn't have to be declared. They are auto-vivified the first time you assign a value to it. In some cases though, you may have to use the `local` directive as in the example below to explicitely state the type of a local variable.
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
def self.m_local_var(arg1)
|
173
|
+
local lvar: :I64, mysquare: :Square
|
174
|
+
lvar = 10
|
175
|
+
mysquare = @@square
|
176
|
+
# ....
|
177
|
+
end
|
178
|
+
```
|
179
|
+
In this example, the `local` directive instructs the compiler that `lvar` is of type `:I64` and the local variable mysquare is of type `Square`.
|
180
|
+
|
181
|
+
### Exporting a method
|
182
|
+
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.
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
class MyClass
|
186
|
+
|
187
|
+
export
|
188
|
+
def self.visible(arg1)
|
189
|
+
# ...
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.not_visible
|
193
|
+
# ...
|
194
|
+
end
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
198
|
+
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
|
199
|
+
|
200
|
+
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)
|
201
|
+
|
202
|
+
## Rlang types
|
203
|
+
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).
|
204
|
+
|
205
|
+
### Implicit type cast
|
206
|
+
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.
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
def self.m_local_var(arg1)
|
210
|
+
arg :arg1, :I64
|
211
|
+
lvar = arg1 * 100
|
212
|
+
# ....
|
213
|
+
end
|
214
|
+
```
|
215
|
+
|
216
|
+
Conversely in the method below the first statement `lvar = 10` auto-vivifies `lvar` as a variable of type `:I32` (the default Rlang type). On the next line, Rlang evaluates `arg1 * 100` as an `:I64` result because `arg1` is declared as being of type `:I64`. Similarly as the type of `lvar` local variable was auto-vivified as `:I32`, the result of the expression `arg1 * 100` will be type cast from `:I64` to `:I32`. Note that Such a type cast may of course result in the value being truncated and the Rlang compiler will emit a warning accordingly.
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
def self.m_local_var(arg1)
|
220
|
+
arg :arg1, :I64
|
221
|
+
lvar = 10 # lvar is auto-vivified as :I32
|
222
|
+
lvar = arg1 * 100
|
223
|
+
# ....
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
### Explicit type cast
|
228
|
+
If Rlang is not capable of guessing the proper type of an expression or variable, you can explicitely cast it to any known type. Look at this example:
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
class MyClass
|
232
|
+
@@cvar = 100.cast_to(:I64)
|
233
|
+
@@square = 123876.cast_to(:Square)
|
234
|
+
# your code here
|
235
|
+
#...
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
The first line will auto-vivify the `@@cvar` class variable as type `:I64`.
|
240
|
+
|
241
|
+
The second example turns the value `123876` into a pointer to a `Square` object. In the absence of dynamic object instantiation this allows you to create your own object at runtime by allocating WebAssembly memory and pointing to it as if it was an object of you choice (see Rlang library below for memory management)
|
242
|
+
|
243
|
+
For `:I32` and `:I64` type cast you can also use the following shortcuts `100.to_I64` or `100.to_I32`
|
244
|
+
|
245
|
+
Note that type cast can be used anywhere in the code whether in class body or method definition.
|
246
|
+
|
247
|
+
## Constants
|
248
|
+
Rlang supports constants too and you can invoke constants from different classes as you would in Ruby. In the example below the `TestB::m_constants` returns 1001 as a result
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
class TestA
|
252
|
+
CONST = 1000
|
253
|
+
# your code here
|
254
|
+
end
|
255
|
+
|
256
|
+
class TestB
|
257
|
+
CONST = 1
|
258
|
+
|
259
|
+
export
|
260
|
+
def self.m_constants
|
261
|
+
CONST + TestA::CONST
|
262
|
+
end
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
## Booleans
|
267
|
+
Rlang the booleans `true` and `false`. You can use them in conditional statements like in the example below. As expected the `x` local variable equals would equal 10 when breaking from the loop
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
x = 0
|
271
|
+
while true
|
272
|
+
x += 1
|
273
|
+
break if x == 10
|
274
|
+
end
|
275
|
+
```
|
276
|
+
As opposed to Ruby though, `true` and `false`are not instances of TrueClass and FalseClass but are internally represented respectively as `1` and `0`. More generally in Rlang (pretty much like in C) any non zero integer value will be considered true. However we strongly advise against mixing boolean condition and integer arithmetics. Like in C this can lead to **very** nasty bugs that will be hard to debunk.
|
277
|
+
|
278
|
+
## Global variables
|
279
|
+
Rlang provides global variable as well. Whereas a constant can only be defined within the scope of a class definition, a global variable can be defined anywhere. When defined at the top level one can only assign a literal value like 100 (or 100.to_I64). Assigning an expression can only be done within the scope of a method.
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
$MYGLOBAL = 100
|
283
|
+
|
284
|
+
class TestB
|
285
|
+
CONST = 1
|
286
|
+
|
287
|
+
export
|
288
|
+
def self.m_constants
|
289
|
+
CONST + $MYGLOBAL
|
290
|
+
end
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
## Conditional statements
|
295
|
+
Rlang supports the following Ruby conditional statements:
|
296
|
+
* if/unless-end
|
297
|
+
* if/unless-else-end
|
298
|
+
* if-elsif-....- elsif-end
|
299
|
+
* as well as if and unless use as modifiers
|
300
|
+
|
301
|
+
**IMPORTANT REMARK** As opposed to Ruby, Rlang conditional statements never evaluates to a value. This is not much of a problem as even in regular Ruby code this feature is rarely used. However you might want to pay attention to some corner cases like in the fibonacci method shown at the beginning of this document. In regular Ruby you would use the following code:
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
def self.fib(n)
|
305
|
+
if n <= 1
|
306
|
+
n
|
307
|
+
else
|
308
|
+
fib(n-1) + fib(n-2)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
```
|
312
|
+
|
313
|
+
but as the if and else clauses doesn't return any value in Rlang you must collect the value in a local variable and return that local variable with an explicit return statement.
|
314
|
+
|
315
|
+
## Iteration statements
|
316
|
+
Rlang supports the following Ruby iteration statements:
|
317
|
+
* while do-end
|
318
|
+
* until do-end
|
319
|
+
|
320
|
+
`break` and `next` statements are also supported
|
321
|
+
|
322
|
+
**IMPORTANT REMARK** As opposed to Ruby, Rlang iteration statements never evaluates to a value.
|
323
|
+
|
324
|
+
## Operators
|
325
|
+
|
326
|
+
Here is the list of operators supported by Rlang:
|
327
|
+
|
328
|
+
* Arithmetic operators: `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `>>`, `<<`
|
329
|
+
* Relational operators: `==`, `!=`, `<`, `>`, `<=`, `>=`
|
330
|
+
* Logical operators: `&&`, `||`, `!`
|
331
|
+
|
332
|
+
### Arithmetics operators
|
333
|
+
Arithmetics operators does the same as in plain Ruby. They apply to `:I32` and `:I64` types. They will apply equally to `:F32` and `:F64` when supported by Rlang
|
334
|
+
|
335
|
+
### Relational operators
|
336
|
+
Relational operators does the same as in plain Ruby. They apply to `:I32` and `:I64` types. They will apply equally to `:F32` and `:F64` when supported by Rlang
|
337
|
+
|
338
|
+
All relational operators evaluate to a boolean value (see above) either `true` (value 1) or `false` (value 0)
|
339
|
+
|
340
|
+
### Logical operators
|
341
|
+
Logical (aka Boolean) operators `&&` (logical AND), `||` (logical OR) and `!` (logical NOT) acts as in plain Ruby.
|
342
|
+
|
343
|
+
It's a Rlang best practice to apply logical operators to boolean values only (e.g. `true`, `false` or boolean values resulting from comparisons). However in Rlang all non zero value is equivalent to true so, like in C, you can mix and match both booleans and integer values although it is not recommended as it typically leads to very nasty bugs that are hard to spot.
|
344
|
+
|
345
|
+
### Pointer Arithmetics
|
346
|
+
With the ability to define classes with attributes and instantiate objects from those classes, comes the notion of pointer arithmetics. When a new object is instantiated in Ruby, it is assigned a unique object ID. Similaly in Rlang the statement `@@cvar = Square.new`, will instatiate a new object, allocate the space needed in the WebAssembly memory, return the address of this memory space and, in this particular case, store the address i a class variable. In other words, `@@cvar` is a pointer to the new object.
|
347
|
+
|
348
|
+
With this we can start using pointer arithmetics like you would do in C. Supported operators are `+`, `-` as well as all relational operators `==`, `!=`, `<`, `>`, `<=`, `>=`. As an example the statement `@@cvar += 1` would result in @@cvar pointing to a memory address increased by the size of the Square object (here 8 bytes as Square has one `I64` attribute)
|
349
|
+
|
350
|
+
You can see examples of pointer arithmetics are work in the memory allocator class (Malloc) provide in the Rlang library.
|
351
|
+
|
352
|
+
## Requiring files
|
353
|
+
`require` and `require_relative` are supported by Rlang. It means that, like in plain Ruby, you can split your Rlang classes in distinct files, require them in a master file and compile this single master file.
|
354
|
+
|
355
|
+
`require` looks for file in the Rlang load path that you can define by using the `-I` command line option of the rlang compiler (See the [Rlang Compiler Documentation](https://github.com/ljulliar/rlang/blob/master/docs/RlangCompiler.md). However for your Rlang projects, we strongly suggest using `require_relative` rather than `require` as all your Rlang files most likely form a single entity that you'll end up compiling into a single WebAssembly module.
|
356
|
+
|
357
|
+
If no extension is specified for the required file, Rlang will start looking for a matching `.wat` file first (which means you can include hand written WAT files in your Rlang projects) and second for a matching `.rb` file.
|
358
|
+
|
359
|
+
## Code inlining
|
360
|
+
There are two ways to use WAT code directly in your Rlang projects:
|
361
|
+
* The first one is to require a `.wat` file (see previous section)
|
362
|
+
* The second is to use the `inline` directive directly in your code.
|
363
|
+
|
364
|
+
Here is an example:
|
365
|
+
```ruby
|
366
|
+
class MyOtherClass
|
367
|
+
def self.x10_square(arg1)
|
368
|
+
arg1 *= 10
|
369
|
+
inline wat: '(i32.mul
|
370
|
+
(local.get $arg1)
|
371
|
+
(local.get $arg1))',
|
372
|
+
ruby: 'arg1 ** 2'
|
373
|
+
end
|
374
|
+
end
|
375
|
+
```
|
376
|
+
What this code sample does is to multiply the method argument by 10 (in Ruby) and then inline some WAT code that squares this argument. The reason for the `ruby:` keyword argument is to give the equivalent Ruby code that will be used when you run your Rlang code in the Rlang simulator (still in development).
|
377
|
+
|
378
|
+
A third keyword argument `wtype:` also allows to specifiy the WebAssembly type produced by the fragment of inlined WAT code. By default it is assumed to produce an `:I32`. If not you can either specify `wtype: :I64` or `wtype: :none`
|
379
|
+
|
380
|
+
## The Rlang library
|
381
|
+
Rlang comes with a library that provides a number of pre-defined classes and methods (written in Rlang of course) that you can use by adding the following statement in your Rlang files
|
382
|
+
|
383
|
+
```ruby
|
384
|
+
require 'rlang/lib'
|
385
|
+
```
|
386
|
+
|
387
|
+
For now, the Rlang library is very modest and only contains basic WebAssembly memory management functions like `Memory::size` and `Memory::grow` to mirror the WebAssembly functions of the same name.
|
388
|
+
|
389
|
+
A basic dynamic memory allocator is also provided. It is shamelessly adapted from example provided in the famous Kernigan & Richie C book. Take a look at the [C version](https://github.com/ljulliar/rlang/blob/master/lib/rlang/lib/malloc.c) and see how easy it is to adapt [Malloc in Rlang](https://github.com/ljulliar/rlang/blob/master/lib/rlang/lib/malloc.rb).
|
390
|
+
|
391
|
+
That's it! Enjoy Rlang and, as always, feedback and contributions are welcome.
|
data/lib/builder/ext.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Rubinius WebAssembly VM
|
2
|
+
# Copyright (c) 2019, Laurent Julliard and contributors
|
3
|
+
# All rights reserved.
|
4
|
+
|
5
|
+
require_relative '../ext/tempfile'
|
6
|
+
|
7
|
+
module Builder::Rlang
|
8
|
+
class Builder
|
9
|
+
|
10
|
+
LIB_DIR = File.expand_path('../../../../lib', __FILE__)
|
11
|
+
RLANG = File.expand_path('../../../../bin/rlang', __FILE__)
|
12
|
+
|
13
|
+
attr_reader :source, :target, :wat_path
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@wat_path = nil
|
17
|
+
@target = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile(source, target, options='')
|
21
|
+
@source = source # Path to Rlang file
|
22
|
+
@target = target
|
23
|
+
@options = options
|
24
|
+
system("ruby -I#{LIB_DIR} -- #{RLANG} #{@options} --wasm -o #{target} #{@source}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def cleanup
|
28
|
+
File.unlink(@target) if @target
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|