This change substitutes the string based emission mechanism with
cranelift-codegen's x64 backend.
This change _does not_:
* Introduce new functionality in terms of supported instructions.
* Change the semantics of the assembler/macroassembler in terms of the logic to
emit instructions.
The most notable differences between this change and the previous version are:
* Handling of shared flags and ISA-specific flags, which for now are left with
the default value.
* Simplification of instruction emission per operand size: previously the
assembler defined different methods depending on the operand size (e.g. `mov`
for 64 bits, and `movl` for 32 bits). This change updates such approach so that
each assembler method takes an operand size as a parameter, reducing duplication
and making the code more concise and easier to integrate with the x64's `Inst` enum.
* Introduction of a disassembler for testing purposes.
As of this change, Winch generates the following code for the following test
programs:
```wat
(module
(export "main" (func $main))
(func $main (result i32)
(i32.const 10)
(i32.const 20)
i32.add
))
```
```asm
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: b8 0a 00 00 00 mov eax, 0xa
9: 83 c0 14 add eax, 0x14
c: 5d pop rbp
d: c3 ret
```
```wat
(module
(export "main" (func $main))
(func $main (result i32)
(local $foo i32)
(local $bar i32)
(i32.const 10)
(local.set $foo)
(i32.const 20)
(local.set $bar)
(local.get $foo)
(local.get $bar)
i32.add
))
```
```asm
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: 48 83 ec 08 sub rsp, 8
8: 48 c7 04 24 00 00 00 00 mov qword ptr [rsp], 0
10: b8 0a 00 00 00 mov eax, 0xa
15: 89 44 24 04 mov dword ptr [rsp + 4], eax
19: b8 14 00 00 00 mov eax, 0x14
1e: 89 04 24 mov dword ptr [rsp], eax
21: 8b 04 24 mov eax, dword ptr [rsp]
24: 8b 4c 24 04 mov ecx, dword ptr [rsp + 4]
28: 01 c1 add ecx, eax
2a: 48 89 c8 mov rax, rcx
2d: 48 83 c4 08 add rsp, 8
31: 5d pop rbp
32: c3 ret
```
```wat
(module
(export "main" (func $main))
(func $main (param i32) (param i32) (result i32)
(local.get 0)
(local.get 1)
i32.add
))
```
```asm
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: 48 83 ec 08 sub rsp, 8
8: 89 7c 24 04 mov dword ptr [rsp + 4], edi
c: 89 34 24 mov dword ptr [rsp], esi
f: 8b 04 24 mov eax, dword ptr [rsp]
12: 8b 4c 24 04 mov ecx, dword ptr [rsp + 4]
16: 01 c1 add ecx, eax
18: 48 89 c8 mov rax, rcx
1b: 48 83 c4 08 add rsp, 8
1f: 5d pop rbp
20: c3 ret
```
78 lines
2.4 KiB
Rust
78 lines
2.4 KiB
Rust
//! Winch CLI tool, meant mostly for testing purposes.
|
|
//!
|
|
//! Reads Wasm in binary/text format and compiles them
|
|
//! to any of the supported architectures using Winch.
|
|
|
|
use anyhow::{Context, Result};
|
|
use clap::Parser;
|
|
use cranelift_codegen::settings;
|
|
use std::{fs, path::PathBuf, str::FromStr};
|
|
use target_lexicon::Triple;
|
|
use wasmtime_environ::{
|
|
wasmparser::{types::Types, Parser as WasmParser, Validator},
|
|
DefinedFuncIndex, FunctionBodyData, Module, ModuleEnvironment, Tunables,
|
|
};
|
|
use winch_codegen::{lookup, TargetIsa};
|
|
|
|
mod disasm;
|
|
|
|
#[derive(Parser, Debug)]
|
|
struct Options {
|
|
/// The input file.
|
|
input: PathBuf,
|
|
|
|
/// The target architecture.
|
|
#[clap(long = "target")]
|
|
target: String,
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
let opt = Options::from_args();
|
|
let bytes = fs::read(&opt.input)
|
|
.with_context(|| format!("Failed to read input file {}", opt.input.display()))?;
|
|
let bytes = wat::parse_bytes(&bytes)?;
|
|
let triple = Triple::from_str(&opt.target)?;
|
|
let shared_flags = settings::Flags::new(settings::builder());
|
|
let isa_builder = lookup(triple)?;
|
|
let isa = isa_builder.build(shared_flags)?;
|
|
let mut validator = Validator::new();
|
|
let parser = WasmParser::new(0);
|
|
let mut types = Default::default();
|
|
let tunables = Tunables::default();
|
|
let mut translation = ModuleEnvironment::new(&tunables, &mut validator, &mut types)
|
|
.translate(parser, &bytes)
|
|
.context("Failed to translate WebAssembly module")?;
|
|
let _ = types.finish();
|
|
|
|
let body_inputs = std::mem::take(&mut translation.function_body_inputs);
|
|
let module = &translation.module;
|
|
let types = translation.get_types();
|
|
|
|
body_inputs
|
|
.into_iter()
|
|
.try_for_each(|func| compile(&*isa, module, types, func))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn compile(
|
|
isa: &dyn TargetIsa,
|
|
module: &Module,
|
|
types: &Types,
|
|
f: (DefinedFuncIndex, FunctionBodyData<'_>),
|
|
) -> Result<()> {
|
|
let index = module.func_index(f.0);
|
|
let sig = types
|
|
.func_type_at(index.as_u32())
|
|
.expect(&format!("function type at index {:?}", index.as_u32()));
|
|
let FunctionBodyData { body, validator } = f.1;
|
|
let validator = validator.into_validator(Default::default());
|
|
let buffer = isa
|
|
.compile_function(&sig, &body, validator)
|
|
.expect("Couldn't compile function");
|
|
|
|
disasm::print(buffer.data(), isa)?;
|
|
|
|
Ok(())
|
|
}
|