-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Add SemIR support for virtual functions #4272
Add SemIR support for virtual functions #4272
Conversation
Yes, I think there should be tests. Note though, I've commented where this is affecting tests -- those should be cleaned up.
This is fine: code which passes check is not currently assumed to compile correctly (and could crash in lowering). We're not fuzzing lowering right now because of this.
I think these belong on Function, not EntityWithParamsBase. You're correct not all functions can be virtual, but not all entities are functions and I think that's more significant. I would be reticent to put entity-specific features on EntityWithParamsBase. Note I don't think we've done any explicit bitpacking in SemIR (at least, not on these entities). It may be something we want to adjust, but right now we're prioritizing readability over saving bytes. |
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.
Seems like you have some changes to make based on @jonmeow 's comments, so I'm pausing my review.
toolchain/check/handle_function.cpp
Outdated
if (!is_virtual && | ||
introducer.modifier_set.HasAnyOf(KeywordModifierSet::Method)) { |
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 this only works because the method modifiers are mutually exclusive? Otherwise this would accept virtual
along with an unsupported method modifier. Maybe worth a comment.
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.
Oh, right - thanks!
Now that I've implemented @jonmeow's suggestion of also handling abstract and impl, there's nothing left to diagnose here so I've removed this block entirely.
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 the addition to EntityWithParamsBase suitable? of course not all functions can be virtual, so it's a wasted bit at the moment for all those cases (though it's free, since it's bitpacked - but as we want to add more bits in there it might not be a scalable solution)?
I think these belong on Function, not EntityWithParamsBase. You're correct not all functions can be virtual, but not all entities are functions and I think that's more significant. I would be reticent to put entity-specific features on EntityWithParamsBase.
Ah, right, makes sense - done that.
Note I don't think we've done any explicit bitpacking in SemIR (at least, not on these entities). It may be something we want to adjust, but right now we're prioritizing readability over saving bytes.
Ah, sure - I've removed the bit field stuff then.
toolchain/check/handle_function.cpp
Outdated
if (!is_virtual && | ||
introducer.modifier_set.HasAnyOf(KeywordModifierSet::Method)) { |
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.
Oh, right - thanks!
Now that I've implemented @jonmeow's suggestion of also handling abstract and impl, there's nothing left to diagnose here so I've removed this block entirely.
Oh, I had one other question - is mapping these language features directly to SemIR the right choice? Or should we consider a more semantic description of these properties? (like could all 3 of these properties map to "virtual"? I guess then we wouldn't know the difference between abstract and a virtual function that's defined externally (if that's even possible in Carbon? I assume so) - could have two properties, then, virtual and abstract (& perhaps all abstract things are also marked virtual))? I guess probably not - but thought it was worth asking/understanding better what semantic representation SemIR is going for. |
toolchain/sem_ir/function.h
Outdated
// Is this function declaration is virtual - which is to say, it includes an | ||
// implementation, but can be overridden in a derived class. | ||
bool is_virtual; | ||
|
||
// Is this function declaration is abstract - having no implementation, and | ||
// must be implemented in a concrete derived class. | ||
bool is_abstract; | ||
|
||
// Is this function declaration an implementation - overriding an abstract or | ||
// virtual function declared in a base class. | ||
bool is_impl; |
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.
These are mutually exclusive, had you considered an enum?
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 - open to haggling over naming, of course.
// | ||
// AUTOUPDATE | ||
// TIP: To test this file alone, run: | ||
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/dynamic_function_modifiers.carbon |
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.
Maybe "virtual_function_modifiers" (or even just "virtual_modifiers")? Although these are one way to get dynamic dispatch, https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#virtual-methods calls these "virtual methods" and "virtual override keywords"
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.
Ah, sure - done.
toolchain/check/testdata/class/dynamic_function_modifiers.carbon
Outdated
Show resolved
Hide resolved
toolchain/check/testdata/class/dynamic_function_modifiers.carbon
Outdated
Show resolved
Hide resolved
This is essentially what I'd expect, with the Regarding mapping all to "virtual", each of the keywords implies different semantics, so they do need to be tracked separately.
For example, it is invalid to declare a final class that inherits from a class without providing |
Sorry, I'm not quite following here - you mean: Good to go with what we have now, not try to overdesign it without experience. Or you'd encourage doing more implementation work/prototyping before a patch like this goes in, so we're heading in a more informed direction?
Sorry, I should've used more words: I figured those invariants would be checked in But I guess it's probably convenient to have all 3 markers for lowering, if not technically necessary (C++ being a proof of concept that it's not /necessary/ and can be derived from a rougher description). Being able to tell which functions introduce new vtable slots (virtual), those without implementations (so vtable slot can be initialized with null/skip the relocation/symbol reference), and overrides. |
… and impl properties as well
Turned out the access modifier testing wasn't failing by itstelf, so remove the `fail_` prefix from the remaining test coverage, and rename it to be more specific about what's covered (access modifiers).
…now they're all covered/implemented in Check/SemIR
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
ffc0e43
to
c25da7d
Compare
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.
Basically LG here, but one small style request (and one mulling comment, just mentioning the thought here)
Feel free to merge after adjusting the struct order
toolchain/sem_ir/function.h
Outdated
@@ -23,6 +23,12 @@ struct FunctionFields { | |||
// always present if the function has a declared return type. | |||
InstId return_storage_id; | |||
|
|||
enum class VirtualModifier { None, Virtual, Abstract, Impl }; |
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.
Sorry, can you move this to the top (for declaration order styles, types/enums come before instance members)
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.
Ah, sure - done in a2d80a6
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Virtual)) { | ||
virtual_modifier = SemIR::FunctionFields::VirtualModifier::Virtual; | ||
} | ||
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Abstract)) { | ||
virtual_modifier = SemIR::FunctionFields::VirtualModifier::Abstract; | ||
} | ||
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Impl)) { | ||
virtual_modifier = SemIR::FunctionFields::VirtualModifier::Impl; | ||
} |
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.
Note, not suggesting a change here, but thinking about this...
Compare what you're doing here with the class
code:
https://github.com/carbon-language/carbon-lang/blob/trunk/toolchain/check/handle_class.cpp#L203-L208
Maybe we should have a helper or something on KeywordModifierSet
to help switch modifiers to an appropriate enum? It feels like something that we could maybe do with a variadic template...
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.
Oh, yeah - I can look into 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.
Sent #4290
Aside: The handle_class.cpp
case uses a non-scoped enum (enum
, not enum class
) and references the entities via the derived type (Class::Enumerator
rather than ClassFields::Enumerator
) - I guess the latter's clearly an improvement over the code I've written here for the function case, but should we standardize on the enum or enum class here? In this particular case an unscoped enum seems OK?
…4290) (based on #4272 (comment)) Could haggle over the name "ToEnum" probably avoids the debate over "enumeration" (the type being returned) v "enumerator" (the value being returned) --------- Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
I guess this technically would also allow code to pass check that hasn't before, and that isn't covered by tests (since it's masked by other failures in the tests that already test this functionality) - should I add another test/add some code to a valid test case?
Also, this'll miscompile in lowering, since there's no support there yet - should I do anything about that to make lowering fail in some way? Or is it acceptable that some things just silently mis-lower? (I could add a currently-miscompiling test case too, to demonstrate this? (not sure if the autogenerated tests leave space for comments that would explain that the currently-tested behavior is incorrect?))
Is the addition to EntityWithParamsBase suitable? of course not all functions can be virtual, so it's a wasted bit at the moment for all those cases (though it's free, since it's bitpacked - but as we want to add more bits in there it might not be a scalable solution)?