Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/src/interpret.rs
1691 views
1
//! CLI tool to interpret Cranelift IR files.
2
3
use crate::utils::iterate_files;
4
use clap::Parser;
5
use cranelift_interpreter::environment::FunctionStore;
6
use cranelift_interpreter::interpreter::{Interpreter, InterpreterState};
7
use cranelift_interpreter::step::ControlFlow;
8
use cranelift_reader::{ParseError, ParseOptions, parse_run_command, parse_test};
9
use std::path::PathBuf;
10
use std::{fs, io};
11
use thiserror::Error;
12
13
/// Interpret clif code
14
#[derive(Parser)]
15
pub struct Options {
16
/// Specify an input file to be used. Use '-' for stdin.
17
#[arg(required = true)]
18
files: Vec<PathBuf>,
19
20
/// Be more verbose
21
#[arg(short, long)]
22
verbose: bool,
23
}
24
25
/// Run files through the Cranelift interpreter, interpreting any functions with annotations.
26
pub fn run(options: &Options) -> anyhow::Result<()> {
27
let mut total = 0;
28
let mut errors = 0;
29
for file in iterate_files(&options.files) {
30
total += 1;
31
let runner = FileInterpreter::from_path(file)?;
32
match runner.run() {
33
Ok(_) => {
34
if options.verbose {
35
println!("{}", runner.path());
36
}
37
}
38
Err(e) => {
39
if options.verbose {
40
println!("{}: {}", runner.path(), e.to_string());
41
}
42
errors += 1;
43
}
44
}
45
}
46
47
if options.verbose {
48
match total {
49
0 => println!("0 files"),
50
1 => println!("1 file"),
51
n => println!("{n} files"),
52
}
53
}
54
55
match errors {
56
0 => Ok(()),
57
1 => anyhow::bail!("1 failure"),
58
n => anyhow::bail!("{} failures", n),
59
}
60
}
61
62
/// Contains CLIF code that can be executed with [FileInterpreter::run].
63
pub struct FileInterpreter {
64
path: Option<PathBuf>,
65
contents: String,
66
}
67
68
impl FileInterpreter {
69
/// Construct a file runner from a CLIF file path.
70
pub fn from_path(path: impl Into<PathBuf>) -> Result<Self, io::Error> {
71
let path = path.into();
72
log::trace!("New file runner from path: {}:", path.to_string_lossy());
73
let contents = fs::read_to_string(&path)?;
74
Ok(Self {
75
path: Some(path),
76
contents,
77
})
78
}
79
80
/// Construct a file runner from a CLIF code string. Currently only used for testing.
81
#[cfg(test)]
82
pub fn from_inline_code(contents: String) -> Self {
83
log::trace!("New file runner from inline code: {}:", &contents[..20]);
84
Self {
85
path: None,
86
contents,
87
}
88
}
89
90
/// Return the path of the file runner or `[inline code]`.
91
pub fn path(&self) -> String {
92
match self.path {
93
None => "[inline code]".to_string(),
94
Some(ref p) => p.to_string_lossy().to_string(),
95
}
96
}
97
98
/// Run the file; this searches for annotations like `; run: %fn0(42)` or
99
/// `; test: %fn0(42) == 2` and executes them, performing any test comparisons if necessary.
100
pub fn run(&self) -> Result<(), FileInterpreterFailure> {
101
// parse file
102
let test = parse_test(&self.contents, ParseOptions::default())
103
.map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))?;
104
105
// collect functions
106
let mut env = FunctionStore::default();
107
let mut commands = vec![];
108
for (func, details) in test.functions.iter() {
109
for comment in &details.comments {
110
if let Some(command) = parse_run_command(comment.text, &func.signature)
111
.map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))?
112
{
113
commands.push(command);
114
}
115
}
116
// Note: func.name may truncate the function name
117
env.add(func.name.to_string(), func);
118
}
119
120
// Run assertion commands
121
for command in commands {
122
command
123
.run(|func_name, args| {
124
// Because we have stored function names with a leading %, we need to re-add it.
125
let func_name = &format!("%{func_name}");
126
let state = InterpreterState::default().with_function_store(env.clone());
127
match Interpreter::new(state).call_by_name(func_name, args) {
128
Ok(ControlFlow::Return(results)) => Ok(results.to_vec()),
129
Ok(ControlFlow::Trap(trap)) => Err(trap.to_string()),
130
Ok(_) => panic!("Unexpected returned control flow--this is likely a bug."),
131
Err(t) => Err(t.to_string()),
132
}
133
})
134
.map_err(|s| FileInterpreterFailure::FailedExecution(s))?;
135
}
136
137
Ok(())
138
}
139
}
140
141
/// Possible sources of failure in this file.
142
#[derive(Error, Debug)]
143
pub enum FileInterpreterFailure {
144
#[error("failure reading file")]
145
Io(#[from] io::Error),
146
#[error("failure parsing file {0}: {1}")]
147
ParsingClif(String, ParseError),
148
#[error("failed to run function: {0}")]
149
FailedExecution(String),
150
}
151
152
#[cfg(test)]
153
mod test {
154
use super::*;
155
156
#[test]
157
fn nop() {
158
let code = String::from(
159
"
160
function %test() -> i8 {
161
block0:
162
nop
163
v1 = iconst.i8 -1
164
v2 = iconst.i8 42
165
return v1
166
}
167
; run: %test() == -1
168
",
169
);
170
FileInterpreter::from_inline_code(code).run().unwrap()
171
}
172
173
#[test]
174
fn filetests() {
175
run(&Options {
176
files: vec![PathBuf::from("../filetests/filetests/interpreter")],
177
verbose: true,
178
})
179
.unwrap()
180
}
181
}
182
183