Macros
Due Friday, 26 Sep 1440 ET
Setup
Requirements
- To complete this assignment, you must:
- Create a
32
directory in your271rs
repository. - This folder must be a Cargo package.
- It must leverage no other Cargo packages, namely it may not use packages for randomization.
- Create a
My responsibility
- I will provide a reference solution in Python
- The reference will differ in the following ways:
- They will not work on bits, as there isn’t great bit manipulation in Python without libraries.
- I will provide an example of a left rotate while the assignment requires a right rotate.
- The reference will differ in the following ways:
- I will provide a testing
src/main.rs
.- I will provide sample output.
Your responsibility
- You will create a solution in Rust
- It will contain 3 macros
choice
median
rotate
- a “right” rotate.- You are advised, but not required, to make left rotate.
- It will contain 3 macros
Summary
Description
- The purpose of this homework is to write four (4) bitwise macros
- Two trenanry operations
- Choice
- Median, also called Majority
- And two rotations
- Right, which sees use, and
- Left, as an academic exercise
- Two trenanry operations
- These will see use in the next assignment, SHA256
- They are logically and historically interesting within cryptography
- There is no graceful way, to my knowledge, to describe these on bits in Python
- I will provide pseudo code over tuples of integers.
- I provide conversion functions from strings.
- I am aware of plenty non-graceful ways, but
- If you want to show me one you like send me a DM
Macros
- Rust macros are described in Rust Book 20.5
- Basically, a macro replaces text in a
.rs
file before the code is compiled. - We will right
max!
- Suppose \(\exists\) the following in
lib
:- Do note - I wrote this multiline.
cargo fmt
pushed it onto one line!
The #[macro_export] annotation indicates that this macro should be made available…
- The body is
match
-like, but over text not computation.- There is only one case here:
( $x:expr, $y:expr )
- This will be more complex than the cases you need.
- There is only one case here:
- Macro variables are
$
prefixed, to differentiate from code variables.- You can accept a fixed number, like 2, as
( %x:expr, %y:expr )
- Where:
$x
and$y
are variables that may be used within the macro body.- That match to Rust expressions (like
val
or7+2
) via:expr
- You can accept a fixed number, like 2, as
- We can then use the macro:
src/main.rs
- We see the expected result… but how?
Internals
rustc
comes along and seesmax!
rustc
looks intopackage_name_likely_macros
and looks forsrc/lib.rs
rustc
looks formacro_rules! max
rustc
matches the pattern( $x:expr, $y:expr )
to(1, 2)
- As
1
and2
are valid Rust expressions.
- As
rustc
replacesmax!(1,2)
withif $x > $y { $x } else { $y }
rustc
replaces$x
with1
and$y
with2
- Then, at long last,
rustc
compiles the code and creates an executable.
Quick Exercises
Sizeof
- You will notice something very annoying soon, if not already.
- You probably don’t know how big any thing is in a macro!
- They don’t follow the Rust convention of everything having a type!
- That’s why you can
println!
ordbg!
orvec!
any type!
- You just may need to know how many bits are in what you’re working with:
- Read more
- This gives size in bytes, not bits, but just may come in handy!
- In a future assignment, you will be working on
u64
.
$ cat src/main.rs
fn main() {
dbg!(size_of_val(&1_u64));
}
$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/hamming`
[src/main.rs:2:5] size_of_val(&1_u64) = 8
- We must
&
prefix to allow the function to work on borrowed values, when checking the size of e.g. vectors.
Choice
- Here I provide Pythonic boolean choice and bitwise choice.
- You will need Rust bitwise choice.
- Choice is sometimes also referred to as the “ternary operator”
- Most famously in .js
- This is… potentially confusing.
- It is a ternary operator.
- The Python operator is non-standard and intentionally ugly.
macros.py
# ref: choice := (e and f) xor ((not e) and g)
# src: https://en.wikipedia.org/wiki/SHA-2
# We just tell Python the ints are bools
# We just use "!=" as xor
def _choice(e:bool, f:bool, g:bool) -> bool:
return int(f if e else g)
# return int((e and f) != ((not e) and g))
import itertools
tester = list(itertools.product([0, 1],repeat=3))
print(" === Boolean Choice === ")
[print('_choice'+str(test), '->', _choice(*test)) for test in tester]
arrays = (tuple(zip(*tester)))
def choice(e:tuple[bool], f:tuple[bool], g:tuple[bool]) -> tuple[bool]:
return tuple(_choice(_e, _f, _g) for _e, _f, _g in zip(e,f,g))
# This was ugly
# print('choice'+str(arrays), '->', choice(*arrays))
# pretty print
bitstr = lambda bits : "".join([str(b) for b in bits])
bsstrs = lambda arrs : str(tuple(bitstr(bits) for bits in arrs))
print(" === Bitwise Choice === ")
print('choice'+bsstrs(arrays), '->', "'"+bitstr(choice(*arrays))+"'")
- You can run it yourself, but here is the output for reference.
Median
Or “Majority”
- Here I provide Pythonic boolean median and bitwise median.
- You will need Rust bitwise median.
- I will take it as given you know what a median and majority are.
- Logicially equivalent with \(n = 3\)
- The following code is appended to “macros.py”
macros.pyimport numpy as np def _median(e:bool, f:bool, g:bool) -> bool: return int(np.median([e,f,g])) print(" === Boolean Median === ") [print('_median'+str(test), '->', _median(*test)) for test in tester] def median(e:tuple[bool], f:tuple[bool], g:tuple[bool]) -> tuple[bool]: return tuple(_median(_e, _f, _g) for _e, _f, _g in zip(e,f,g)) print(" === Bitwise Median === ") print('median'+bsstrs(arrays), '->', "'"+bitstr(median(*arrays))+"'")
- You can run it yourself, but here is the output for reference.
Rotate
Sometimes “Rotright and Rotleft”
- Virtually identical to rotation ciphers.
____________________________
[ ABCDEFGHIJKLMNOPQRSTUVWXYZ ] # alphabet
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
____________________________
[ DEFGHIJKLMNOPQRSTUVWXYZABC ] # rotate(3)
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
____________________________
[ XYZABCDEFGHIJKLMNOPQRSTUVW ] # rotate(-3)
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- We understand this as:
- Take an array and,
- Take a numerical value…
- of less than the length of the array.
- Maintain all elements of the array, but
- Increase their index by the numerical value, and
- Indices greater than array length wrap around…
- Using modulo array length.
- We apply this same idea to the notion of boolean arrays.
- A unsigned int is a boolean array of some length.
- It is possible to determine these lengths.
- Here is a Python rotleft on boolean arrays of size 8.
macros.py
- You can run it yourself, but here is the output for reference.
=== Bitwise Rotleft ===
rotleft(00101101,0) -> 00101101
rotleft(00101101,1) -> 01011010
rotleft(00101101,2) -> 10110100
rotleft(00101101,3) -> 01101001
rotleft(00101101,4) -> 11010010
rotleft(00101101,5) -> 10100101
rotleft(00101101,6) -> 01001011
rotleft(00101101,7) -> 10010110
rotleft(00101101,8) -> 00101101
- We note that this forms a “backward” or “leftward” rotate.
- This is a non-standard rotate, often called
lotate
orrotleft
- A future assignment will use a “forward” or “rightward” rotate.
- This is a non-standard rotate, often called
- Without showing code, it would look like this.
=== Bitwise Rotate ===
rotate(00101101,0) -> 00101101
rotate(00101101,1) -> 10010110
rotate(00101101,2) -> 01001011
rotate(00101101,3) -> 10100101
rotate(00101101,4) -> 11010010
rotate(00101101,5) -> 01101001
rotate(00101101,6) -> 10110100
rotate(00101101,7) -> 01011010
rotate(00101101,8) -> 00101101
Overflow
- Rust appears to implement some shifting operations a bit differently that other languages.
- Read this
- Look at this:
- That
r
inshr
is forright
.
Tester
src/main.rs
fn main() {
/* Various Variables*/
let a : [u64; 4] = [0x1111111111110000, 0x1111000011001100, 0x1100110010101010, 0x0123456789ABCDEF];
println!("*Rotates use a decimal shift value, but print in hexadecimal:\n");
println!("choice(\n{:016X},\n{:016X},\n{:016X}) = \n--------\n{:016X}\n\n", a[0], a[1], a[2], macros::choice!(a[0], a[1], a[2]));
println!("median(\n{:016X},\n{:016X},\n{:016X}) = \n--------\n{:016X}\n\n", a[0], a[1], a[2], macros::median!(a[0], a[1], a[2]));
println!("*Rotates use a decimal shift value, but print in hexadecimal:\n");
println!("rotate!(\n{:016X}, 04) = \n--------\n{:016X}\n\n", a[3], macros::rotate!(a[3], 4));
println!("rotate!(\n{:016X}, 08) = \n--------\n{:016X}\n\n", a[3], macros::rotate!(a[3], 8));
println!("rotate!(\n{:016X}, 12) = \n--------\n{:016X}\n\n", a[3], macros::rotate!(a[3], 12));
println!("rotate!(\n{:016X}, 02) = \n--------\n{:016X}\n\n", 0x1000, macros::rotate!(0x1000_u64, 2));
println!("rotate!(\n{:016X}, 30) = \n--------\n{:016X}\n\n", 0x1000, macros::rotate!(0x1000_u64, 30));
}
- Sample output:
*Rotates use a decimal shift value, but print in hexadecimal:
choice(
1111111111110000,
1111000011001100,
1100110010101010) =
--------
1111000011001010
median(
1111111111110000,
1111000011001100,
1100110010101010) =
--------
1111110011101000
*Rotates use a decimal shift value, but print in hexadecimal:
rotate!(
0123456789ABCDEF, 04) =
--------
F0123456789ABCDE
rotate!(
0123456789ABCDEF, 08) =
--------
EF0123456789ABCD
rotate!(
0123456789ABCDEF, 12) =
--------
DEF0123456789ABC
rotate!(
0000000000001000, 02) =
--------
0000000000000400
rotate!(
0000000000001000, 30) =
--------
0000400000000000