-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use procedural macro to implement g!
and s!
macros
#6
Comments
Why do these even need to be macros? Couldn't you just implement |
There's a few reasons:
|
3, 4 the types should be copy anyway, they are just a bag of bits 1,2 maybe there is a way to construct a lazy expression using operators and then have a method on it to compute the result but it's probably not worth it Thanks for explaining! |
Not quite. I think allowing copy means that they may be moved around in memory a lot. I wanted to leave the door open to zeroing out all
That's could be a good alternative if we can come up with the right design. The downside I think will be is that is the expression optmization will have to be done at runtime. |
It doesn't. Values can be moved around in memory a lot even if the type doesn't implement
Done right, the compiler should optimize-out unreachable branches. |
Are you claiming there is no point in trying to zero out memory after it's dropped? If this was indeed the case then I would indeed implement |
Yes, at least until there's direct compiler support for it. Note also that the only way that memory could be exploited is if the program has a memory bug. Which should be unlikely in Rust already and the effort is probably better spent on preventing the remaining memory bugs.
I'm not sure what you expect here. I don't have any links but I logically concluded it from following facts:
From 1 and 2 we can conclude there's no way to guarantee zeroing in Rust, or even C! 4 means our memory-safe program won't leak memory, 5 means OS won't leak our memory to other users. 6 means mainly that even though my argument in 5 implies "OS can't leak a page to different user but could to different process under the same user" it doesn't matter. IDK if you heard, recent announcement from Google claimed that they had so far zero memory vulnerabilities in a bunch of Rust code. So it seems exploitability of non-zeroed memory is very close to zero in practice. |
hey @Kixunil happy new year :) I am becoming more and more convinced of your position. I think we can make everything
So my current thinking is to make everything copy regardless of secrecy but to still using macros for arithmetic expressions. |
Happy new year to you too! I don't think it would involve enum or a #[derive(Copy, Clone)]
pub struct Add<Op1, Op2> where Op1: Expr, Op2: Expr<Output=Op1::Output>, Op1::Output: AactualAdd {
operand1: Op1,
operand2: Op2,
}
#[derive(Copy, Clone)]
pub struct Mul<Op1, Op2> where Op1: Expr, Op2: Expr, Op1::Output: ActualMul<Op2::Output>, Op2::Output: ActualMul<Op1::Output> {
operand1: Op1,
operand2: Op2,
}
#[derive(Copy, Clone)]
pub struct Neg<Op> {
operand: Op,
}
pub trait ActualAdd {
fn actual_add(self, rhs: Self) -> Self;
}
pub trait ActualMul<Rhs> {
type Output;
fn actual_mul(self, rhs: Rhs) -> Self::Output;
}
pub trait Expr: Copy {
type Output: ActualAdd;
// If Self is Mul these should be the operands, uninhabited otherwise
type MulOp1;
type MulOp2;
fn eval(self) -> Self::Output;
/// Returns Ok if Self is Mul
// have more such functions for other optimizations
#[inline]
fn to_mul(self) -> Option<Mul<Self::MulOp1, Self::MulOp2>> {
None
}
}
impl<Op1: Expr, Op2: Expr<Output=Op1::Output>> Expr for Add<Op1, Op2> {
type Output = Op1::Output;
type MulOp1 = Infallible;
type MulOp2 = Infallible;
fn eval(self) -> Self::Output {
match (self.operand1.to_mul(), self.operand2.to_mul()) {
(Some(op1), Some(op2)) => {
let op1_1 = op1.operand1.eval();
let op1_2 = op1.operand2.eval();
let op2_1 = op2.operand1.eval();
let op2_2 = op2.operand2.eval();
if op1_1 == op2_1 {
op1_1.actual_mul(op1_2.actual_add(op2_2))
} else if op1_1 == op2_2 {
op1_1.actual_mul(op1_2.actual_add(op2_1))
} else if op1_2 == op2_1 {
op1_2.actual_mul(op1_1.actual_add(op2_2))
} else if op1_2 == op2_2 {
op1_2.actual_mul(op1_1.actual_add(op2_1))
} else {
op1_1.actual_mul(op1_2).actual_add(op2_1.actual_mul(op_2_2))
}
},
_ => self.operand1.eval().actual_add(self.operand2.eval()),
}
}
}
impl<Rhs> Add<Rhs> for Scalar where Rhs: Expr<Output=Scalar> {
type Output = Add<Scalar, T>;
fn add(self, rhs: Rhs) -> Self::Output {
Add {
operand1: self,
operand2: rhs,
}
}
}
// impl the remaining ops the same way.
impl Expr for Scalar {
type Output = Self;
type MulOp1 = Infallible;
type MulOp2 = Infallible;
fn eval(self) -> Self::Output { self }
}
impl Expr for Point {
type Output = Self;
type MulOp1 = Infallible;
type MulOp2 = Infallible;
fn eval(self) -> Self::Output { self }
} Will likely need a bit more tweaking of types&such but should work. It pretty much bypasses specialization (which would be much nicer). |
Fascinating! I see a trait instead of an enum approach might work too. I would have two traits fwiw |
I made the |
To implement
g!
ands!
I wrote a compiler in rust's macro language: https://github.com/LLFourn/secp256kfun/blob/master/secp256kfun/src/macros.rs#L85. It turns out you can write procedural macros to write expression macros (not just derives): https://doc.rust-lang.org/reference/procedural-macros.htmlRewrite these macros use procedural macros so they are easier to understand. See if this allows for better testing of the macros as well.
The text was updated successfully, but these errors were encountered: