wardite 0.2.1 → 0.3.0

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.
@@ -0,0 +1,800 @@
1
+ # rbs_inline: enabled
2
+ module Wardite
3
+ class Section
4
+ attr_accessor :name #: String
5
+
6
+ attr_accessor :code #: Integer
7
+
8
+ attr_accessor :size #: Integer
9
+ end
10
+
11
+ class TypeSection < Section
12
+ attr_accessor :defined_types #: Array[Array[Symbol]]
13
+
14
+ attr_accessor :defined_results #: Array[Array[Symbol]]
15
+
16
+ # @rbs return: void
17
+ def initialize
18
+ self.name = "Type"
19
+ self.code = 0x1
20
+
21
+ @defined_types = []
22
+ @defined_results = []
23
+ end
24
+ end
25
+
26
+ class FunctionSection < Section
27
+ attr_accessor :func_indices #: Array[Integer]
28
+
29
+ # @rbs return: void
30
+ def initialize
31
+ self.name = "Function"
32
+ self.code = 0x3
33
+
34
+ @func_indices = []
35
+ end
36
+ end
37
+
38
+ class TableSection < Section
39
+ attr_accessor :table_types #: Array[Symbol]
40
+
41
+ attr_accessor :table_limits #: Array[[Integer, Integer?]]
42
+
43
+ # @rbs return: void
44
+ def initialize
45
+ self.name = "Table"
46
+ self.code = 0x4
47
+
48
+ @table_types = []
49
+ @table_limits = []
50
+ end
51
+ end
52
+
53
+ class MemorySection < Section
54
+ attr_accessor :limits #: Array[[Integer, Integer?]]
55
+
56
+ # @rbs return: void
57
+ def initialize
58
+ self.name = "Memory"
59
+ self.code = 0x5
60
+
61
+ @limits = []
62
+ end
63
+ end
64
+
65
+ class GlobalSection < Section
66
+ class Global
67
+ attr_accessor :type #: Symbol
68
+
69
+ attr_accessor :mutable #: bool
70
+
71
+ # TODO: unused in wasm 1.0 spec?
72
+ attr_accessor :shared #: bool
73
+
74
+ attr_accessor :value #: wasmValue
75
+
76
+ # @rbs &blk: (Global) -> void
77
+ # @rbs return: void
78
+ def initialize(&blk)
79
+ blk.call(self)
80
+ end
81
+ end
82
+
83
+ attr_accessor :globals #: Array[Global]
84
+
85
+ # @rbs return: void
86
+ def initialize
87
+ self.name = "Data"
88
+ self.code = 0x6
89
+
90
+ @globals = []
91
+ end
92
+ end
93
+
94
+ class StartSection < Section
95
+ attr_accessor :func_index #: Integer
96
+
97
+ # @rbs return: void
98
+ def initialize
99
+ self.name = "Start"
100
+ self.code = 0x8
101
+ self.func_index = -1
102
+ end
103
+ end
104
+
105
+ class ElemSection < Section
106
+ attr_accessor :table_indices #: Array[Integer]
107
+
108
+ attr_accessor :table_offsets #: Array[Integer]
109
+
110
+ attr_accessor :element_indices #: Array[Array[Integer]]
111
+
112
+ # @rbs return: void
113
+ def initialize
114
+ self.name = "Elem"
115
+ self.code = 0x9
116
+
117
+ @table_indices = []
118
+ @table_offsets = []
119
+ @element_indices = []
120
+ end
121
+ end
122
+
123
+ class CodeSection < Section
124
+ class CodeBody
125
+ attr_accessor :locals_count #: Array[Integer]
126
+
127
+ attr_accessor :locals_type #: Array[Symbol]
128
+
129
+ attr_accessor :body #: Array[Op]
130
+
131
+ # @rbs &blk: (CodeBody) -> void
132
+ # @rbs return: void
133
+ def initialize(&blk)
134
+ blk.call(self)
135
+ end
136
+ end
137
+
138
+ attr_accessor :func_codes #:Array[CodeBody]
139
+
140
+ # @rbs return: void
141
+ def initialize
142
+ self.name = "Code"
143
+ self.code = 0xa
144
+
145
+ @func_codes = []
146
+ end
147
+ end
148
+
149
+ class DataSection < Section
150
+ class Segment
151
+ attr_accessor :flags #: Integer
152
+
153
+ attr_accessor :offset #: Integer
154
+
155
+ attr_accessor :data #: String
156
+
157
+ # @rbs &blk: (Segment) -> void
158
+ # @rbs return: void
159
+ def initialize(&blk)
160
+ blk.call(self)
161
+ end
162
+ end
163
+
164
+ attr_accessor :segments #: Array[Segment]
165
+
166
+ # @rbs return: void
167
+ def initialize
168
+ self.name = "Data"
169
+ self.code = 0xb
170
+
171
+ @segments = []
172
+ end
173
+ end
174
+
175
+ class ExportSection < Section
176
+ class ExportDesc
177
+ attr_accessor :name #: String
178
+
179
+ attr_accessor :kind #: Integer
180
+
181
+ attr_accessor :func_index #: Integer
182
+ end
183
+
184
+ attr_accessor :exports #: Hash[String, ExportDesc]
185
+
186
+ def initialize #: void
187
+ self.name = "Export"
188
+ self.code = 0x7
189
+
190
+ @exports = {}
191
+ end
192
+
193
+ # @rbs &blk: (ExportDesc) -> void
194
+ def add_desc(&blk)
195
+ desc = ExportDesc.new
196
+ blk.call(desc)
197
+ self.exports[desc.name] = desc
198
+ end
199
+ end
200
+
201
+ class ImportSection < Section
202
+ class ImportDesc
203
+ attr_accessor :module_name #: String
204
+
205
+ attr_accessor :name #: String
206
+
207
+ attr_accessor :kind #: Integer
208
+
209
+ attr_accessor :sig_index #: Integer
210
+ end
211
+
212
+ attr_accessor :imports #: Array[ImportDesc]
213
+
214
+ def initialize #: void
215
+ self.name = "Import"
216
+ self.code = 0x2
217
+
218
+ @imports = []
219
+ end
220
+
221
+ # @rbs &blk: (ImportDesc) -> void
222
+ def add_desc(&blk)
223
+ desc = ImportDesc.new
224
+ blk.call(desc) if blk
225
+ self.imports << desc
226
+ end
227
+ end
228
+
229
+ module BinaryLoader
230
+ extend Wardite::Leb128Helper
231
+ extend Wardite::ValueHelper
232
+
233
+ # @rbs self.@buf: File|StringIO
234
+
235
+ # @rbs buf: File|StringIO
236
+ # @rbs import_object: Hash[Symbol, Hash[Symbol, wasmCallable]]
237
+ # @rbs enable_wasi: boolish
238
+ # @rbs return: Instance
239
+ def self.load_from_buffer(buf, import_object: {}, enable_wasi: true)
240
+ @buf = buf
241
+
242
+ version = preamble
243
+ sections_ = sections
244
+
245
+ if enable_wasi
246
+ wasi_env = Wardite::WasiSnapshotPreview1.new
247
+ import_object[:wasi_snapshot_preview1] = wasi_env.to_module
248
+ end
249
+
250
+ return Instance.new(import_object) do |i|
251
+ i.version = version
252
+ i.sections = sections_
253
+ end
254
+ end
255
+
256
+ # @rbs return: Integer
257
+ def self.preamble
258
+ asm = @buf.read 4
259
+ if !asm
260
+ raise LoadError, "buffer too short"
261
+ end
262
+ if asm != "\u0000asm"
263
+ raise LoadError, "invalid preamble"
264
+ end
265
+
266
+ vstr = @buf.read(4)
267
+ if !vstr
268
+ raise LoadError, "buffer too short"
269
+ end
270
+ version = vstr.to_enum(:chars)
271
+ .with_index
272
+ .inject(0) {|dest, (c, i)| dest | (c.ord << i*8) }
273
+ if version != 1
274
+ raise LoadError, "unsupported version: #{version}"
275
+ end
276
+ version
277
+ end
278
+
279
+ # @rbs return: Array[Section]
280
+ def self.sections
281
+ sections = [] #: Array[Section]
282
+
283
+ loop do
284
+ byte = @buf.read(1)
285
+ if !byte
286
+ break
287
+ end
288
+ code = byte.ord
289
+
290
+ section = case code
291
+ when Wardite::SectionType
292
+ type_section
293
+ when Wardite::SectionImport
294
+ import_section
295
+ when Wardite::SectionFunction
296
+ function_section
297
+ when Wardite::SectionTable
298
+ table_section
299
+ when Wardite::SectionMemory
300
+ memory_section
301
+ when Wardite::SectionGlobal
302
+ global_section
303
+ when Wardite::SectionExport
304
+ export_section
305
+ when Wardite::SectionStart
306
+ start_section
307
+ when Wardite::SectionElement
308
+ elem_section
309
+ when Wardite::SectionCode
310
+ code_section
311
+ when Wardite::SectionData
312
+ data_section
313
+ when Wardite::SectionCustom
314
+ unimplemented_skip_section(code)
315
+ else
316
+ raise LoadError, "unknown code: #{code}(\"#{code.to_s 16}\")"
317
+ end
318
+
319
+ if section
320
+ sections << section
321
+ end
322
+ end
323
+ sections
324
+ end
325
+
326
+ # @rbs return: TypeSection
327
+ def self.type_section
328
+ dest = TypeSection.new
329
+
330
+ size = fetch_uleb128(@buf)
331
+ dest.size = size
332
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
333
+
334
+ len = fetch_uleb128(sbuf)
335
+ len.times do |i|
336
+ fncode = assert_read(sbuf, 1)
337
+ if fncode != "\x60"
338
+ raise LoadError, "not a function definition"
339
+ end
340
+
341
+ arglen = fetch_uleb128(sbuf)
342
+ arg = []
343
+ arglen.times do
344
+ case ty = assert_read(sbuf, 1)&.ord
345
+ when 0x7f
346
+ arg << :i32
347
+ when 0x7e
348
+ arg << :i64
349
+ when 0x7d
350
+ arg << :f32
351
+ when 0x7c
352
+ arg << :f64
353
+ else
354
+ raise NotImplementedError, "unsupported for now: #{ty.inspect}"
355
+ end
356
+ end
357
+ dest.defined_types << arg
358
+
359
+ retlen = fetch_uleb128(sbuf)
360
+ ret = []
361
+ retlen.times do
362
+ case ty = assert_read(sbuf, 1)&.ord
363
+ when 0x7f
364
+ ret << :i32
365
+ when 0x7e
366
+ ret << :i64
367
+ when 0x7d
368
+ ret << :f32
369
+ when 0x7c
370
+ ret << :f64
371
+ else
372
+ raise NotImplementedError, "unsupported for now: #{ty.inspect}"
373
+ end
374
+ end
375
+ dest.defined_results << ret
376
+ end
377
+
378
+ dest
379
+ end
380
+
381
+ # @rbs return: ImportSection
382
+ def self.import_section
383
+ dest = ImportSection.new
384
+ size = fetch_uleb128(@buf)
385
+ dest.size = size
386
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
387
+
388
+ len = fetch_uleb128(sbuf)
389
+ len.times do |i|
390
+ mlen = fetch_uleb128(sbuf)
391
+ module_name = assert_read(sbuf, mlen)
392
+ nlen = fetch_uleb128(sbuf)
393
+ name = assert_read(sbuf, nlen)
394
+ kind_ = assert_read(sbuf, 1)
395
+ kind = kind_[0]&.ord
396
+ if !kind
397
+ raise "[BUG] empty unpacked string" # guard rbs
398
+ end
399
+
400
+ index = fetch_uleb128(sbuf)
401
+ dest.add_desc do |desc|
402
+ desc.module_name = module_name
403
+ desc.name = name
404
+ desc.kind = kind
405
+ desc.sig_index = index
406
+ end
407
+ end
408
+
409
+ dest
410
+ end
411
+
412
+ # @rbs return: MemorySection
413
+ def self.memory_section
414
+ dest = MemorySection.new
415
+ size = fetch_uleb128(@buf)
416
+ dest.size = size
417
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
418
+
419
+ len = fetch_uleb128(sbuf)
420
+ if len != 1
421
+ raise LoadError, "memory section has invalid size: #{len}"
422
+ end
423
+ len.times do |i|
424
+ flags = fetch_uleb128(sbuf)
425
+ min = fetch_uleb128(sbuf)
426
+
427
+ max = nil
428
+ if flags != 0
429
+ max = fetch_uleb128(sbuf)
430
+ end
431
+ dest.limits << [min, max]
432
+ end
433
+ dest
434
+ end
435
+
436
+ # @rbs return: StartSection
437
+ def self.start_section
438
+ dest = StartSection.new
439
+ size = fetch_uleb128(@buf)
440
+ dest.size = size
441
+ # StartSection won't use size
442
+ func_index = fetch_uleb128(@buf)
443
+ dest.func_index = func_index
444
+ dest
445
+ end
446
+
447
+ # @rbs return: ElemSection
448
+ def self.elem_section
449
+ dest = ElemSection.new
450
+ size = fetch_uleb128(@buf)
451
+ dest.size = size
452
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
453
+
454
+ len = fetch_uleb128(sbuf)
455
+ len.times do |i|
456
+ etype = fetch_uleb128(sbuf)
457
+ case etype
458
+ when 0x0 # expr, vec(funcidx)
459
+ dest.table_indices << 0 # default and fixed to table[0]
460
+
461
+ code = fetch_insn_while_end(sbuf)
462
+ ops = code_body(StringIO.new(code))
463
+ offset = decode_expr(ops)
464
+ dest.table_offsets << offset
465
+
466
+ elms = []
467
+ elen = fetch_uleb128(sbuf)
468
+ elen.times do |i|
469
+ index = fetch_uleb128(sbuf)
470
+ elms << index
471
+ end
472
+ dest.element_indices << elms
473
+ else
474
+ raise NotImplementedError, "element section type #{etype} is a TODO!"
475
+ end
476
+ end
477
+ dest
478
+ end
479
+
480
+ # @rbs return: GlobalSection
481
+ def self.global_section
482
+ dest = GlobalSection.new
483
+ size = fetch_uleb128(@buf)
484
+ dest.size = size
485
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
486
+
487
+ len = fetch_uleb128(sbuf)
488
+ len.times do |i|
489
+ typeb = fetch_uleb128(sbuf)
490
+ gtype = Op.i2type(typeb)
491
+ mut = sbuf.read 1
492
+ if !mut
493
+ raise LoadError, "global section too short"
494
+ end
495
+
496
+ code = fetch_insn_while_end(sbuf)
497
+ ops = code_body(StringIO.new(code))
498
+ value = decode_global_expr(ops)
499
+
500
+ global = GlobalSection::Global.new do |g|
501
+ g.type = gtype
502
+ g.mutable = (mut.ord == 0x01)
503
+ g.shared = false # always
504
+ g.value = value
505
+ end
506
+ dest.globals << global
507
+ end
508
+ dest
509
+ end
510
+
511
+ # @rbs return: FunctionSection
512
+ def self.function_section
513
+ dest = FunctionSection.new
514
+ size = fetch_uleb128(@buf)
515
+ dest.size = size
516
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
517
+
518
+ len = fetch_uleb128(sbuf)
519
+ len.times do |i|
520
+ index = fetch_uleb128(sbuf)
521
+ dest.func_indices << index
522
+ end
523
+ dest
524
+ end
525
+
526
+ # @rbs return: TableSection
527
+ def self.table_section
528
+ dest = TableSection.new
529
+ size = fetch_uleb128(@buf)
530
+ dest.size = size
531
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
532
+
533
+ len = fetch_uleb128(sbuf)
534
+ len.times do |i|
535
+ code = fetch_uleb128(sbuf)
536
+ type = Op.i2type(code)
537
+ dest.table_types << type
538
+
539
+ flags = fetch_uleb128(sbuf)
540
+ min = fetch_uleb128(sbuf)
541
+ max = nil
542
+ if flags != 0
543
+ max = fetch_uleb128(sbuf)
544
+ end
545
+ dest.table_limits << [min, max]
546
+ end
547
+ dest
548
+ end
549
+
550
+ # @rbs return: CodeSection
551
+ def self.code_section
552
+ dest = CodeSection.new
553
+ size = fetch_uleb128(@buf)
554
+ dest.size = size
555
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
556
+
557
+ len = fetch_uleb128(sbuf)
558
+ len.times do |i|
559
+ ilen = fetch_uleb128(sbuf)
560
+ code = assert_read(sbuf, ilen)
561
+ last_code = code[-1]
562
+ if ! last_code
563
+ raise "[BUG] empty code fetched" # guard for steep check
564
+ end
565
+ if last_code.ord != 0x0b
566
+ $stderr.puts "warning: instruction not ended with inst end(0x0b): 0x0#{last_code.ord}"
567
+ end
568
+ cbuf = StringIO.new(code)
569
+ locals_count = []
570
+ locals_type = []
571
+ locals_len = fetch_uleb128(cbuf)
572
+ locals_len.times do
573
+ type_count = fetch_uleb128(cbuf)
574
+ locals_count << type_count
575
+ value_type = assert_read(cbuf, 1)&.ord
576
+ locals_type << Op.i2type(value_type || -1)
577
+ end
578
+ body = code_body(cbuf)
579
+ dest.func_codes << CodeSection::CodeBody.new do |b|
580
+ b.locals_count = locals_count
581
+ b.locals_type = locals_type
582
+ b.body = body
583
+ end
584
+ end
585
+ dest
586
+ end
587
+
588
+ # @rbs buf: StringIO
589
+ # @rbs return: Array[::Wardite::Op]
590
+ def self.code_body(buf)
591
+ dest = []
592
+ while c = buf.read(1)
593
+ namespace, code = Op.to_sym(c)
594
+ operand_types = Op.operand_of(code)
595
+ operand = [] #: Array[operandItem]
596
+ operand_types.each do |typ|
597
+ case typ
598
+ when :u8
599
+ ope = buf.read 1
600
+ if ! ope
601
+ raise LoadError, "buffer too short"
602
+ end
603
+ operand << ope.ord
604
+ when :u32
605
+ operand << fetch_uleb128(buf)
606
+ when :u32_vec
607
+ len = fetch_uleb128(buf)
608
+ vec = [] #: Array[Integer]
609
+ len.times do
610
+ vec << fetch_uleb128(buf)
611
+ end
612
+ operand << vec
613
+ when :i32
614
+ operand << fetch_sleb128(buf)
615
+ when :i64
616
+ operand << fetch_sleb128(buf)
617
+ when :f32
618
+ data = buf.read 4
619
+ if !data || data.size != 4
620
+ raise LoadError, "buffer too short"
621
+ end
622
+ v = data.unpack("e")[0]
623
+ raise "String#unpack is broken" if !v.is_a?(Float)
624
+ operand << v
625
+ when :f64
626
+ data = buf.read 8
627
+ if !data || data.size != 8
628
+ raise LoadError, "buffer too short"
629
+ end
630
+ v = data.unpack("E")[0]
631
+ raise "String#unpack is broken" if !v.is_a?(Float)
632
+ operand << v
633
+ when :u8_block
634
+ block_ope = buf.read 1
635
+ if ! block_ope
636
+ raise LoadError, "buffer too short for if"
637
+ end
638
+ if block_ope.ord == 0x40
639
+ operand << Block.void
640
+ else
641
+ operand << Block.new([block_ope.ord])
642
+ end
643
+ else
644
+ $stderr.puts "warning: unknown type #{typ.inspect}. defaulting to u32"
645
+ operand << fetch_uleb128(buf)
646
+ end
647
+ end
648
+
649
+ dest << Op.new(namespace, code, operand)
650
+ end
651
+
652
+ dest
653
+ end
654
+
655
+ # @rbs return: DataSection
656
+ def self.data_section
657
+ dest = DataSection.new
658
+ size = fetch_uleb128(@buf)
659
+ dest.size = size
660
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
661
+
662
+ len = fetch_uleb128(sbuf)
663
+ len.times do |i|
664
+ mem_index = fetch_uleb128(sbuf)
665
+ code = fetch_insn_while_end(sbuf)
666
+ ops = code_body(StringIO.new(code))
667
+ offset = decode_expr(ops)
668
+
669
+ len = fetch_uleb128(sbuf)
670
+ data = sbuf.read len
671
+ if !data
672
+ raise LoadError, "buffer too short"
673
+ end
674
+
675
+ segment = DataSection::Segment.new do |seg|
676
+ seg.flags = mem_index
677
+ seg.offset = offset
678
+ seg.data = data
679
+ end
680
+ dest.segments << segment
681
+ end
682
+ dest
683
+ end
684
+
685
+ # @rbs sbuf: StringIO
686
+ # @rbs return: String
687
+ def self.fetch_insn_while_end(sbuf)
688
+ code = String.new("")
689
+ loop {
690
+ c = sbuf.read 1
691
+ if !c
692
+ break
693
+ end
694
+ code << c
695
+ if c == "\u000b" # :end
696
+ break
697
+ end
698
+ }
699
+ code
700
+ end
701
+
702
+ # @rbs ops: Array[Op]
703
+ # @rbs return: Integer
704
+ def self.decode_expr(ops)
705
+ # sees first opcode
706
+ op = ops.first
707
+ if !op
708
+ raise LoadError, "empty opcodes"
709
+ end
710
+ case op.code
711
+ when :i32_const
712
+ arg = op.operand[0]
713
+ if !arg.is_a?(Integer)
714
+ raise "Invalid definition of operand"
715
+ end
716
+ return arg
717
+ else
718
+ raise "Unimplemented offset op: #{op.code.inspect}"
719
+ end
720
+ end
721
+
722
+ # @rbs ops: Array[Op]
723
+ # @rbs return: wasmValue
724
+ def self.decode_global_expr(ops)
725
+ # sees first opcode
726
+ op = ops.first
727
+ if !op
728
+ raise LoadError, "empty opcodes"
729
+ end
730
+ case op.code
731
+ when :i32_const
732
+ arg = op.operand[0]
733
+ if !arg.is_a?(Integer)
734
+ raise "Invalid definition of operand"
735
+ end
736
+ return I32(arg)
737
+ when :i64_const
738
+ arg = op.operand[0]
739
+ if !arg.is_a?(Integer)
740
+ raise "Invalid definition of operand"
741
+ end
742
+ return I64(arg)
743
+ # TODO: floats
744
+ else
745
+ raise "Unimplemented offset op: #{op.code.inspect}"
746
+ end
747
+ end
748
+
749
+ # @rbs return: ExportSection
750
+ def self.export_section
751
+ dest = ExportSection.new
752
+ size = fetch_uleb128(@buf)
753
+ dest.size = size
754
+ sbuf = StringIO.new(@buf.read(size) || raise("buffer too short"))
755
+
756
+ len = fetch_uleb128(sbuf)
757
+ len.times do |i|
758
+ nlen = fetch_uleb128(sbuf)
759
+ name = assert_read(sbuf, nlen)
760
+ kind_ = assert_read(sbuf, 1)
761
+ kind = kind_[0]&.ord
762
+ if !kind
763
+ raise "[BUG] empty unpacked string" # guard rbs
764
+ end
765
+
766
+ index = fetch_uleb128(sbuf)
767
+ dest.add_desc do |desc|
768
+ desc.name = name
769
+ desc.kind = kind
770
+ desc.func_index = index
771
+ end
772
+ end
773
+
774
+ dest
775
+ end
776
+
777
+ # @rbs code: Integer
778
+ # @rbs return: nil
779
+ def self.unimplemented_skip_section(code)
780
+ $stderr.puts "warning: unimplemented section: 0x0#{code}"
781
+ size = @buf.read(1)&.ord
782
+ @buf.read(size)
783
+ nil
784
+ end
785
+
786
+ # @rbs sbuf: StringIO
787
+ # @rbs n: Integer
788
+ # @rbs return: String
789
+ def self.assert_read(sbuf, n)
790
+ ret = sbuf.read n
791
+ if !ret
792
+ raise LoadError, "too short section size"
793
+ end
794
+ if ret.size != n
795
+ raise LoadError, "too short section size"
796
+ end
797
+ ret
798
+ end
799
+ end
800
+ end