relaton-un 2.1.0 → 2.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 +4 -4
- data/.github/workflows/rake.yml +1 -1
- data/Gemfile +1 -0
- data/lib/relaton/un/token_generator.rb +25 -79
- data/lib/relaton/un/version.rb +1 -1
- data/lib/relaton/un/wasm/decoder.rb +434 -0
- data/lib/relaton/un/wasm/instance.rb +101 -0
- data/lib/relaton/un/wasm/interpreter.rb +613 -0
- data/lib/relaton/un/wasm/memory.rb +112 -0
- data/lib/relaton/un/wasm/module.rb +47 -0
- data/lib/relaton/un/wasm.rb +17 -0
- data/relaton-un.gemspec +0 -1
- metadata +8 -21
- data/grammars/basicdoc.rng +0 -2140
- data/grammars/biblio-standoc.rng +0 -268
- data/grammars/biblio.rng +0 -2125
- data/grammars/relaton-un-compile.rng +0 -11
- data/grammars/relaton-un.rng +0 -122
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd62341915793af74a836d522b901c108bda7fbc57825c0f663203d2f048777a
|
|
4
|
+
data.tar.gz: 26133140a50fb40d46db8eb3959beb9fe4bbbcf51d0716d7f1a5bcbbff782664
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13fa9b4d6f59851cbff14ade0621bee34ab7a2d152300a4feebab83673daa4923b46789ec21b417b86969e3c5935095f68950c511ebc72afaf70a443c5da63e9
|
|
7
|
+
data.tar.gz: 8fed8aefd9a06f23f27dbc2d9c5dc6f7dc4793fd884a734f56fedf69facd70d31dbbb46fd9e81d1ea4146f4dd7df81afe6f92dcf3122072834311e9c7195ae65
|
data/.github/workflows/rake.yml
CHANGED
data/Gemfile
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "wasmtime"
|
|
5
|
-
WASMTIME_LOADED = true
|
|
6
|
-
rescue LoadError
|
|
7
|
-
WASMTIME_LOADED = false
|
|
8
|
-
end
|
|
3
|
+
require_relative "wasm"
|
|
9
4
|
|
|
10
5
|
module Relaton
|
|
11
6
|
module Un
|
|
12
7
|
module TokenGenerator
|
|
13
|
-
WASMTIME_AVAILABLE = WASMTIME_LOADED
|
|
14
8
|
WASM_PATH = File.join(__dir__, "wasm_v_bg.wasm")
|
|
15
9
|
DOMAIN = "documents.un.org"
|
|
16
10
|
|
|
@@ -18,11 +12,6 @@ module Relaton
|
|
|
18
12
|
# Tokens are cached per-minute since the WASM output changes each minute.
|
|
19
13
|
# @return [String] decimal string representation of the i64 token
|
|
20
14
|
def self.generate
|
|
21
|
-
unless WASMTIME_AVAILABLE
|
|
22
|
-
warn "[relaton-un] wasmtime gem is not available on this platform. Token generation is disabled."
|
|
23
|
-
return nil
|
|
24
|
-
end
|
|
25
|
-
|
|
26
15
|
now = Time.now.utc
|
|
27
16
|
key = [now.year, now.month, now.day, now.hour, now.min]
|
|
28
17
|
return @cached_token if @cached_key == key && @cached_token
|
|
@@ -31,92 +20,49 @@ module Relaton
|
|
|
31
20
|
@cached_token = call_wasm(*key)
|
|
32
21
|
end
|
|
33
22
|
|
|
34
|
-
def self.call_wasm(year, month, day, hour, minute)
|
|
35
|
-
|
|
36
|
-
mod = Wasmtime::Module.new(engine, File.binread(WASM_PATH))
|
|
37
|
-
store = Wasmtime::Store.new(engine)
|
|
38
|
-
linker = Wasmtime::Linker.new(engine)
|
|
39
|
-
|
|
23
|
+
def self.call_wasm(year, month, day, hour, minute)
|
|
24
|
+
mod = (@module ||= Wasm::Module.parse(File.binread(WASM_PATH)))
|
|
40
25
|
heap = Heap.new
|
|
41
26
|
fake_window = Object.new
|
|
42
27
|
fake_location = Object.new
|
|
43
|
-
|
|
44
|
-
# Pre-allocate well-known objects in the heap
|
|
45
28
|
heap.alloc(fake_window) # index 4
|
|
46
29
|
heap.alloc(fake_location) # index 5
|
|
47
30
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
instance
|
|
51
|
-
result = instance.invoke("check", year, month, day, hour, minute)
|
|
52
|
-
result.to_s
|
|
31
|
+
imports = { "wbg" => build_imports(heap, fake_window, fake_location) }
|
|
32
|
+
instance = Wasm::Instance.new(mod, imports)
|
|
33
|
+
instance.invoke("check", year, month, day, hour, minute).to_s
|
|
53
34
|
end
|
|
54
35
|
|
|
55
|
-
def self.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
linker.func_new("wbg", "__wbg_location_8cc8ccf27e342c0a", [:i32], [:i32]) do |_caller, _idx|
|
|
65
|
-
heap.alloc(fake_location)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
define_host_import(linker, heap)
|
|
69
|
-
|
|
70
|
-
linker.func_new("wbg", "__wbg_newnoargs_b5b063fc6c2f0376", [:i32, :i32], [:i32]) do |_caller, _ptr, _len|
|
|
71
|
-
heap.alloc(Object.new)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
linker.func_new("wbg", "__wbg_call_97ae9d8645dc388b", [:i32, :i32], [:i32]) do |_caller, _func_idx, _this_idx|
|
|
75
|
-
heap.alloc(fake_window)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
linker.func_new("wbg", "__wbindgen_object_clone_ref", [:i32], [:i32]) do |_caller, idx|
|
|
79
|
-
heap.alloc(heap.get(idx))
|
|
80
|
-
end
|
|
81
|
-
|
|
36
|
+
def self.build_imports(heap, fake_window, fake_location) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
37
|
+
w = {}
|
|
38
|
+
w["__wbindgen_object_drop_ref"] = ->(_c, idx) { heap.drop(idx); nil }
|
|
39
|
+
w["__wbg_instanceof_Window_acc97ff9f5d2c7b4"] = ->(_c, _idx) { 1 }
|
|
40
|
+
w["__wbg_location_8cc8ccf27e342c0a"] = ->(_c, _idx) { heap.alloc(fake_location) }
|
|
41
|
+
w["__wbg_newnoargs_b5b063fc6c2f0376"] = ->(_c, _ptr, _len) { heap.alloc(Object.new) }
|
|
42
|
+
w["__wbg_call_97ae9d8645dc388b"] = ->(_c, _f, _t) { heap.alloc(fake_window) }
|
|
43
|
+
w["__wbindgen_object_clone_ref"] = ->(_c, idx) { heap.alloc(heap.get(idx)) }
|
|
82
44
|
%w[
|
|
83
45
|
__wbg_self_6d479506f72c6a71
|
|
84
46
|
__wbg_window_f2557cc78490aceb
|
|
85
47
|
__wbg_globalThis_7f206bda628d5286
|
|
86
48
|
__wbg_global_ba75c50d1cf384f4
|
|
87
|
-
].each
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
linker.func_new("wbg", "__wbindgen_is_undefined", [:i32], [:i32]) do |_caller, idx|
|
|
94
|
-
heap.get(idx) == :undefined ? 1 : 0
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
linker.func_new("wbg", "__wbindgen_debug_string", [:i32, :i32], []) do |_caller, _ret_ptr, _idx|
|
|
98
|
-
# no-op
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
linker.func_new("wbg", "__wbindgen_throw", [:i32, :i32], []) do |caller, ptr, len|
|
|
49
|
+
].each { |name| w[name] = ->(_c) { heap.alloc(fake_window) } }
|
|
50
|
+
w["__wbindgen_is_undefined"] = ->(_c, idx) { heap.get(idx) == :undefined ? 1 : 0 }
|
|
51
|
+
w["__wbindgen_debug_string"] = ->(_c, _ret_ptr, _idx) { nil }
|
|
52
|
+
w["__wbindgen_throw"] = lambda do |caller, ptr, len|
|
|
102
53
|
mem = caller.export("memory").to_memory
|
|
103
|
-
|
|
104
|
-
raise msg
|
|
54
|
+
raise mem.read(ptr, len)
|
|
105
55
|
end
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
# The host import writes the domain string into WASM memory.
|
|
109
|
-
# It allocates space via __wbindgen_malloc, copies the UTF-8 bytes,
|
|
110
|
-
# and writes (ptr, len) at the return pointer address.
|
|
111
|
-
def self.define_host_import(linker, _heap)
|
|
112
|
-
linker.func_new("wbg", "__wbg_host_f82dbbd8bb5ef24a", [:i32, :i32], []) do |caller, ret_ptr, _loc_idx|
|
|
56
|
+
w["__wbg_host_f82dbbd8bb5ef24a"] = lambda do |caller, ret_ptr, _loc_idx|
|
|
113
57
|
mem = caller.export("memory").to_memory
|
|
114
|
-
|
|
58
|
+
malloc = caller.export("__wbindgen_malloc").to_func
|
|
115
59
|
host_bytes = DOMAIN.encode("utf-8")
|
|
116
|
-
ptr =
|
|
60
|
+
ptr = malloc.call(host_bytes.bytesize)
|
|
117
61
|
mem.write(ptr, host_bytes)
|
|
118
62
|
mem.write(ret_ptr, [ptr, host_bytes.bytesize].pack("V2"))
|
|
63
|
+
nil
|
|
119
64
|
end
|
|
65
|
+
w
|
|
120
66
|
end
|
|
121
67
|
|
|
122
68
|
# Manages a slab-allocated heap of Ruby objects indexed by i32,
|
|
@@ -153,7 +99,7 @@ module Relaton
|
|
|
153
99
|
end
|
|
154
100
|
end
|
|
155
101
|
|
|
156
|
-
private_class_method :call_wasm, :
|
|
102
|
+
private_class_method :call_wasm, :build_imports
|
|
157
103
|
end
|
|
158
104
|
end
|
|
159
105
|
end
|
data/lib/relaton/un/version.rb
CHANGED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Un
|
|
5
|
+
module Wasm
|
|
6
|
+
# Binary WebAssembly decoder. Handles the subset of WASM MVP plus the
|
|
7
|
+
# sign-extension proposal that the shipped wasm_v_bg.wasm uses. No
|
|
8
|
+
# floats, no SIMD, no bulk-memory, no reference types beyond funcref.
|
|
9
|
+
class Decoder
|
|
10
|
+
MAGIC = "\x00asm".b
|
|
11
|
+
VERSION = "\x01\x00\x00\x00".b
|
|
12
|
+
|
|
13
|
+
VALTYPE_I32 = 0x7F
|
|
14
|
+
VALTYPE_I64 = 0x7E
|
|
15
|
+
VALTYPE_F32 = 0x7D
|
|
16
|
+
VALTYPE_F64 = 0x7C
|
|
17
|
+
|
|
18
|
+
def initialize(bytes)
|
|
19
|
+
@bytes = bytes.b
|
|
20
|
+
@pos = 0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def decode
|
|
24
|
+
header!
|
|
25
|
+
mod = Module.new
|
|
26
|
+
until eof?
|
|
27
|
+
id = read_byte
|
|
28
|
+
size = read_u32
|
|
29
|
+
section_end = @pos + size
|
|
30
|
+
decode_section(mod, id, section_end)
|
|
31
|
+
@pos = section_end
|
|
32
|
+
end
|
|
33
|
+
mod
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def header!
|
|
39
|
+
magic = take(4)
|
|
40
|
+
raise DecodeError, "bad magic: #{magic.inspect}" unless magic == MAGIC
|
|
41
|
+
|
|
42
|
+
version = take(4)
|
|
43
|
+
raise DecodeError, "bad version: #{version.inspect}" unless version == VERSION
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def decode_section(mod, id, section_end)
|
|
47
|
+
case id
|
|
48
|
+
when 0 then skip_to(section_end) # custom
|
|
49
|
+
when 1 then decode_types(mod)
|
|
50
|
+
when 2 then decode_imports(mod)
|
|
51
|
+
when 3 then decode_functions(mod)
|
|
52
|
+
when 4 then decode_tables(mod)
|
|
53
|
+
when 5 then decode_memories(mod)
|
|
54
|
+
when 6 then decode_globals(mod)
|
|
55
|
+
when 7 then decode_exports(mod)
|
|
56
|
+
when 8 then mod.instance_variable_set(:@start, read_u32)
|
|
57
|
+
when 9 then decode_elements(mod)
|
|
58
|
+
when 10 then decode_code(mod)
|
|
59
|
+
when 11 then decode_data(mod)
|
|
60
|
+
else raise DecodeError, "unknown section id #{id}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def decode_types(mod)
|
|
65
|
+
read_vec do
|
|
66
|
+
tag = read_byte
|
|
67
|
+
raise DecodeError, "bad functype tag #{tag}" unless tag == 0x60
|
|
68
|
+
|
|
69
|
+
params = read_vec { read_byte }
|
|
70
|
+
results = read_vec { read_byte }
|
|
71
|
+
mod.types << FuncType.new(params, results)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def decode_imports(mod)
|
|
76
|
+
read_vec do
|
|
77
|
+
mod_name = read_name
|
|
78
|
+
field = read_name
|
|
79
|
+
kind_byte = read_byte
|
|
80
|
+
kind, desc = case kind_byte
|
|
81
|
+
when 0x00 then [:func, read_u32]
|
|
82
|
+
when 0x01 then [:table, decode_tabletype]
|
|
83
|
+
when 0x02 then [:memory, decode_memtype]
|
|
84
|
+
when 0x03 then [:global, [read_byte, read_byte == 1]]
|
|
85
|
+
else raise DecodeError, "bad import kind #{kind_byte}"
|
|
86
|
+
end
|
|
87
|
+
mod.imports << Import.new(mod_name, field, kind, desc)
|
|
88
|
+
mod.function_type_indices << desc if kind == :func
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def decode_functions(mod)
|
|
93
|
+
read_vec do
|
|
94
|
+
mod.function_type_indices << read_u32
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def decode_tables(mod)
|
|
99
|
+
read_vec do
|
|
100
|
+
mod.tables << decode_tabletype
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def decode_memories(mod)
|
|
105
|
+
read_vec do
|
|
106
|
+
mod.memories << decode_memtype
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def decode_globals(mod)
|
|
111
|
+
read_vec do
|
|
112
|
+
valtype = read_byte
|
|
113
|
+
mutable = read_byte == 1
|
|
114
|
+
init_expr = decode_const_expr
|
|
115
|
+
mod.globals << Global.new(valtype, mutable, init_expr)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def decode_exports(mod)
|
|
120
|
+
read_vec do
|
|
121
|
+
name = read_name
|
|
122
|
+
kind_byte = read_byte
|
|
123
|
+
kind = case kind_byte
|
|
124
|
+
when 0x00 then :func
|
|
125
|
+
when 0x01 then :table
|
|
126
|
+
when 0x02 then :memory
|
|
127
|
+
when 0x03 then :global
|
|
128
|
+
else raise DecodeError, "bad export kind #{kind_byte}"
|
|
129
|
+
end
|
|
130
|
+
mod.exports << Export.new(name, kind, read_u32)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def decode_elements(mod)
|
|
135
|
+
read_vec do
|
|
136
|
+
flags = read_u32
|
|
137
|
+
# MVP form: flags == 0 means active, table 0, offset, funcref vec.
|
|
138
|
+
raise DecodeError, "unsupported element segment flags #{flags}" unless flags.zero?
|
|
139
|
+
|
|
140
|
+
offset_expr = decode_const_expr
|
|
141
|
+
funcs = read_vec { read_u32 }
|
|
142
|
+
mod.elements << Element.new(0, offset_expr, funcs)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def decode_code(mod)
|
|
147
|
+
read_vec do
|
|
148
|
+
size = read_u32
|
|
149
|
+
body_end = @pos + size
|
|
150
|
+
local_groups = read_vec { [read_u32, read_byte] } # [count, valtype]
|
|
151
|
+
locals = local_groups.flat_map { |count, vt| Array.new(count, vt) }
|
|
152
|
+
body = decode_instructions(body_end)
|
|
153
|
+
mod.code << Code.new(locals, body)
|
|
154
|
+
@pos = body_end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def decode_data(mod)
|
|
159
|
+
read_vec do
|
|
160
|
+
flags = read_u32
|
|
161
|
+
mem_idx = 0
|
|
162
|
+
offset_expr = nil
|
|
163
|
+
case flags
|
|
164
|
+
when 0
|
|
165
|
+
offset_expr = decode_const_expr
|
|
166
|
+
when 1
|
|
167
|
+
# passive — not used here
|
|
168
|
+
when 2
|
|
169
|
+
mem_idx = read_u32
|
|
170
|
+
offset_expr = decode_const_expr
|
|
171
|
+
else
|
|
172
|
+
raise DecodeError, "bad data flags #{flags}"
|
|
173
|
+
end
|
|
174
|
+
bytes = take(read_u32)
|
|
175
|
+
mod.data << Data.new(mem_idx, offset_expr, bytes)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def decode_tabletype
|
|
180
|
+
elem_type = read_byte
|
|
181
|
+
decode_limits.then { |init, max| TableType.new(elem_type, init, max) }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def decode_memtype
|
|
185
|
+
decode_limits.then { |init, max| MemoryType.new(init, max) }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def decode_limits
|
|
189
|
+
flag = read_byte
|
|
190
|
+
init = read_u32
|
|
191
|
+
max = flag == 1 ? read_u32 : nil
|
|
192
|
+
[init, max]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# A constant init expression: a single instruction followed by `end`.
|
|
196
|
+
# For our module it'll only be i32.const / i64.const / global.get.
|
|
197
|
+
def decode_const_expr
|
|
198
|
+
op = read_byte
|
|
199
|
+
val = case op
|
|
200
|
+
when 0x41 then [:i32_const, read_s32]
|
|
201
|
+
when 0x42 then [:i64_const, read_s64]
|
|
202
|
+
when 0x23 then [:global_get, read_u32]
|
|
203
|
+
else raise DecodeError, "unsupported const expr op 0x#{op.to_s(16)}"
|
|
204
|
+
end
|
|
205
|
+
end_op = read_byte
|
|
206
|
+
raise DecodeError, "const expr not terminated with end (got 0x#{end_op.to_s(16)})" unless end_op == 0x0B
|
|
207
|
+
|
|
208
|
+
val
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Decode the instruction stream of a function body up to its final
|
|
212
|
+
# `end`. Returns a flat array of [op_sym, *operands]. Block-introducing
|
|
213
|
+
# ops are post-processed to carry `end_pc` (and `else_pc` for `if`)
|
|
214
|
+
# so the interpreter can jump without scanning at runtime.
|
|
215
|
+
def decode_instructions(body_end)
|
|
216
|
+
instrs = []
|
|
217
|
+
stack = [] # of [kind_sym, instr_index]
|
|
218
|
+
|
|
219
|
+
until @pos >= body_end
|
|
220
|
+
op = read_byte
|
|
221
|
+
pc = instrs.size
|
|
222
|
+
case op
|
|
223
|
+
when 0x00 then instrs << [:unreachable]
|
|
224
|
+
when 0x01 then instrs << [:nop]
|
|
225
|
+
when 0x02
|
|
226
|
+
bt = decode_blocktype
|
|
227
|
+
instrs << [:block, bt, nil] # end_pc filled in below
|
|
228
|
+
stack << [:block, pc]
|
|
229
|
+
when 0x03
|
|
230
|
+
bt = decode_blocktype
|
|
231
|
+
instrs << [:loop, bt, nil]
|
|
232
|
+
stack << [:loop, pc]
|
|
233
|
+
when 0x04
|
|
234
|
+
bt = decode_blocktype
|
|
235
|
+
instrs << [:if, bt, nil, nil] # else_pc, end_pc
|
|
236
|
+
stack << [:if, pc]
|
|
237
|
+
when 0x05
|
|
238
|
+
if_kind, if_pc = stack.last
|
|
239
|
+
raise DecodeError, "else without matching if" unless if_kind == :if
|
|
240
|
+
|
|
241
|
+
instrs[if_pc][2] = pc # else_pc points at the `else` instruction
|
|
242
|
+
instrs << [:else, nil]
|
|
243
|
+
when 0x0B
|
|
244
|
+
instrs << [:end]
|
|
245
|
+
if stack.any?
|
|
246
|
+
kind, intro_pc = stack.pop
|
|
247
|
+
case kind
|
|
248
|
+
when :block, :loop then instrs[intro_pc][2] = pc
|
|
249
|
+
when :if
|
|
250
|
+
instrs[intro_pc][3] = pc # end_pc
|
|
251
|
+
instrs[intro_pc][2] ||= pc # else_pc defaults to end_pc
|
|
252
|
+
# if there was an `else`, fix it up
|
|
253
|
+
else_pc = instrs[intro_pc][2]
|
|
254
|
+
instrs[else_pc][1] = pc if else_pc < pc && instrs[else_pc][0] == :else
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
when 0x0C then instrs << [:br, read_u32]
|
|
258
|
+
when 0x0D then instrs << [:br_if, read_u32]
|
|
259
|
+
when 0x0E
|
|
260
|
+
labels = read_vec { read_u32 }
|
|
261
|
+
default = read_u32
|
|
262
|
+
instrs << [:br_table, labels, default]
|
|
263
|
+
when 0x0F then instrs << [:return_]
|
|
264
|
+
when 0x10 then instrs << [:call, read_u32]
|
|
265
|
+
when 0x11
|
|
266
|
+
type_idx = read_u32
|
|
267
|
+
table_idx = read_u32
|
|
268
|
+
instrs << [:call_indirect, type_idx, table_idx]
|
|
269
|
+
when 0x1A then instrs << [:drop]
|
|
270
|
+
when 0x1B then instrs << [:select]
|
|
271
|
+
when 0x20 then instrs << [:local_get, read_u32]
|
|
272
|
+
when 0x21 then instrs << [:local_set, read_u32]
|
|
273
|
+
when 0x22 then instrs << [:local_tee, read_u32]
|
|
274
|
+
when 0x23 then instrs << [:global_get, read_u32]
|
|
275
|
+
when 0x24 then instrs << [:global_set, read_u32]
|
|
276
|
+
when 0x28..0x3E
|
|
277
|
+
align = read_u32
|
|
278
|
+
offset = read_u32
|
|
279
|
+
instrs << [MEM_OPS[op], align, offset]
|
|
280
|
+
when 0x3F
|
|
281
|
+
read_byte # reserved
|
|
282
|
+
instrs << [:memory_size]
|
|
283
|
+
when 0x40
|
|
284
|
+
read_byte # reserved
|
|
285
|
+
instrs << [:memory_grow]
|
|
286
|
+
when 0x41 then instrs << [:i32_const, read_s32]
|
|
287
|
+
when 0x42 then instrs << [:i64_const, read_s64]
|
|
288
|
+
when 0x45..0x8A then instrs << [NUM_OPS.fetch(op)]
|
|
289
|
+
when 0xA7 then instrs << [:i32_wrap_i64]
|
|
290
|
+
when 0xAC then instrs << [:i64_extend_i32_s]
|
|
291
|
+
when 0xAD then instrs << [:i64_extend_i32_u]
|
|
292
|
+
when 0xC0 then instrs << [:i32_extend8_s]
|
|
293
|
+
when 0xC1 then instrs << [:i32_extend16_s]
|
|
294
|
+
when 0xC2 then instrs << [:i64_extend8_s]
|
|
295
|
+
when 0xC3 then instrs << [:i64_extend16_s]
|
|
296
|
+
when 0xC4 then instrs << [:i64_extend32_s]
|
|
297
|
+
else
|
|
298
|
+
raise DecodeError, "unsupported opcode 0x#{op.to_s(16)} at byte #{@pos - 1}"
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
instrs
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Block type: 0x40 = empty, single valtype byte, or signed LEB128 type
|
|
305
|
+
# index. Returns one of [:empty] / [:value, valtype] / [:typeidx, idx].
|
|
306
|
+
def decode_blocktype
|
|
307
|
+
b = peek_byte
|
|
308
|
+
if b == 0x40
|
|
309
|
+
read_byte
|
|
310
|
+
[:empty]
|
|
311
|
+
elsif [VALTYPE_I32, VALTYPE_I64, VALTYPE_F32, VALTYPE_F64].include?(b)
|
|
312
|
+
[:value, read_byte]
|
|
313
|
+
else
|
|
314
|
+
[:typeidx, read_s33]
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
MEM_OPS = {
|
|
319
|
+
0x28 => :i32_load, 0x29 => :i64_load,
|
|
320
|
+
0x2C => :i32_load8_s, 0x2D => :i32_load8_u,
|
|
321
|
+
0x2E => :i32_load16_s, 0x2F => :i32_load16_u,
|
|
322
|
+
0x30 => :i64_load8_s, 0x31 => :i64_load8_u,
|
|
323
|
+
0x32 => :i64_load16_s, 0x33 => :i64_load16_u,
|
|
324
|
+
0x34 => :i64_load32_s, 0x35 => :i64_load32_u,
|
|
325
|
+
0x36 => :i32_store, 0x37 => :i64_store,
|
|
326
|
+
0x3A => :i32_store8, 0x3B => :i32_store16,
|
|
327
|
+
0x3C => :i64_store8, 0x3D => :i64_store16,
|
|
328
|
+
0x3E => :i64_store32
|
|
329
|
+
}.freeze
|
|
330
|
+
|
|
331
|
+
NUM_OPS = {
|
|
332
|
+
0x45 => :i32_eqz, 0x46 => :i32_eq, 0x47 => :i32_ne,
|
|
333
|
+
0x48 => :i32_lt_s, 0x49 => :i32_lt_u, 0x4A => :i32_gt_s, 0x4B => :i32_gt_u,
|
|
334
|
+
0x4C => :i32_le_s, 0x4D => :i32_le_u, 0x4E => :i32_ge_s, 0x4F => :i32_ge_u,
|
|
335
|
+
0x50 => :i64_eqz, 0x51 => :i64_eq, 0x52 => :i64_ne,
|
|
336
|
+
0x53 => :i64_lt_s, 0x54 => :i64_lt_u, 0x55 => :i64_gt_s, 0x56 => :i64_gt_u,
|
|
337
|
+
0x57 => :i64_le_s, 0x58 => :i64_le_u, 0x59 => :i64_ge_s, 0x5A => :i64_ge_u,
|
|
338
|
+
0x67 => :i32_clz, 0x68 => :i32_ctz, 0x69 => :i32_popcnt,
|
|
339
|
+
0x6A => :i32_add, 0x6B => :i32_sub, 0x6C => :i32_mul,
|
|
340
|
+
0x6D => :i32_div_s, 0x6E => :i32_div_u, 0x6F => :i32_rem_s, 0x70 => :i32_rem_u,
|
|
341
|
+
0x71 => :i32_and, 0x72 => :i32_or, 0x73 => :i32_xor,
|
|
342
|
+
0x74 => :i32_shl, 0x75 => :i32_shr_s, 0x76 => :i32_shr_u,
|
|
343
|
+
0x77 => :i32_rotl, 0x78 => :i32_rotr,
|
|
344
|
+
0x79 => :i64_clz, 0x7A => :i64_ctz, 0x7B => :i64_popcnt,
|
|
345
|
+
0x7C => :i64_add, 0x7D => :i64_sub, 0x7E => :i64_mul,
|
|
346
|
+
0x7F => :i64_div_s, 0x80 => :i64_div_u, 0x81 => :i64_rem_s, 0x82 => :i64_rem_u,
|
|
347
|
+
0x83 => :i64_and, 0x84 => :i64_or, 0x85 => :i64_xor,
|
|
348
|
+
0x86 => :i64_shl, 0x87 => :i64_shr_s, 0x88 => :i64_shr_u,
|
|
349
|
+
0x89 => :i64_rotl, 0x8A => :i64_rotr
|
|
350
|
+
}.freeze
|
|
351
|
+
|
|
352
|
+
# ---- low-level reading -----------------------------------------------
|
|
353
|
+
|
|
354
|
+
def eof?
|
|
355
|
+
@pos >= @bytes.bytesize
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def read_byte
|
|
359
|
+
b = @bytes.getbyte(@pos)
|
|
360
|
+
raise DecodeError, "unexpected eof" unless b
|
|
361
|
+
|
|
362
|
+
@pos += 1
|
|
363
|
+
b
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def peek_byte
|
|
367
|
+
@bytes.getbyte(@pos) or raise(DecodeError, "unexpected eof")
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def take(n)
|
|
371
|
+
s = @bytes.byteslice(@pos, n)
|
|
372
|
+
raise DecodeError, "unexpected eof (wanted #{n} bytes)" unless s && s.bytesize == n
|
|
373
|
+
|
|
374
|
+
@pos += n
|
|
375
|
+
s
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def skip_to(end_pos)
|
|
379
|
+
@pos = end_pos
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def read_u32
|
|
383
|
+
result = 0
|
|
384
|
+
shift = 0
|
|
385
|
+
loop do
|
|
386
|
+
b = read_byte
|
|
387
|
+
result |= (b & 0x7F) << shift
|
|
388
|
+
break if (b & 0x80).zero?
|
|
389
|
+
|
|
390
|
+
shift += 7
|
|
391
|
+
raise DecodeError, "u32 overflow" if shift > 35
|
|
392
|
+
end
|
|
393
|
+
result
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def read_s32
|
|
397
|
+
read_signed(32)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def read_s33
|
|
401
|
+
read_signed(33)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def read_s64
|
|
405
|
+
read_signed(64)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def read_signed(bits)
|
|
409
|
+
result = 0
|
|
410
|
+
shift = 0
|
|
411
|
+
loop do
|
|
412
|
+
b = read_byte
|
|
413
|
+
result |= (b & 0x7F) << shift
|
|
414
|
+
shift += 7
|
|
415
|
+
if (b & 0x80).zero?
|
|
416
|
+
result -= (1 << shift) if shift < bits && (b & 0x40).positive?
|
|
417
|
+
return result
|
|
418
|
+
end
|
|
419
|
+
raise DecodeError, "signed overflow" if shift > bits + 7
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def read_name
|
|
424
|
+
take(read_u32).force_encoding(Encoding::UTF_8)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def read_vec
|
|
428
|
+
count = read_u32
|
|
429
|
+
Array.new(count) { yield }
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|