Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Added initial Rust codegen-meta implementation. #403

Merged
merged 26 commits into from
Jul 19, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c05527a
Added initial Rust codegen-meta implementation.
data-pup May 28, 2018
4e3205a
Replace 'Cretonne' in comments.
data-pup Jul 17, 2018
565516b
Prevent iterator overflow.
data-pup Jul 17, 2018
4944d8c
1.25.0 compatibility changes.
data-pup Jul 17, 2018
69f34e0
Implemented debug traits for type variants.
data-pup Jul 17, 2018
61378a8
Added consistent comments.
data-pup Jul 17, 2018
194ec5e
Cleaned up a loop via clippy fix.
data-pup Jul 17, 2018
2f42b13
Added new license to codegen-meta Cargo.toml
data-pup Jul 17, 2018
9d90806
Edited lane type iterator `next` method.
data-pup Jul 17, 2018
8842b28
Removed functions that are not needed in Rust, and edited desc.
data-pup Jul 17, 2018
b9a1835
Debug trait derived for valuetype.
data-pup Jul 17, 2018
ebed1c9
Added comments for iterator types in the base types submodule.
data-pup Jul 17, 2018
7c39c33
Numbering is now handled in the cdsl/types.rs file.
data-pup Jul 17, 2018
bba565b
Moved type number logic into cdsl/types.
data-pup Jul 17, 2018
f842b07
Repeating the lane change cleanup.
data-pup Jul 17, 2018
a62db2e
Removed codegen-meta crate from codegen deps.
data-pup Jul 17, 2018
07e2919
Typo fix.
data-pup Jul 17, 2018
1a8f003
Addressing a patch note.
data-pup Jul 17, 2018
1d121c5
Addressing patch note.
data-pup Jul 17, 2018
2c1232d
Lowercase in vector names.
data-pup Jul 17, 2018
d8e9423
Fixing a comment bug.
data-pup Jul 17, 2018
73b6da6
Added a copy of the license file.
data-pup Jul 17, 2018
352a859
Formatting changes.
data-pup Jul 17, 2018
551b419
Cleaned up the vector type numbering.
data-pup Jul 17, 2018
9534bed
1.25 compatibility.
data-pup Jul 18, 2018
46d4bb1
Fixed pattern match arms.
data-pup Jul 19, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lib/codegen-meta/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "cranelift-codegen-meta"
authors = ["The Cranelift Project Developers"]
version = "0.15.0"
description = "DSL for cranelift-codegen code generator library"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a minor point, I'm thinking I'd like to move away from "DSL" terminology in general. It feels like part of the problem with the Python code is the DSL approach, which leans a little toward being its own specialized world and a little away from being idiomatic Python code. This makes working within the system more streamlined, but it also makes debugging the system and changing how the system works harder.

So how about "Metaprogram for cranelift-codegen code generator library"?

license = "Apache-2.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cranelift's license is now "Apache-2.0 WITH LLVM-exception".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, please add a copy of the LICENSE file under lib/codegen-meta (this is a recent change).

documentation = "https://cranelift.readthedocs.io/"
repository = "https://github.com/CraneStation/cranelift"
keywords = ["compile", "compiler", "jit"]

[dependencies]
# It is a goal of the cranelift-codegen crate to have minimal external dependencies.
# Please don't add any unless they are essential to the task of creating binary
# machine code.

[badges]
maintenance = { status = "experimental" }
travis-ci = { repository = "CraneStation/cranelift" }
3 changes: 3 additions & 0 deletions lib/codegen-meta/src/base/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! Definitions for the base Cranelift language.

pub mod types;
247 changes: 247 additions & 0 deletions lib/codegen-meta/src/base/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
//! This module predefines all the Cranelift scalar types.

// Numbering scheme for value types:
//
// 0: Void
// 0x01-0x6f: Special types
// 0x70-0x7f: Lane types
// 0x80-0xff: Vector types
//
// Vector types are encoded with the lane type in the low 4 bits and log2(lanes)
// in the high 4 bits, giving a range of 2-256 lanes.
static LANE_BASE: u8 = 0x70;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Bool {
/// 1-bit bool.
B1 = 1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this code depend on the enum fields having explicit values?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lane types such as Bool or Int use enums with explicit discriminators, so that the bits value can be set without a match statement. One example of where this happens is in the impl From<base_types::Bool> for LaneType block in cdsl/types.rs :)

