wardite 0.1.0 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13e64d51eb1befe294c1ffe57e53b85cbeac7015c989e9fa88d7e3528f55c6df
4
- data.tar.gz: ae6a11d049b3970b6f8f8f87a7fd07f1c69a367afe706296413797075c9b4c12
3
+ metadata.gz: a5f1ce687265ea33d718ed38618cdba69873acabbd241515ef941f7a226672d0
4
+ data.tar.gz: a8a2cef102636f2fdfb9dc530bd268a6e8eb754bc2da9af882acd0b60e4c47f8
5
5
  SHA512:
6
- metadata.gz: 8696f7f48858528a7b8d5ba524cdeada76ffde6fad9cd9009b8e1246ab51f0be5c6b0de511d4af29eef17a211d24602977fabe4bed31f63df925368317f664c6
7
- data.tar.gz: 8d4e5e7b2931c2e04176099ad27b6d8bb65937cdada6abaaf7827674f83800373461344c4d8c9971e8f43b65abcf785466c88034331026c96d2139b8258b85d0
6
+ metadata.gz: b0e1f8005b1b96f7f2f34d32b1ddf97dde71577791a91d5a3309c9c83bf46e25e0d41f25d5859a94d4fec8714f4b7a42700756f8d6fae87c7e32b5d97e265e87
7
+ data.tar.gz: c6e9f7333baf51b2a08f0944beae0095a430c32a151c317e17d0becccf337724b86a0f214697e203cfbfbc251ee974230a211cc130ff2ff8cf95eabf09a47de6
data/README.md CHANGED
@@ -1,28 +1,53 @@
1
1
  # Wardite
2
2
 
