-
Notifications
You must be signed in to change notification settings - Fork 25
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
proc macro to generate memory-efficient gate #695
Conversation
One comment, just based on your commentary (not the patch, I am out of energy for the week). impl StepNarrow<StepA> for Compact {
fn narrow(&self, step: &StepA) -> Self {
Self(match (self.0, step.as_ref()) {
(0, "A1") => 1, This would be better as: impl StepNarrow<StepA> for Compact {
fn narrow(&self, step: &StepA) -> Self {
Self(match (self.0, step) { And, to confirm. Do you have annotations for enum arms that take integers, like this: enum StepC {
DoX,
DoY(u32),
} It would be OK if you only have code for |
StepB::Bar => "bar", | ||
StepB::Baz => "baz", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StepB::Bar => "bar", | |
StepB::Baz => "baz", | |
Self::Bar => "bar", | |
Self::Baz => "baz", |
ipa-macros/src/derive_gate/mod.rs
Outdated
// config file, and generate the steps file accordingly. However, the value could change after the steps | ||
// file is generated, so we need to make sure that the steps file is always up to date somehow. | ||
// | ||
// 6. Root step |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need a separate type and proc macro for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alternatively we could come up with a oneshot
run of IPA that initializes TestWorld
in a way that does not allow to use it in more than one run
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was more about whether we need to dynamically generate a string representation of root step(s), i.e., does query ID "X" must be generated by Compact
gate's AsRef<str>
implementation? If we do not require the root step in a real world scenario, we don't need to worry about this. Descriptive
gate has id
field which embeds protocol/run-0
as the root step, so I was wondering if that was also needed for Compact
gate.
We don't have macros or annotations for a macro to deal with this yet, but I cover these cases in #695. That said, we should create an improved version of the macro with the annotation for this. I could do this together with what we discussed about using |
@@ -0,0 +1,23 @@ | |||
[package] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are a lot of benefits using workspaces, we should consider using it at some point: https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html
ipa-macros/src/derive_gate/mod.rs
Outdated
}; | ||
use syn::{parse_macro_input, punctuated::Punctuated, DeriveInput, PathArguments, PathSegment}; | ||
|
||
// TODOs: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lots of good stuff here, lets make sure we have a tracking issue for those. Likely this code will not go through a lot of changes so we may just forget about them
ipa-macros/src/derive_gate/mod.rs
Outdated
// - We could use https://doc.rust-lang.org/proc_macro/struct.Span.html#method.source_file, but it's in nightly. | ||
// - This will also allow us to generate state transition match statements from int to int rather than str to int. | ||
// | ||
// 2. Enable compile-time detection of steps that are `narrow`ed but never trigger `send` in the protocol. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: steps need to be inert in order to be "invisible". If they are created to generate shared randomness, that's fine
ipa-macros/src/derive_gate/mod.rs
Outdated
// a case where there are child steps branching off from the conditional step. | ||
// (b) Forcibly execute conditional steps when `step-trace` feature is on to generate the steps file. | ||
// Not sure what is the best way to do this, or whether this will work though. | ||
// - Currently, we are doing (a-1). It could let unexpected state transitions happen without panicking, but |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if it is a great option, but we could annotate such steps with a macro that must enumerate all the valid "parent" steps for conditionally executed one. Then use that information to generate "narrow"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we could attribute such a step with #[conditional_step]
(name TBD). This assumes we have a solution to #704.
ipa-macros/src/derive_gate/mod.rs
Outdated
// we could apply the same conditions used to call `multiply` using these contexts to where we `narrow` | ||
// the context. The code becomes a bit messy, but it's doable. | ||
// - crate::protocol::context::UpgradeStep | ||
// This steps is executed in both malicious and semi-honest contexts, but the `narrow` call in semi-honest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible to solve it with putting the step narrow inside the upgrade
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is basically what I did. Instead of the template implementation in UpgradedContext
trait, I created specific implementations of upgrade
for semi-honest and malicious.
ipa-macros/src/tree.rs
Outdated
if let Some(my_parent_ref) = my_parent_weak.upgrade() { | ||
Some(Node { | ||
data: my_parent_ref, | ||
}) | ||
} else { | ||
None | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be wrong but I think this should work
if let Some(my_parent_ref) = my_parent_weak.upgrade() { | |
Some(Node { | |
data: my_parent_ref, | |
}) | |
} else { | |
None | |
} | |
my_parent_weak.upgrade().clone() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference here is that my_parent_ref
encloses InnerNode
but the return type is Node
. I could implement From<> but this is the only place we would use that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed!
new_child | ||
} | ||
|
||
pub fn get_children(&self) -> Vec<Node<T>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returning borrowed data may be nicer, but not sure if it is easy to do.
pub fn get_children(&self) -> impl Iterator<Item = Node<T>>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me take this as a homework. Not sure how to return an iterator while keeping borrow().iter()
.
ipa-macros/src/tree.rs
Outdated
} | ||
|
||
impl<T> Node<T> { | ||
pub fn new(value: T) -> Node<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub fn new(value: T) -> Node<T> { | |
pub fn new(value: T)-> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
|
||
// We want to make sure that once a parent node is dropped, all its children are dropped as well. | ||
// To do that, we use `Rc<>` for children, and `Weak<>` for parent. (`Rc` because it's single-threaded) | ||
type InnerNodeRef<T> = Rc<InnerNode<T>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it feels that it should be reversed, that is: Rc<RefCell<Node<T>>>
for strong reference and Weak<RefCell<Node<T>>>
for weak
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's a bit confusing...
The type of Node::data::parent
is
Rc<RefCell<Weak<InnerNode<T>>>>
and Node::data::children
is
Rc<RefCell<Vec<Rc<InnerNode<T>>>>>
,
but Node::data::value
is
Rc<T>
.
We can keep the value
immutable while able to mutate parent/child edges via add_child
.
Rc::clone(&self.data) | ||
} | ||
|
||
pub fn add_child(&self, value: T) -> Node<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could make it a method on Rc and make a Node
constructor that takes Weak
as an argument for parent (pass Weak::new() for none).
pub fn add_child(self: Rc<Self>, value: T) -> Self {
let child = Rc::new(Node::new(value, self.downgrade());
self.children.push(Rc::clone(child));
child
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would make this part of the code a bit more readable. I think hiding all Rc/Weak stuff from the users of Node
is a pretty nice feature though.
Procedural macro to implement the
Step
andStepNarrow
traits and generate a memory-efficient gate.The goal is to generate a state transition graph and the corresponding
StepNarrow
implementations for the IPA protocol. This macro assumes that a complete IPA steps file exists in the repo at the location specified bySTEPS_FILE
. The steps file can be generated by runningcollect_steps.py
.The steps file contains a list of narrowed steps, where each line represents a hierarchy of narrowed steps delimited by "/". For example, the following lines represent a hierarchy of narrowed steps ("=> x" parts are not in the file. They are added in here as comments to indicate their potential IDs when the macro parses the file):
From these lines, we want to generate
StepNarrow
implementations for each step.Currently, this derive macro assumes it annotates the
Compact
struct defined insrc/protocol/step/compact.rs
. TheCompact
struct is a wrapper around au16
value that represents the current state of the IPA protocol.In the future, we might change the macro to annotate each step in the IPA protocol. The macro will then generate both
Descriptive
andCompact
implementations for the step. However, that kind of derive macro needs to be able to analyze the fully qualified module path of the step at runtime. This is because there are many locally-definedStep
enums in IPA, and we need to disambiguate them. There are two ways to solve this. One is to use a feature to resolve the fully qualified module path of a Step enum/struct, but the feature is in nightly as of June 2023. Another way is to define globally unique step names.