1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
use crate::{
AddressOffset, DataSegmentIdx, I64ValueSplit, InstructionSet, TrapCode, N_BYTES_PER_MEMORY_PAGE,
};
use rwasm_fuel_policy::{MEMORY_BYTES_PER_FUEL, MEMORY_BYTES_PER_FUEL_LOG2};
impl InstructionSet {
pub const MSH_I64_LOAD: u32 = 2;
pub const MSH_I64_LOAD8_S: u32 = 1;
pub const MSH_I64_LOAD8_U: u32 = 1;
pub const MSH_I64_LOAD16_S: u32 = 1;
pub const MSH_I64_LOAD16_U: u32 = 1;
pub const MSH_I64_LOAD32_S: u32 = 1;
pub const MSH_I64_LOAD32_U: u32 = 1;
pub const MSH_I64_STORE: u32 = 2;
pub const MSH_I64_STORE8: u32 = 0;
pub const MSH_I64_STORE16: u32 = 0;
pub const MSH_I64_STORE32: u32 = 0;
pub const MSH_I64_CONST: u32 = 2;
pub const MSH_MEMORY_GROW_CHECKED: u32 = 2;
pub const MSH_MEMORY_FILL_CHECKED: u32 = 2;
pub const MSH_MEMORY_COPY_CHECKED: u32 = 2;
pub const MSH_MEMORY_INIT_CHECKED: u32 = 2;
/// Loads a 64-bit word from a memory and copies it on the stack by as 32-bit words
///
/// Input: [addr]
/// Output: [hi, lo]
///
/// Max stack height: 2
pub fn op_i64_load(&mut self, offset: AddressOffset) {
// [addr]
self.op_local_get(1);
// [addr, addr]
self.op_i32_load(offset.saturating_add(4));
// [hi, addr]
self.op_local_get(2);
// [addr, hi, addr]
self.op_i32_load(offset);
// [lo, hi, addr]
self.op_local_set(2);
// [hi, lo]
}
/// Max stack height: 1
pub fn op_i64_load8_s(&mut self, offset: AddressOffset) {
self.op_i32_load8_s(offset);
self.op_dup();
self.op_i32_const(31);
self.op_i32_shr_s();
}
/// Max stack height: 1
pub fn op_i64_load8_u(&mut self, offset: AddressOffset) {
self.op_i32_load8_u(offset);
self.op_i32_const(0);
}
/// Max stack height: 1
pub fn op_i64_load16_s(&mut self, offset: AddressOffset) {
self.op_i32_load16_s(offset);
self.op_dup();
self.op_i32_const(31);
self.op_i32_shr_s();
}
/// Max stack height: 1
pub fn op_i64_load16_u(&mut self, offset: AddressOffset) {
self.op_i32_load16_u(offset);
self.op_i32_const(0);
}
/// Max stack height: 1
pub fn op_i64_load32_s(&mut self, offset: AddressOffset) {
self.op_i32_load(offset);
self.op_dup();
self.op_i32_const(31);
self.op_i32_shr_s();
}
/// Max stack height: 1
pub fn op_i64_load32_u(&mut self, offset: AddressOffset) {
self.op_i32_load(offset);
self.op_i32_const(0);
}
/// Max stack height: 2
pub fn op_i64_store(&mut self, offset: AddressOffset) {
self.op_local_get(3);
self.op_local_get(2);
self.op_i32_store(offset.saturating_add(4));
self.op_drop();
self.op_i32_store(offset);
}
/// Max stack height: 0
pub fn op_i64_store8(&mut self, offset: AddressOffset) {
self.op_drop();
self.op_i32_store8(offset);
}
/// Max stack height: 0
pub fn op_i64_store16(&mut self, offset: AddressOffset) {
self.op_drop();
self.op_i32_store16(offset);
}
/// Max stack height: 0
pub fn op_i64_store32(&mut self, offset: AddressOffset) {
self.op_drop();
self.op_i32_store(offset);
}
/// Max stack height: 2
pub fn op_i64_const(&mut self, value: i64) {
let (lo, hi) = value.split_into_i32_tuple();
self.op_i32_const(lo); // [lo]
self.op_i32_const(hi); // [hi, lo]
}
/// Max stack height: 2
pub fn op_memory_grow_checked(&mut self, max_pages: Option<u32>, inject_fuel_check: bool) {
// we must do max memory check before an execution
if let Some(max_pages) = max_pages {
self.op_local_get(1); // d
self.op_memory_size(); // memory_size
self.op_i32_add(); // memory_size+d
self.op_i32_const(max_pages); // max_pages
self.op_i32_gt_s(); // memory_size+d>max_pages
self.op_br_if_eqz(4);
self.op_drop();
self.op_i32_const(u32::MAX);
self.op_br(if inject_fuel_check { 8 } else { 2 });
}
// now we know that pages can't exceed i32::MAX,
// so we can safely multiply the num of pages to the page size
// to calculate fuel required for memory to grow
if inject_fuel_check {
self.op_local_get(1); // n
self.op_i32_const(N_BYTES_PER_MEMORY_PAGE); // size of each memory page
self.op_i32_mul(); // overflow is impossible here (we pass max pages in trustless mode)
self.op_i32_const(MEMORY_BYTES_PER_FUEL_LOG2); // 2^6=64
self.op_i32_shr_u(); // delta/64
self.op_consume_fuel_stack();
}
// emit memory grows only if fuel is charged
self.op_memory_grow();
}
/// Max stack height: 2
pub fn op_memory_fill_checked(&mut self, inject_fuel_check: bool) {
// [d, val, n]
if inject_fuel_check {
self.op_local_get(1); // n
self.op_i32_const(MEMORY_BYTES_PER_FUEL - 1); // upper round
self.op_i32_add();
self.op_i32_const(MEMORY_BYTES_PER_FUEL_LOG2); // 2^6=64
self.op_i32_shr_u(); // delta/64
self.op_consume_fuel_stack();
}
// emit memory fill
self.op_memory_fill();
}
/// Max stack height: 2
pub fn op_memory_copy_checked(&mut self, inject_fuel_check: bool) {
// [d, s, n]
if inject_fuel_check {
self.op_local_get(1); // n
self.op_i32_const(MEMORY_BYTES_PER_FUEL - 1); // upper round
self.op_i32_add();
self.op_i32_const(MEMORY_BYTES_PER_FUEL_LOG2); // 2^6=64
self.op_i32_shr_u(); // delta/64
self.op_consume_fuel_stack();
}
// emit memory copy
self.op_memory_copy();
}
/// MemoryInit opcode reads 3 elements from the stack (dst, src, len), where:
/// - dst - Memory destination of copied data
/// - src - Data source of copied data (in the passive section)
/// - len - Length of copied data
///
/// In the `passive_sections` field, we store info about all passive sections
/// that are presented in the WebAssembly binary. When a passive section is activated
/// though `memory.init` opcode, we find modified offsets in the data section
/// and put on the stack by removing previous values.
///
/// Here is the stack structure for `memory.init` call:
/// - ... some other stack elements
/// - dst
/// - src
/// - len
/// - ... call of `memory.init` happens here
///
/// Here we need to replace the `src` field with our modified, but since we don't know
/// how the stack was structured, then we can achieve it by replacing a stack element using
/// `local.set` opcode.
///
/// - dst
/// - src <-----+
/// - len |
/// - new_src --+
/// - ... call `local.set` to replace prev offset
///
/// Here we use 1 offset because we pop `new_src`, and then count from the top, `len`
/// has 0 offset and `src` has offset 1.
///
/// Before doing these ops, we must ensure that the specified length of copied data
/// doesn't exceed the original section size. We also inject GT check to make sure that
/// there is no data section overflow.
///
/// Max stack height: 2
pub fn op_memory_init_checked(
&mut self,
rewrite_offset: Option<u32>,
rewrite_length: Option<u32>,
data_segment_index: DataSegmentIdx,
inject_fuel_check: bool,
) {
// do an overflow check
if let Some(length) = rewrite_length.filter(|v| *v > 0) {
self.op_local_get(1); // n
self.op_local_get(3); // s
self.op_i32_add(); // n + s
self.op_i32_const(length);
self.op_i32_gt_s(); // n + s > length
self.op_br_if_eqz(2);
self.op_trap(TrapCode::MemoryOutOfBounds);
}
// we need to replace the offset on the stack with the new value
if let Some(offset) = rewrite_offset.filter(|v| *v > 0) {
self.op_i32_const(offset);
self.op_local_get(3);
self.op_i32_add();
self.op_local_set(2);
}
// [d, s, n]
if inject_fuel_check {
self.op_local_get(1); // n
self.op_i32_const(MEMORY_BYTES_PER_FUEL - 1); // upper round
self.op_i32_add();
self.op_i32_const(MEMORY_BYTES_PER_FUEL_LOG2); // 2^6=64
self.op_i32_shr_u(); // delta/64
self.op_consume_fuel_stack();
}
// emit memory init
self.op_memory_init(data_segment_index);
}
}