3
+ [![workflow](https://github.com/udzura/wardite/actions/workflows/main.yml/badge.svg)](https://github.com/udzura/wardite/actions) [![gem version](https://badge.fury.io/rb/wardite.svg)](https://rubygems.org/gems/wardite)
4
+
3
5
  A pure-ruby webassembly runtime.
4
6
 
5
- ## Installation
7
+ - [x] Fully typed by RBS (with the aid of [rbs-inline](https://github.com/soutaro/rbs-inline))
8
+ - [ ] WASI (p1) support
6
9
 
7
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+ ## Installation
8
11
 
9
12
  Install the gem and add to the application's Gemfile by executing:
10
13
 
11
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
14
+ $ bundle add wardite
12
15
 
13
16
  If bundler is not being used to manage dependencies, install the gem by executing:
14
17
 
15
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
18
+ $ gem install wardite
16
19
 
17
20
  ## Usage
18
21
 
19
- TODO: Write usage instructions here
22
+ - In your ruby code, instanciate Wardite runtime:
23
+
24
+ ```ruby
25
+ require "wardite"
26
+
27
+ path = ARGV[0]
28
+ method = ARGV[1]
29
+ args = ARGV[2..-1] || []
30
+
31
+ instance = Wardite::new(path: path);
32
+ if !method && instance.runtime.respond_to?(:_start)
33
+ instance.runtime._start
34
+ else
35
+ instance.runtime.call(method, args)
36
+ end
37
+ ```
38
+
39
+ - Wardite bundles `wardite` cli command:
40
+
41
+ ```console
42
+ $ wardite examples/test.wasm
43
+ #=> Test!
44
+ ```
20
45
 
21
46
  ## Development
22
47
 
23
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test-unit` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
48
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
49
 
25
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
50
+ Additionaly, you can run `bundle exec rake check` to generate rbs files from annotations and run `steep check`.
26
51
 
27
52
  ## Contributing
28
53
 
@@ -32,4 +57,8 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/udzura
32
57
 
33
58
  - https://github.com/technohippy/wasmrb?tab=readme-ov-file
34
59
  - Referencial implementation but no support with WASI
35
- - Wardite aims to support full WASI (previwe 1)
60
+ - Wardite aims to support full WASI (previwe 1)
61
+ - https://github.com/skanehira/chibiwasm
62
+ - Small and consise implementation in Rust
63
+ - Wardite was first built upon [its development tutorial](https://skanehira.github.io/writing-a-wasm-runtime-in-rust/). Thanks!
64
+ - Many of test wat files under [`examples/`](./examples/) are borrowed from chibiwasm project
data/examples/fib.wat ADDED
@@ -0,0 +1,14 @@
1
+ (module
2
+ (func $fib (export "fib") (param $n i32) (result i32)
3
+ (if
4
+ (i32.lt_s (local.get $n) (i32.const 2))
5
+ (then (return (i32.const 1)))
6
+ )
7
+ (return
8
+ (i32.add
9
+ (call $fib (i32.sub (local.get $n) (i32.const 2)))
10
+ (call $fib (i32.sub (local.get $n) (i32.const 1)))
11
+ )
12
+ )
13
+ )
14
+ )
data/examples/fib2.wat ADDED
@@ -0,0 +1,13 @@
1
+ (module
2
+ (func $fib (export "fib") (param i32) (result i32)
3
+ (if (result i32) (i32.le_u (local.get 0) (i32.const 1))
4
+ (then (i32.const 1))
5
+ (else
6
+ (i32.add
7
+ (call $fib (i32.sub (local.get 0) (i32.const 2)))
8
+ (call $fib (i32.sub (local.get 0) (i32.const 1)))
9
+ )
10
+ )
11
+ )
12
+ )
13
+ )
data/exe/wardite CHANGED
@@ -11,5 +11,6 @@ instance = Wardite::BinaryLoader::load_from_buffer(f);
11
11
  if !method && instance.runtime.respond_to?(:_start)
12
12
  instance.runtime._start
13
13
  else
14
- instance.runtime.call(method, args)
14
+ ret = instance.runtime.call(method, args)
15
+ $stderr.puts "return value: #{ret.inspect}"
15
16
  end
@@ -16,8 +16,12 @@ module Wardite
16
16
  # @rbs return: Symbol
17
17
  def self.to_sym(chr)
18
18
  case chr
19
+ when "\u0004"
20
+ :if
19
21
  when "\u000b"
20
22
  :end
23
+ when "\u000f"
24
+ :return
21
25
  when "\u0010"
22
26
  :call
23
27
  when "\u0020"
@@ -28,10 +32,16 @@ module Wardite
28
32
  :i32_store
29
33
  when "\u0041"
30
34
  :i32_const
35
+ when "\u0048"
36
+ :i32_lts
37
+ when "\u004d"
38
+ :i32_leu
31
39
  when "\u006a"
32
40
  :i32_add
41
+ when "\u006b"
42
+ :i32_sub
33
43
  else
34
- raise NotImplementedError, "unimplemented: #{chr.inspect}"
44
+ raise NotImplementedError, "unimplemented: #{"%04x" % chr.ord}"
35
45
  end
36
46
  end
37
47
 
@@ -45,6 +55,8 @@ module Wardite
45
55
  [:i32]
46
56
  when :i32_store
47
57
  [:u32, :u32]
58
+ when :if
59
+ [:u8_block]
48
60
  else
49
61
  []
50
62
  end
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Wardite
5
- VERSION = "0.1.0" #: String
5
+ VERSION = "0.1.1" #: String
6
6
  end
data/lib/wardite/wasi.rb CHANGED
@@ -34,6 +34,7 @@ module Wardite
34
34
  iovs += 4
35
35
  slen = unpack_le_int(memory.data[iovs...(iovs+4)])
36
36
  iovs += 4
37
+ # TODO: parallel write?
37
38
  nwritten += file.write(memory.data[start...(start+slen)])
38
39
  end
39
40
 
@@ -42,7 +43,6 @@ module Wardite
42
43
  0
43
44
  end
44
45
 
45
-
46
46
  # @rbs return: Hash[Symbol, Proc]
47
47
  def to_module
48
48
  {
data/lib/wardite.rb CHANGED
@@ -419,13 +419,23 @@ module Wardite
419
419
  while c = buf.read(1)
420
420
  code = Op.to_sym(c)
421
421
  operand_types = Op.operand_of(code)
422
- operand = []
422
+ operand = [] #: Array[Object]
423
423
  operand_types.each do |typ|
424
424
  case typ
425
425
  when :u32
426
426
  operand << fetch_uleb128(buf)
427
427
  when :i32
428
428
  operand << fetch_sleb128(buf)
429
+ when :u8_block # :if specific
430
+ block_ope = buf.read 1
431
+ if ! block_ope
432
+ raise "buffer too short for if"
433
+ end
434
+ if block_ope.ord == 0x40
435
+ operand << Block.void
436
+ else
437
+ operand << Block.new([block_ope.ord])
438
+ end
429
439
  else
430
440
  $stderr.puts "warning: unknown type #{typ.inspect}. defaulting to u32"
431
441
  operand << fetch_uleb128(buf)
@@ -820,6 +830,19 @@ module Wardite
820
830
  # @rbs return: void
821
831
  def eval_insn(frame, insn)
822
832
  case insn.code
833
+ when :if
834
+ block = insn.operand[0]
835
+ raise EvalError, "if op without block" if !block.is_a?(Block)
836
+ cond = stack.pop
837
+ raise EvalError, "cond not found" if !cond.is_a?(Integer)
838
+ next_pc = fetch_ops_while_end(frame.body, frame.pc)
839
+ if cond.zero?
840
+ frame.pc = next_pc
841
+ end
842
+
843
+ label = Label.new(:if, next_pc, stack.size, block.result_size)
844
+ frame.labels.push(label)
845
+
823
846
  when :local_get
824
847
  idx = insn.operand[0]
825
848
  if !idx.is_a?(Integer)
@@ -841,12 +864,32 @@ module Wardite
841
864
  end
842
865
  frame.locals[idx] = value
843
866
 
867
+ when :i32_lts
868
+ right, left = stack.pop, stack.pop
869
+ if !right.is_a?(Integer) || !left.is_a?(Integer)
870
+ raise EvalError, "maybe empty stack"
871
+ end
872
+ value = (left < right) ? 1 : 0
873
+ stack.push(value)
874
+ when :i32_leu
875
+ right, left = stack.pop, stack.pop
876
+ if !right.is_a?(Integer) || !left.is_a?(Integer)
877
+ raise EvalError, "maybe empty stack"
878
+ end
879
+ value = (left >= right) ? 1 : 0
880
+ stack.push(value)
844
881
  when :i32_add
845
882
  right, left = stack.pop, stack.pop
846
883
  if !right.is_a?(Integer) || !left.is_a?(Integer)
847
884
  raise EvalError, "maybe empty stack"
848
885
  end
849
886
  stack.push(left + right)
887
+ when :i32_sub
888
+ right, left = stack.pop, stack.pop
889
+ if !right.is_a?(Integer) || !left.is_a?(Integer)
890
+ raise EvalError, "maybe empty stack"
891
+ end
892
+ stack.push(left - right)
850
893
  when :i32_const
851
894
  const = insn.operand[0]
852
895
  if !const.is_a?(Integer)
@@ -883,26 +926,72 @@ module Wardite
883
926
  raise GenericError, "got a non-function pointer"
884
927
  end
885
928
 
886
- when :end
929
+ when :return
887
930
  old_frame = call_stack.pop
888
931
  if !old_frame
889
932
  raise EvalError, "maybe empty call stack"
890
933
  end
891
934
 
892
- # unwind the stacks
893
- if old_frame.arity > 0
894
- if old_frame.arity > 1
895
- raise ::NotImplementedError, "return artiy >= 2 not yet supported ;;"
935
+ stack_unwind(old_frame.sp, old_frame.arity)
936
+
937
+ when :end
938
+ if old_label = frame.labels.pop
939
+ frame.pc = old_label.pc
940
+ stack_unwind(old_label.sp, old_label.arity)
941
+ else
942
+ old_frame = call_stack.pop
943
+ if !old_frame
944
+ raise EvalError, "maybe empty call stack"
896
945
  end
897
- value = stack.pop
898
- if !value
899
- raise EvalError, "cannot obtain return value"
946
+ stack_unwind(old_frame.sp, old_frame.arity)
947
+ end
948
+ end
949
+ end
950
+
951
+ # @rbs ops: Array[Op]
952
+ # @rbs pc_start: Integer
953
+ # @rbs return: Integer
954
+ def fetch_ops_while_end(ops, pc_start)
955
+ cursor = pc_start
956
+ depth = 0
957
+ loop {
958
+ cursor += 1
959
+ inst = ops[cursor]
960
+ case inst&.code
961
+ when nil
962
+ raise EvalError, "end op not found"
963
+ when :i
964
+ depth += 1
965
+ when :end
966
+ if depth == 0
967
+ return cursor
968
+ else
969
+ depth -= 1
900
970
  end
901
- self.stack = drained_stack(old_frame.sp)
902
- stack.push value
903
971
  else
904
- self.stack = drained_stack(old_frame.sp)
972
+ # nop
973
+ end
974
+ }
975
+ raise "[BUG] unreachable"
976
+ end
977
+
978
+ # unwind the stack and put return value if exists
979
+ # @rbs sp: Integer
980
+ # @rbs arity: Integer
981
+ # @rbs return: void
982
+ def stack_unwind(sp, arity)
983
+ if arity > 0
984
+ if arity > 1
985
+ raise ::NotImplementedError, "return artiy >= 2 not yet supported ;;"
905
986
  end
987
+ value = stack.pop
988
+ if !value
989
+ raise EvalError, "cannot obtain return value"
990
+ end
991
+ self.stack = drained_stack(sp)
992
+ stack.push value
993
+ else
994
+ self.stack = drained_stack(sp)
906
995
  end
907
996
  end
908
997
 
@@ -943,6 +1032,8 @@ module Wardite
943
1032
 
944
1033
  attr_accessor :arity #: Integer
945
1034
 
1035
+ attr_accessor :labels #: Array[Label]
1036
+
946
1037
  attr_accessor :locals #: Array[Object]
947
1038
 
948
1039
  # @rbs pc: Integer
@@ -957,6 +1048,28 @@ module Wardite
957
1048
  @body = body
958
1049
  @arity = arity
959
1050
  @locals = locals
1051
+ @labels = []
1052
+ end
1053
+ end
1054
+
1055
+ class Label
1056
+ attr_accessor :kind #: (:if|:loop|:block)
1057
+
1058
+ attr_accessor :pc #: Integer
1059
+ attr_accessor :sp #: Integer
1060
+
1061
+ attr_accessor :arity #: Integer
1062
+
1063
+ # @rbs kind: (:if|:loop|:block)
1064
+ # @rbs pc: Integer
1065
+ # @rbs sp: Integer
1066
+ # @rbs arity: Integer
1067
+ # @rbs returb: void
1068
+ def initialize(kind, pc, sp, arity)
1069
+ @kind = kind
1070
+ @pc = pc
1071
+ @sp = sp
1072
+ @arity = arity
960
1073
  end
961
1074
  end
962
1075
 
@@ -1068,6 +1181,37 @@ module Wardite
1068
1181
  end
1069
1182
  end
1070
1183
 
1184
+ class Block
1185
+ VOID = nil #: nil
1186
+
1187
+ attr_accessor :block_types #: nil|Array[Integer]
1188
+
1189
+ # @rbs return: Block
1190
+ def self.void
1191
+ new(VOID)
1192
+ end
1193
+
1194
+ # @rbs block_types: nil|Array[Integer]
1195
+ # @rbs return: void
1196
+ def initialize(block_types=VOID)
1197
+ @block_types = block_types
1198
+ end
1199
+
1200
+ # @rbs return: bool
1201
+ def void?
1202
+ !!block_types
1203
+ end
1204
+
1205
+ # @rbs return: Integer
1206
+ def result_size
1207
+ if block_types # !void?
1208
+ block_types.size
1209
+ else
1210
+ 0
1211
+ end
1212
+ end
1213
+ end
1214
+
1071
1215
  class Exports
1072
1216
  attr_accessor :mappings #: Hash[String, [Integer, WasmFunction|ExternalFunction]]
1073
1217
 
@@ -1146,4 +1290,18 @@ module Wardite
1146
1290
  class LoadError < StandardError; end
1147
1291
  class ArgumentError < StandardError; end
1148
1292
  class EvalError < StandardError; end
1293
+
1294
+ # @rbs path: String|nil
1295
+ # @rbs buffer: File|StringIO|nil
1296
+ # @rbs **options: Hash[Symbol, Object]
1297
+ # @rbs return: Instance
1298
+ def self.new(path: nil, buffer: nil, **options)
1299
+ if path
1300
+ buffer = File.open(path)
1301
+ end
1302
+ if !buffer
1303
+ raise ::ArgumentError, "nil buffer passed"
1304
+ end
1305
+ Wardite::BinaryLoader::load_from_buffer(buffer, **options);
1306
+ end
1149
1307
  end
@@ -247,6 +247,17 @@ module Wardite
247
247
  # @rbs return: void
248
248
  def eval_insn: (Frame frame, Op insn) -> void
249
249
 
250
+ # @rbs ops: Array[Op]
251
+ # @rbs pc_start: Integer
252
+ # @rbs return: Integer
253
+ def fetch_ops_while_end: (Array[Op] ops, Integer pc_start) -> Integer
254
+
255
+ # unwind the stack and put return value if exists
256
+ # @rbs sp: Integer
257
+ # @rbs arity: Integer
258
+ # @rbs return: void
259
+ def stack_unwind: (Integer sp, Integer arity) -> void
260
+
250
261
  # @rbs finish: Integer
251
262
  # @rbs return: Array[Object]
252
263
  def drained_stack: (Integer finish) -> Array[Object]
@@ -270,6 +281,8 @@ module Wardite
270
281
 
271
282
  attr_accessor arity: Integer
272
283
 
284
+ attr_accessor labels: Array[Label]
285
+
273
286
  attr_accessor locals: Array[Object]
274
287
 
275
288
  # @rbs pc: Integer
@@ -281,6 +294,23 @@ module Wardite
281
294
  def initialize: (Integer pc, Integer sp, Array[Op] body, Integer arity, Array[Object] locals) -> untyped
282
295
  end
283
296
 
297
+ class Label
298
+ attr_accessor kind: :if | :loop | :block
299
+
300
+ attr_accessor pc: Integer
301
+
302
+ attr_accessor sp: Integer
303
+
304
+ attr_accessor arity: Integer
305
+
306
+ # @rbs kind: (:if|:loop|:block)
307
+ # @rbs pc: Integer
308
+ # @rbs sp: Integer
309
+ # @rbs arity: Integer
310
+ # @rbs returb: void
311
+ def initialize: (:if | :loop | :block kind, Integer pc, Integer sp, Integer arity) -> untyped
312
+ end
313
+
284
314
  class Store
285
315
  attr_accessor funcs: Array[WasmFunction | ExternalFunction]
286
316
 
@@ -319,6 +349,25 @@ module Wardite
319
349
  def initialize: () { (WasmData) -> void } -> void
320
350
  end
321
351
 
352
+ class Block
353
+ VOID: nil
354
+
355
+ attr_accessor block_types: nil | Array[Integer]
356
+
357
+ # @rbs return: Block
358
+ def self.void: () -> Block
359
+
360
+ # @rbs block_types: nil|Array[Integer]
361
+ # @rbs return: void
362
+ def initialize: (?nil | Array[Integer] block_types) -> void
363
+
364
+ # @rbs return: bool
365
+ def void?: () -> bool
366
+
367
+ # @rbs return: Integer
368
+ def result_size: () -> Integer
369
+ end
370
+
322
371
  class Exports
323
372
  attr_accessor mappings: Hash[String, [ Integer, WasmFunction | ExternalFunction ]]
324
373
 
@@ -381,4 +430,10 @@ module Wardite
381
430
 
382
431
  class EvalError < StandardError
383
432
  end
433
+
434
+ # @rbs path: String|nil
435
+ # @rbs buffer: File|StringIO|nil
436
+ # @rbs **options: Hash[Symbol, Object]
437
+ # @rbs return: Instance
438
+ def self.new: (?path: String | nil, ?buffer: File | StringIO | nil, **Hash[Symbol, Object] options) -> Instance
384
439
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wardite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uchio Kondo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-27 00:00:00.000000000 Z
11
+ date: 2024-10-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A pure-ruby webassembly runtime
14
14
  email:
@@ -25,6 +25,8 @@ files:
25
25
  - examples/add.wasm
26
26
  - examples/add.wat
27
27
  - examples/call.wat
28
+ - examples/fib.wat
29
+ - examples/fib2.wat
28
30
  - examples/helloworld.wat
29
31
  - examples/i32_const.wat
30
32
  - examples/i32_store.wat