-
Notifications
You must be signed in to change notification settings - Fork 49
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
Generate DecodeFrom()
methods for Golang
#71
Conversation
It seems there is still a minor problem (decoding an empty value instead of nil in some cases). I will look into that soon |
OK, that's fixed, but verify-range seems to be failing https://app.circleci.com/pipelines/github/stellar/go/9834/workflows/44ff1f80-b9fe-4787-8783-711d6cd0b8e8/jobs/57566 |
@bartekn could you please take a quick look at the failure above?
I don't understand why stellar core fails and there is no context. |
OK, I think I know what's happening. I shouldn't be using There was a bug in the captive core backend hiding the error stellar/go#4066 |
OK, the verify-range problem is fixed. I am going to run verify-range in the last 100k ledgers 🤞 |
Note for reviewers, please check the code generated at stellar/go#4064 before reviewing the code here. In fact, it's vital to review the code generated before reviewing this PR itself. |
verify range succeeded! 🥳
|
966e0f4
to
968464a
Compare
out.puts " for i := 0; i < len(#{var}); i++ {" | ||
element_var = "#{var}[i]" | ||
optional_within = type.is_a?(AST::Identifier) && type.resolved_type.sub_type == :optional | ||
if optional_within |
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.
Intuitively, it seems like this optional block (and above) should be able to be handled with a recursive data-type, like a Option<Foo>
type-thing, and having a render_optional
method... 🤔 but this is probably good enough for now.
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.
I think I'm thinking something like:
def render_decode_from_body(out, var, type, declared_variables:, self_encode:)
# ...
if type.sub_type == :optional
return render_optional(out, var, type, type, declared_variables: declared_variables, self_encode: self_encode) do |inner_type|
# Recurse into the unwrapped inner type
render_decode_from_body(out, var, inner_type, declared_variables: declared_variables, self_encode: self_encode)
end
end
# ...
end
def render_optional(out, var, type, declared_variables:, self_encode:)
render_variable_declaration(out, " ", 'b', "bool", declared_variables: declared_variables)
out.puts " b, nTmp, err = d.DecodeBool()"
out.puts tail
out.puts " #{var} = nil"
out.puts " if b {"
out.puts " #{var} = new(#{name type.resolved_type.declaration.type})"
# TODO: Unwrap the inner_type (without the optional wrapper) here
yield inner_type
out.puts " }"
end
But, actually you could do a similar block thing for arrays as well, I think...
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.
Either way, probably outside the scope of this PR.
also, unrelated to this PR, but wdyt of, instead of doing |
I think it's probably a good idea. we in fact do run gofmt when generating the code in stellar-go. However, that requires a bigger refactoring |
@bartekn PTAL |
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.
Awesome!
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.
Three requests (❗) otherwise LGTM. I definitely don't understand all of it, unfortunately xdrgen is pretty complex, but the areas of the generated code I sampled in stellar/go#4064 look good.
I also left some non-blocking comments: a suggestion (💡), a thought for future improvements that may or may not be beneficial (💭), and a question about something I don't understand (❔).
🔍 I see above we ran a verify range test with 100k ledgers. That's only 5 days worth of ledgers. Given the larger surface area of xdr decoding and its risk for Horizon, and that Horizon supports reingestion of much larger periods of time, should we perform a verify range of a much larger range of time before merging? cc @bartekn
lib/xdrgen/generators/go.rb
Outdated
@@ -580,8 +815,16 @@ def render_top_matter(out) | |||
xdrType() | |||
} | |||
|
|||
type xdrDecodable interface { |
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 you add for every type a cast to an unnamed variable? We do that with all the other interfaces in here to make sure all the types satisfy the interfaces. i.e.:
var _ xdrDecodeable = (*#{name})(nil)
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 you name this interface decoderFrom
? That's the pattern used for similar named interfaces in the stdlib. e.g. The interface that contains the ReadFrom
function is ReaderFrom.
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.
Will do
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.
+1 As a follow up we can remove the xdrType thing I added too, it's superfluous and wasn't the best approach I took. I'm happy to do the clean up for EncodeTo, but if you're going to do it 👍🏻 sgtm.
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.
If you could cleanup the encodeto part (adding errors, making it consistent with this code etc ...). I would appreciate it.
if err != nil { | ||
return n, err | ||
} | ||
if _, ok := #{private_name type}Map[v]; !ok { |
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.
💭 Given that most of our enums start at zero and are sequential, and given that use of the map will now be in the hot path, I wonder if for enums that we detect have that property, if we could simply replace this with a if v >= len(...Map) { // error }
? We don't have to do this, and we definitely don't have to do this in this PR, but maybe something to evaluate if there is a saving here over hitting the map.
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.
Good idea, However:
There are many enums with negative values. Some examples are: BucketEntryType
, CreateAccountResultCode
, PaymentResultCode
, PathPaymentStrictReceiveResultCode
....
Not even a range is generally a possibility since the XDR definition can contain scattered values.
We could infer a range where available and use that, but I think it is an overoptimization (without further proof of a bottleneck) and definitely out of the scope of this PR.
Let's later see if further profiling reveals map access as a problem.
render_variable_declaration(out, " ", 'b', "bool", declared_variables: declared_variables) | ||
out.puts " b, nTmp, err = d.DecodeBool()" | ||
out.puts tail | ||
out.puts " #{var} = nil" |
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.
🎉 I really like that this sets the value of every field.
968464a
to
548f250
Compare
This is the decoding counterpart of #65
There is a dramatic performance improvement: ~13x speedup and and ~11x allocation reduction.
Check stellar/go#4064 for details on the speedup and the generated code.
TODO:
EncodeTo()
-related code consistent with the changes in this PR. I intentionally didn't touchEncodeTo
-code here in order to avoid noise (otherwise it will be more difficult to read the generated code at xdr: update using xdrgen#71 go#4064 ). I will update them in a separate PR.