Skip to content

Commit 99622ab

Browse files
authored
asm: prepare docs for publishing (#10178)
This change is motivated by staring at the output of `cargo doc` for the `cranelift-assembler-x64` crate. In order to write a sensible top-level example, I felt it was best to refactor how we construct the instructions: this removes the `build` module and adds conventional `<inst_name>::new` functions to each instruction. Also, a generated `From` implementation makes it easier to convert to an `Inst`. This change has other doc-related refactorings and tweaks, but should not change any functionality.
1 parent a7ba893 commit 99622ab

File tree

6 files changed

+133
-67
lines changed

6 files changed

+133
-67
lines changed

cranelift/assembler-x64/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Like `cranelift-codegen`, using this assembler starts with `enum Inst`. For
1010
convenience, a `main.rs` script prints the path to this generated code:
1111

1212
```console
13-
$ cat $(cargo run)
14-
#[derive(arbitrary::Arbitrary, Debug)]
15-
pub enum Inst {
13+
$ cat $(cargo run) | head
14+
...
15+
pub enum Inst<R:Registers> {
1616
andb_i(andb_i),
1717
andw_i(andw_i),
1818
andl_i(andl_i),

cranelift/assembler-x64/meta/src/generate.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ pub fn rust_assembler(f: &mut Formatter, insts: &[dsl::Inst]) {
1818
generate_inst_encode_impl(f, insts);
1919
generate_inst_visit_impl(f, insts);
2020
generate_inst_features_impl(f, insts);
21-
generate_inst_constructor_impl(f, insts);
2221

2322
// Generate per-instruction structs.
2423
f.empty_line();
2524
for inst in insts {
2625
inst.generate_struct(f);
2726
inst.generate_struct_impl(f);
2827
inst.generate_display_impl(f);
28+
inst.generate_from_impl(f);
2929
f.empty_line();
3030
}
3131

@@ -69,6 +69,7 @@ pub fn isle_definitions(f: &mut Formatter, insts: &[dsl::Inst]) {
6969

7070
/// `enum Inst { ... }`
7171
fn generate_inst_enum(f: &mut Formatter, insts: &[dsl::Inst]) {
72+
fmtln!(f, "#[doc(hidden)]");
7273
generate_derive(f);
7374
generate_derive_arbitrary_bounds(f);
7475
fmtln!(f, "pub enum Inst<R: Registers> {{");
@@ -174,15 +175,3 @@ fn generate_inst_features_impl(f: &mut Formatter, insts: &[dsl::Inst]) {
174175
});
175176
fmtln!(f, "}}");
176177
}
177-
178-
/// `pub mod build { pub fn <inst>... }`
179-
fn generate_inst_constructor_impl(f: &mut Formatter, insts: &[dsl::Inst]) {
180-
fmtln!(f, "pub mod build {{");
181-
f.indent(|f| {
182-
fmtln!(f, "use super::*;");
183-
for inst in insts {
184-
inst.generate_variant_constructor(f);
185-
}
186-
});
187-
fmtln!(f, "}}");
188-
}

cranelift/assembler-x64/meta/src/generate/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ impl dsl::Feature {
99
/// This function recreates the `Feature` struct itself in the generated
1010
/// code.
1111
pub fn generate_enum(f: &mut Formatter) {
12+
fmtln!(f, "#[doc(hidden)]");
1213
generate_derive(f);
14+
fmtln!(f, "#[derive(Copy, PartialEq)]"); // Add a couple more helpful derives.
1315
fmtln!(f, "pub enum Feature {{");
1416
f.indent(|f| {
1517
for feature in dsl::ALL_FEATURES {

cranelift/assembler-x64/meta/src/generate/inst.rs

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,25 @@ impl dsl::Inst {
5151
}
5252
}
5353

54-
// `fn <inst>(<params>) -> Inst { ... }`
55-
pub fn generate_variant_constructor(&self, f: &mut Formatter) {
56-
let variant_name = self.name();
54+
/// `impl <inst> { ... }`
55+
pub fn generate_struct_impl(&self, f: &mut Formatter) {
56+
let impl_block = self.generate_impl_block_start();
57+
let struct_name = self.struct_name_with_generic();
58+
fmtln!(f, "{impl_block} {struct_name} {{");
59+
f.indent(|f| {
60+
self.generate_new_function(f);
61+
f.empty_line();
62+
self.generate_encode_function(f);
63+
f.empty_line();
64+
self.generate_visit_function(f);
65+
f.empty_line();
66+
self.generate_features_function(f);
67+
});
68+
fmtln!(f, "}}");
69+
}
70+
71+
// `fn new(<params>) -> Self { ... }`
72+
pub fn generate_new_function(&self, f: &mut Formatter) {
5773
let params = comma_join(
5874
self.format
5975
.operands
@@ -69,29 +85,13 @@ impl dsl::Inst {
6985
);
7086

7187
fmtln!(f, "#[must_use]");
72-
fmtln!(f, "pub fn {variant_name}<R: Registers>({params}) -> Inst<R> {{");
88+
fmtln!(f, "pub fn new({params}) -> Self {{");
7389
f.indent(|f| {
74-
fmtln!(f, "Inst::{variant_name}({variant_name} {{ {args} }})",);
90+
fmtln!(f, "Self {{ {args} }}",);
7591
});
7692
fmtln!(f, "}}");
7793
}
7894

79-
/// `impl <inst> { ... }`
80-
pub fn generate_struct_impl(&self, f: &mut Formatter) {
81-
let impl_block = self.generate_impl_block_start();
82-
let struct_name = self.struct_name_with_generic();
83-
fmtln!(f, "{impl_block} {struct_name} {{");
84-
85-
f.indent_push();
86-
self.generate_encode_function(f);
87-
f.empty_line();
88-
self.generate_visit_function(f);
89-
f.empty_line();
90-
self.generate_features_function(f);
91-
f.indent_pop();
92-
fmtln!(f, "}}");
93-
}
94-
9595
/// `fn encode(&self, ...) { ... }`
9696
fn generate_encode_function(&self, f: &mut Formatter) {
9797
let off = if self.format.uses_memory().is_some() {
@@ -191,23 +191,35 @@ impl dsl::Inst {
191191
let impl_block = self.generate_impl_block_start();
192192
let struct_name = self.struct_name_with_generic();
193193
fmtln!(f, "{impl_block} std::fmt::Display for {struct_name} {{");
194-
f.indent_push();
195-
fmtln!(f, "fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{");
196-
197-
f.indent_push();
198-
for op in &self.format.operands {
199-
let location = op.location;
200-
let to_string = location.generate_to_string(op.extension);
201-
fmtln!(f, "let {location} = {to_string};");
202-
}
203-
204-
let inst_name = &self.mnemonic;
205-
let ordered_ops = self.format.generate_att_style_operands();
206-
fmtln!(f, "write!(f, \"{inst_name} {ordered_ops}\")");
207-
f.indent_pop();
194+
f.indent(|f| {
195+
fmtln!(f, "fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{");
196+
f.indent(|f| {
197+
for op in &self.format.operands {
198+
let location = op.location;
199+
let to_string = location.generate_to_string(op.extension);
200+
fmtln!(f, "let {location} = {to_string};");
201+
}
202+
let inst_name = &self.mnemonic;
203+
let ordered_ops = self.format.generate_att_style_operands();
204+
fmtln!(f, "write!(f, \"{inst_name} {ordered_ops}\")");
205+
});
206+
fmtln!(f, "}}");
207+
});
208208
fmtln!(f, "}}");
209+
}
209210

210-
f.indent_pop();
211+
/// `impl From<struct> for Inst { ... }`
212+
pub fn generate_from_impl(&self, f: &mut Formatter) {
213+
let struct_name_r = self.struct_name_with_generic();
214+
let variant_name = self.name();
215+
fmtln!(f, "impl<R: Registers> From<{struct_name_r}> for Inst<R> {{");
216+
f.indent(|f| {
217+
fmtln!(f, "fn from(inst: {struct_name_r}) -> Self {{",);
218+
f.indent(|f| {
219+
fmtln!(f, "Self::{variant_name}(inst)");
220+
});
221+
fmtln!(f, "}}");
222+
});
211223
fmtln!(f, "}}");
212224
}
213225

@@ -245,7 +257,7 @@ impl dsl::Inst {
245257
// TODO: parameterize CraneliftRegisters?
246258
fmtln!(f, "fn x64_{struct_name}(&mut self, {params}) -> {ret_ty} {{",);
247259
f.indent(|f| {
248-
fmtln!(f, "let inst = cranelift_assembler_x64::build::{struct_name}({args});");
260+
fmtln!(f, "let inst = cranelift_assembler_x64::inst::{struct_name}::new({args}).into();");
249261
fmtln!(f, "self.lower_ctx.emit(MInst::External {{ inst }});");
250262
fmtln!(f, "{ret_val}");
251263
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! Expose all known instructions as Rust `struct`s; this is generated in
2+
//! `build.rs`.
3+
//!
4+
//! See also: [`Inst`], an `enum` containing all these instructions.
5+
6+
use crate::api::{AsReg, CodeSink, KnownOffsetTable, RegisterVisitor, Registers};
7+
use crate::imm::{Extension, Imm16, Imm32, Imm8};
8+
use crate::mem::{emit_modrm_sib_disp, GprMem};
9+
use crate::reg::{self, Gpr, Size};
10+
use crate::rex::{self, emit_simm, RexFlags};
11+
12+
// Include code generated by the `meta` crate.
13+
include!(concat!(env!("OUT_DIR"), "/assembler.rs"));
14+
15+
/// Helper function to make code generation simpler.
16+
fn emit_modrm(buffer: &mut impl CodeSink, enc_reg_g: u8, rm_e: u8) {
17+
let modrm = rex::encode_modrm(0b11, enc_reg_g & 7, rm_e & 7);
18+
buffer.put1(modrm);
19+
}

cranelift/assembler-x64/src/lib.rs

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,47 @@
1-
//! A Cranelift-specific x64 assembler; see the `README.md` for more
2-
//! information.
1+
//! A Cranelift-specific x64 assembler.
2+
//!
3+
//! All instructions known to this assembler are listed in the [`inst`] module.
4+
//! The [`Inst`] enumeration contains a variant for each, allowing matching over
5+
//! all these instructions. All of this is parameterized by a [`Registers`]
6+
//! trait, allowing users of this assembler to plug in their own register types.
7+
//!
8+
//! ```
9+
//! # use cranelift_assembler_x64::{Feature, Imm8, inst, Inst, Registers};
10+
//! // Tell the assembler the type of registers we're using; we can always
11+
//! // encode a HW register as a `u8` (e.g., `eax = 0`).
12+
//! pub struct Regs;
13+
//! impl Registers for Regs {
14+
//! type ReadGpr = u8;
15+
//! type ReadWriteGpr = u8;
16+
//! }
17+
//!
18+
//! // Then, build one of the `AND` instructions; this one operates on an
19+
//! // implicit `AL` register with an immediate. We can collect a sequence of
20+
//! // instructions by converting to the `Inst` type.
21+
//! let and = inst::andb_i::new(Imm8::new(0b10101010));
22+
//! let seq: Vec<Inst<Regs>> = vec![and.into()];
23+
//!
24+
//! // Now we can encode this sequence into a code buffer, checking that each
25+
//! // instruction is valid in 64-bit mode.
26+
//! let mut buffer = vec![];
27+
//! let offsets = vec![];
28+
//! for inst in seq {
29+
//! if inst.features().contains(&Feature::_64b) {
30+
//! inst.encode(&mut buffer, &offsets);
31+
//! }
32+
//! }
33+
//! assert_eq!(buffer, vec![0x24, 0b10101010]);
34+
//! ```
35+
//!
36+
//! With an [`Inst`], we can encode the instruction into a code buffer; see the
37+
//! [example](Inst).
338
439
// All of the generated struct names use snake case.
540
#![allow(non_camel_case_types)]
641

742
mod api;
843
mod imm;
44+
pub mod inst;
945
pub mod isle;
1046
mod mem;
1147
mod reg;
@@ -14,25 +50,33 @@ mod rex;
1450
#[cfg(feature = "arbitrary")]
1551
mod arbitrary_impls;
1652

53+
/// An assembly instruction; contains all instructions known to the assembler.
54+
///
55+
/// This wraps all [`inst`] structures into a single enumeration for collecting
56+
/// instructions.
57+
#[doc(inline)]
58+
// This re-exports, and documents, a module that is more convenient to use at
59+
// the library top-level.
60+
pub use inst::Inst;
61+
62+
/// A CPU feature.
63+
///
64+
/// This is generated from the `dsl::Feature` enumeration defined in the `meta`
65+
/// crate (i.e., an exact replica). It describes the CPUID features required by
66+
/// an instruction; see [`Inst::features`].
67+
#[doc(inline)]
68+
// Like `Inst` above, a convenient re-export.
69+
pub use inst::Feature;
70+
1771
pub use api::{
18-
AsReg, CodeSink, Constant, KnownOffsetTable, Label, RegisterVisitor, Registers, TrapCode,
72+
AsReg, CodeSink, Constant, KnownOffset, KnownOffsetTable, Label, RegisterVisitor, Registers,
73+
TrapCode,
1974
};
2075
pub use imm::{Extension, Imm16, Imm32, Imm8, Simm32, Simm32PlusKnownOffset};
2176
pub use mem::{Amode, DeferredTarget, GprMem, Scale};
2277
pub use reg::{Gpr, NonRspGpr, Size};
2378
pub use rex::RexFlags;
2479

25-
// Include code generated by the `meta` crate.
26-
use mem::emit_modrm_sib_disp;
27-
use rex::emit_simm;
28-
include!(concat!(env!("OUT_DIR"), "/assembler.rs"));
29-
30-
/// Helper function to make code generation simpler.
31-
fn emit_modrm(buffer: &mut impl CodeSink, enc_reg_g: u8, rm_e: u8) {
32-
let modrm = rex::encode_modrm(0b11, enc_reg_g & 7, rm_e & 7);
33-
buffer.put1(modrm);
34-
}
35-
3680
/// List the files generated to create this assembler.
3781
pub fn generated_files() -> Vec<std::path::PathBuf> {
3882
env!("ASSEMBLER_BUILT_FILES")

0 commit comments

Comments
 (0)