/// 8-bit bool.
B8 = 8,
/// 16-bit bool.
B16 = 16,
/// 32-bit bool.
B32 = 32,
/// 64-bit bool.
B64 = 64,
}

impl Bool {
/// Get the number of a boolean variant.
pub fn number(self) -> u8 {
let offset = match self {
Bool::B1 => 0,
Bool::B8 => 1,
Bool::B16 => 2,
Bool::B32 => 3,
Bool::B64 => 4,
};

LANE_BASE + offset
}
}

pub struct BoolIterator {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add doc comments to the iterator structs briefly explaining what they're for. Just "This provides an iterator through all supported bool types" or so.

index: u8,
}

impl BoolIterator {
pub fn new() -> Self {
Self { index: 0 }
}
}

impl Iterator for BoolIterator {
type Item = Bool;
fn next(&mut self) -> Option<Self::Item> {
let res = match self.index {
0 => Some(Bool::B1),
1 => Some(Bool::B8),
2 => Some(Bool::B16),
3 => Some(Bool::B32),
4 => Some(Bool::B64),
_ => return None,
};
self.index += 1;
res
}
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Int {
/// 8-bit int.
I8 = 8,
/// 16-bit int.
I16 = 16,
/// 32-bit int.
I32 = 32,
/// 64-bit int.
I64 = 64,
}

impl Int {
/// Get the number of an integer variant.
pub fn number(self) -> u8 {
let offset = 5 + match self {
Int::I8 => 0,
Int::I16 => 1,
Int::I32 => 2,
Int::I64 => 3,
};

LANE_BASE + offset
}
}

pub struct IntIterator {
index: u8,
}

impl IntIterator {
pub fn new() -> Self {
Self { index: 0 }
}
}

impl Iterator for IntIterator {
type Item = Int;
fn next(&mut self) -> Option<Self::Item> {
let res = match self.index {
0 => Some(Int::I8),
1 => Some(Int::I16),
2 => Some(Int::I32),
3 => Some(Int::I64),
_ => return None,
};
self.index += 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about release mode overflow? (Call .next() usize::MAX times and you get I8 again) You could replace the _ => None, with _ => return None, to prevent this.

res
}
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Float {
F32 = 32,
F64 = 64,
}

impl Float {
/// Get the number of a float variant.
pub fn number(self) -> u8 {
let offset = 9 + match self {
Float::F32 => 0,
Float::F64 => 1,
};

LANE_BASE + offset
}
}

/// Iterator through the variants of the Float enum.
pub struct FloatIterator {
index: u8,
}

impl FloatIterator {
pub fn new() -> Self {
Self { index: 0 }
}
}

impl Iterator for FloatIterator {
type Item = Float;
fn next(&mut self) -> Option<Self::Item> {
let res = match self.index {
0 => Some(Float::F32),
1 => Some(Float::F64),
_ => return None,
};
self.index += 1;
res
}
}

/// A type representing CPU flags.
///
/// Flags can't be stored in memory.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Flag {
/// CPU flags from an integer comparison.
IFlags,
/// CPU flags from a floating point comparison.
FFlags,
}

impl Flag {
/// Get the number of a flag variant.
pub fn number(self) -> u8 {
match self {
Flag::IFlags => 1,
Flag::FFlags => 2,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IFlags and FFlags are 0 and 1 below. Why are they 1 and 2 here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number methods are responsible for the value inside of the Type(..) call in the generated file, like so:

pub const IFLAGS: Type = Type(0x1);

I'm not totally content with this approach to be honest, and had been thinking about moving these up into the cdsl/types.rs file instead, so it's a little clearer what's happening. Do you think that sounds like a good idea?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I didn't notice this before, but a lot of this logic is in cdsl/types.py in the Python version, so yeah, that makes sense.

}
}
}

/// Iterator through the variants of the Flag enum.
pub struct FlagIterator {
index: u8,
}

impl FlagIterator {
pub fn new() -> Self {
Self { index: 0 }
}
}

impl Iterator for FlagIterator {
type Item = Flag;
fn next(&mut self) -> Option<Self::Item> {
let res = match self.index {
0 => Some(Flag::IFlags),
1 => Some(Flag::FFlags),
_ => return None,
};
self.index += 1;
res
}
}

#[cfg(test)]
mod iter_tests {
use super::*;

#[test]
fn bool_iter_works() {
let mut bool_iter = BoolIterator::new();
assert_eq!(bool_iter.next(), Some(Bool::B1));
assert_eq!(bool_iter.next(), Some(Bool::B8));
assert_eq!(bool_iter.next(), Some(Bool::B16));
assert_eq!(bool_iter.next(), Some(Bool::B32));
assert_eq!(bool_iter.next(), Some(Bool::B64));
assert_eq!(bool_iter.next(), None);
}

#[test]
fn int_iter_works() {
let mut int_iter = IntIterator::new();
assert_eq!(int_iter.next(), Some(Int::I8));
assert_eq!(int_iter.next(), Some(Int::I16));
assert_eq!(int_iter.next(), Some(Int::I32));
assert_eq!(int_iter.next(), Some(Int::I64));
assert_eq!(int_iter.next(), None);
}

#[test]
fn float_iter_works() {
let mut float_iter = FloatIterator::new();
assert_eq!(float_iter.next(), Some(Float::F32));
assert_eq!(float_iter.next(), Some(Float::F64));
assert_eq!(float_iter.next(), None);
}

#[test]
fn flag_iter_works() {
let mut flag_iter = FlagIterator::new();
assert_eq!(flag_iter.next(), Some(Flag::IFlags));
assert_eq!(flag_iter.next(), Some(Flag::FFlags));
assert_eq!(flag_iter.next(), None);
}
}
77 changes: 77 additions & 0 deletions lib/codegen-meta/src/cdsl/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Cranelift DSL classes.
//!
//! This module defines the classes that are used to define Cranelift
//! instructions and other entitties.

pub mod types;

/// Convert the string `s` to CamelCase.
fn _camel_case(s: &str) -> String {
let mut output_chars = String::with_capacity(s.len());

let mut capitalize = true;
for curr_char in s.chars() {
if curr_char == '_' {
capitalize = true;
} else {
if capitalize {
output_chars.extend(curr_char.to_uppercase());
} else {
output_chars.push(curr_char);
}
capitalize = false;
}
}

output_chars
}

/// Check if `x` is a power of two.
fn _is_power_of_two(x: u8) -> bool {
x > 0 && x & (x - 1) == 0
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know the Python code has this, but for Rust we should use the standard library's is_power_of_two.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, should I just go ahead and remove both this and the next_power_of_two functions from this file?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


/// Compute the next power of two that is greater than `x`.
fn _next_power_of_two(x: u8) -> u8 {
let mut s = 1;
let mut res = x;
while res & (res + 1) != 0 {
res |= res >> s;
s *= 2;
}

res + 1
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for next_power_of_two.


#[cfg(test)]
mod tests {
use super::_camel_case as camel_case;
use super::_is_power_of_two as is_power_of_two;
use super::_next_power_of_two as next_power_of_two;

#[test]
fn camel_case_works() {
assert_eq!(camel_case("x"), "X");
assert_eq!(camel_case("camel_case"), "CamelCase");
}

#[test]
fn is_power_of_two_works() {
assert_eq!(is_power_of_two(1), true);
assert_eq!(is_power_of_two(2), true);
assert_eq!(is_power_of_two(4), true);
assert_eq!(is_power_of_two(8), true);

assert_eq!(is_power_of_two(3), false);
assert_eq!(is_power_of_two(7), false);
}

#[test]
fn next_power_of_two_works() {
assert_eq!(next_power_of_two(0), 1);
assert_eq!(next_power_of_two(1), 2);
assert_eq!(next_power_of_two(2), 4);
assert_eq!(next_power_of_two(3), 4);
assert_eq!(next_power_of_two(4), 8);
}
}
Loading