Skip to content

Commit 31bac3e

Browse files
authored
Merge pull request #2450 from bytecodealliance/cfallin/fix-wasm-reachable
Fix Wasm translator bug: end of toplevel frame is branched-to only for fallthrough returns.
2 parents 93c1993 + 34d9931 commit 31bac3e

File tree

3 files changed

+182
-5
lines changed

3 files changed

+182
-5
lines changed

cranelift/wasm/src/code_translator.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,9 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
514514
Operator::Return => {
515515
let (return_count, br_destination) = {
516516
let frame = &mut state.control_stack[0];
517-
frame.set_branched_to_exit();
517+
if environ.return_mode() == ReturnMode::FallthroughReturn {
518+
frame.set_branched_to_exit();
519+
}
518520
let return_count = frame.num_return_values();
519521
(return_count, frame.br_destination())
520522
};

cranelift/wasm/src/environ/dummy.rs

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::environ::{
1010
WasmFuncType, WasmResult,
1111
};
1212
use crate::func_translator::FuncTranslator;
13+
use crate::state::FuncTranslationState;
1314
use crate::translation_utils::{
1415
DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex,
1516
Table, TableIndex, TypeIndex,
@@ -25,7 +26,7 @@ use cranelift_frontend::FunctionBuilder;
2526
use std::boxed::Box;
2627
use std::string::String;
2728
use std::vec::Vec;
28-
use wasmparser::{FuncValidator, FunctionBody, ValidatorResources, WasmFeatures};
29+
use wasmparser::{FuncValidator, FunctionBody, Operator, ValidatorResources, WasmFeatures};
2930

3031
/// Compute a `ir::ExternalName` for a given wasm function index.
3132
fn get_func_name(func_index: FuncIndex) -> ir::ExternalName {
@@ -111,6 +112,31 @@ impl DummyModuleInfo {
111112
}
112113
}
113114

115+
/// State for tracking and checking reachability at each operator. Used for unit testing with the
116+
/// `DummyEnvironment`.
117+
#[derive(Clone)]
118+
pub struct ExpectedReachability {
119+
/// Before- and after-reachability
120+
reachability: Vec<(bool, bool)>,
121+
before_idx: usize,
122+
after_idx: usize,
123+
}
124+
125+
impl ExpectedReachability {
126+
fn check_before(&mut self, reachable: bool) {
127+
assert_eq!(reachable, self.reachability[self.before_idx].0);
128+
self.before_idx += 1;
129+
}
130+
fn check_after(&mut self, reachable: bool) {
131+
assert_eq!(reachable, self.reachability[self.after_idx].1);
132+
self.after_idx += 1;
133+
}
134+
fn check_end(&self) {
135+
assert_eq!(self.before_idx, self.reachability.len());
136+
assert_eq!(self.after_idx, self.reachability.len());
137+
}
138+
}
139+
114140
/// This `ModuleEnvironment` implementation is a "naïve" one, doing essentially nothing and
115141
/// emitting placeholders when forced to. Don't try to execute code translated for this
116142
/// environment, essentially here for translation debug purposes.
@@ -135,6 +161,9 @@ pub struct DummyEnvironment {
135161

136162
/// Function names.
137163
function_names: SecondaryMap<FuncIndex, String>,
164+
165+
/// Expected reachability data (before/after for each op) to assert. This is used for testing.
166+
expected_reachability: Option<ExpectedReachability>,
138167
}
139168

140169
impl DummyEnvironment {
@@ -148,13 +177,18 @@ impl DummyEnvironment {
148177
debug_info,
149178
module_name: None,
150179
function_names: SecondaryMap::new(),
180+
expected_reachability: None,
151181
}
152182
}
153183

154184
/// Return a `DummyFuncEnvironment` for translating functions within this
155185
/// `DummyEnvironment`.
156186
pub fn func_env(&self) -> DummyFuncEnvironment {
157-
DummyFuncEnvironment::new(&self.info, self.return_mode)
187+
DummyFuncEnvironment::new(
188+
&self.info,
189+
self.return_mode,
190+
self.expected_reachability.clone(),
191+
)
158192
}
159193

160194
fn get_func_type(&self, func_index: FuncIndex) -> TypeIndex {
@@ -171,20 +205,39 @@ impl DummyEnvironment {
171205
pub fn get_func_name(&self, func_index: FuncIndex) -> Option<&str> {
172206
self.function_names.get(func_index).map(String::as_ref)
173207
}
208+
209+
/// Test reachability bits before and after every opcode during translation, as provided by the
210+
/// `FuncTranslationState`. This is generally used only for unit tests. This is applied to
211+
/// every function in the module (so is likely only useful for test modules with one function).
212+
pub fn test_expected_reachability(&mut self, reachability: Vec<(bool, bool)>) {
213+
self.expected_reachability = Some(ExpectedReachability {
214+
reachability,
215+
before_idx: 0,
216+
after_idx: 0,
217+
});
218+
}
174219
}
175220

176221
/// The `FuncEnvironment` implementation for use by the `DummyEnvironment`.
177222
pub struct DummyFuncEnvironment<'dummy_environment> {
178223
pub mod_info: &'dummy_environment DummyModuleInfo,
179224

180225
return_mode: ReturnMode,
226+
227+
/// Expected reachability data (before/after for each op) to assert. This is used for testing.
228+
expected_reachability: Option<ExpectedReachability>,
181229
}
182230

183231
impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> {
184-
pub fn new(mod_info: &'dummy_environment DummyModuleInfo, return_mode: ReturnMode) -> Self {
232+
pub fn new(
233+
mod_info: &'dummy_environment DummyModuleInfo,
234+
return_mode: ReturnMode,
235+
expected_reachability: Option<ExpectedReachability>,
236+
) -> Self {
185237
Self {
186238
mod_info,
187239
return_mode,
240+
expected_reachability,
188241
}
189242
}
190243

@@ -307,6 +360,41 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
307360
}))
308361
}
309362

363+
fn before_translate_operator(
364+
&mut self,
365+
_op: &Operator,
366+
_builder: &mut FunctionBuilder,
367+
state: &FuncTranslationState,
368+
) -> WasmResult<()> {
369+
if let Some(ref mut r) = &mut self.expected_reachability {
370+
r.check_before(state.reachable());
371+
}
372+
Ok(())
373+
}
374+
375+
fn after_translate_operator(
376+
&mut self,
377+
_op: &Operator,
378+
_builder: &mut FunctionBuilder,
379+
state: &FuncTranslationState,
380+
) -> WasmResult<()> {
381+
if let Some(ref mut r) = &mut self.expected_reachability {
382+
r.check_after(state.reachable());
383+
}
384+
Ok(())
385+
}
386+
387+
fn after_translate_function(
388+
&mut self,
389+
_builder: &mut FunctionBuilder,
390+
_state: &FuncTranslationState,
391+
) -> WasmResult<()> {
392+
if let Some(ref mut r) = &mut self.expected_reachability {
393+
r.check_end();
394+
}
395+
Ok(())
396+
}
397+
310398
fn translate_call_indirect(
311399
&mut self,
312400
mut pos: FuncCursor,
@@ -746,7 +834,11 @@ impl<'data> ModuleEnvironment<'data> for DummyEnvironment {
746834
self.func_bytecode_sizes
747835
.push(body.get_binary_reader().bytes_remaining());
748836
let func = {
749-
let mut func_environ = DummyFuncEnvironment::new(&self.info, self.return_mode);
837+
let mut func_environ = DummyFuncEnvironment::new(
838+
&self.info,
839+
self.return_mode,
840+
self.expected_reachability.clone(),
841+
);
750842
let func_index =
751843
FuncIndex::new(self.get_num_func_imports() + self.info.function_bodies.len());
752844
let name = get_func_name(func_index);

cranelift/wasm/tests/wasm_testsuite.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,86 @@ fn handle_module(data: Vec<u8>, flags: &Flags, return_mode: ReturnMode) {
9494
.unwrap();
9595
}
9696
}
97+
98+
#[test]
99+
fn reachability_is_correct() {
100+
let tests = vec![
101+
(
102+
ReturnMode::NormalReturns,
103+
r#"
104+
(module (func (param i32)
105+
(loop
106+
(block
107+
local.get 0
108+
br_if 0
109+
br 1))))"#,
110+
vec![
111+
(true, true), // Loop
112+
(true, true), // Block
113+
(true, true), // LocalGet
114+
(true, true), // BrIf
115+
(true, false), // Br
116+
(false, true), // End
117+
(true, true), // End
118+
(true, true), // End
119+
],
120+
),
121+
(
122+
ReturnMode::NormalReturns,
123+
r#"
124+
(module (func (param i32)
125+
(loop
126+
(block
127+
br 1
128+
nop))))"#,
129+
vec![
130+
(true, true), // Loop
131+
(true, true), // Block
132+
(true, false), // Br
133+
(false, false), // Nop
134+
(false, false), // Nop
135+
(false, false), // Nop
136+
(false, false), // End
137+
],
138+
),
139+
(
140+
ReturnMode::NormalReturns,
141+
r#"
142+
(module (func (param i32) (result i32)
143+
i32.const 1
144+
return
145+
i32.const 42))"#,
146+
vec![
147+
(true, true), // I32Const
148+
(true, false), // Return
149+
(false, false), // I32Const
150+
(false, false), // End
151+
],
152+
),
153+
(
154+
ReturnMode::FallthroughReturn,
155+
r#"
156+
(module (func (param i32) (result i32)
157+
i32.const 1
158+
return
159+
i32.const 42))"#,
160+
vec![
161+
(true, true), // I32Const
162+
(true, false), // Return
163+
(false, false), // I32Const
164+
(false, true), // End
165+
],
166+
),
167+
];
168+
169+
for (return_mode, wat, expected_reachability) in tests {
170+
println!("testing wat:\n{}", wat);
171+
let flags = Flags::new(settings::builder());
172+
let triple = triple!("riscv64");
173+
let isa = isa::lookup(triple).unwrap().finish(flags.clone());
174+
let mut env = DummyEnvironment::new(isa.frontend_config(), return_mode, false);
175+
env.test_expected_reachability(expected_reachability);
176+
let data = wat::parse_str(wat).unwrap();
177+
translate_module(data.as_ref(), &mut env).unwrap();
178+
}
179+
}

0 commit comments

Comments
 (0)