-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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
eof: new contract creation #15512
base: develop
Are you sure you want to change the base?
eof: new contract creation #15512
Conversation
Thank you for your contribution to the Solidity compiler! A team member will follow up shortly. If you haven't read our contributing guidelines and our review checklist before, please do it now, this makes the reviewing process and accepting your contribution smoother. If you have any questions or need our help, feel free to post them in the PR or talk to us directly on the #solidity-dev channel on Matrix. |
f8a9ae7
to
947c5dd
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.
I only skimmed through the PR for now. Here's some initial feedback.
My main question would be whether it wouldn't make more sense to deal with loadimmutable
/setimmutable
in a separate PR first. It's related dataloadn
and looks like creation depends on immutables to some extent.
case EofCreate: | ||
{ | ||
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::EOFCREATE)); | ||
ret.bytecode.push_back(static_cast<uint8_t>(item.data())); | ||
break; | ||
} | ||
case ReturnContract: | ||
{ | ||
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::RETURNCONTRACT)); | ||
ret.bytecode.push_back(static_cast<uint8_t>(item.data())); | ||
break; | ||
} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
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.
Also probably worth considering whether we should maybe create a general assembly item for operations with immediates like I mentioned in #15456 (comment). Though I guess we'd need more than one to accommodate different numbers of immediates and their sizes?
I'm not sure about this and it's also something we can refactor later, but I just wanted to put the general idea for discussion.
libevmasm/AssemblyItem.h
Outdated
/// Loads 32 bytes from static auxiliary data of EOF data section. The offset does *not* have to be always from the beginning | ||
/// of the data EOF section. More details here: https://github.com/ipsilon/eof/blob/main/spec/eof.md#data-section-lifecycle | ||
AuxDataLoadN, | ||
EofCreate, /// Creates new contract using subcointainer as initcode |
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.
EofCreate, /// Creates new contract using subcointainer as initcode | |
EOFCreate, /// Creates new contract using subcontainer as initcode |
t("library_address", IRNames::libraryAddressImmutable()); | ||
else | ||
// TODO: We assume that libraries do not have immutable variables. The address is stored in 0 offset of | ||
// auxiliary data section. Is it correct? |
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 is a correct assumption. Libraries cannot have state variables and this immutable for address is the only one I'm aware of.
I think that even if it's not correct, I we'll easily find out soon enough because we'll have to deal with all the loadimmutable
calls in the generator anyway so no need to worry too much.
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.
BTW, I think it might be best to deal with the immutable-related builtins first (loadimmutable
, setimmutable
). I see several options:
- remove them from the dialect when EOF is enabled.
- implement them on EOF using
auxdataload()
. - implement them on EOF, but make auxdataload()`'s 0 offset start after them.
Going with (2) or (3) would actually allow you not to care about it here at all. You'd just keep using the same immutable. You'd instead have to adjust the mechanism for allocating immutables to put them in aux data section.
I'm not sure whether (2) or (3) is preferable though.
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.
Ok. Option 1 is implemented here but I will investigate other options and we can decide which is the best.
6567616
to
82930c3
Compare
feeb58b
to
75122e0
Compare
This comment was marked as resolved.
This comment was marked as resolved.
7bde2af
to
e1840a3
Compare
a200d15
to
107b86e
Compare
107b86e
to
2bb05a2
Compare
libevmasm/Assembly.cpp
Outdated
solAssert(item.data() <= std::numeric_limits<uint8_t>::max(), | ||
"Invalid EofCreate/ReturnContract index value."); |
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.
Shouldn't this be checked against the number of subcontainers instead?
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.
Also:
solAssert(item.data() <= std::numeric_limits<uint8_t>::max(), | |
"Invalid EofCreate/ReturnContract index value."); | |
solAssert( | |
item.data() <= std::numeric_limits<uint8_t>::max(), | |
"Invalid subcontainer index." | |
); |
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
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.
EOFCREATE
should be added to readWriteOperations()
, isDeterministic()
and memory()
too.
RETURNCONTRACT
should be added to memory()
. Maybe also otherState()
since it results in the code being stored? Not sure about invalidInViewFunctions()
- what happens if someone tries to use it in a STATICCALL
?
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.
RETURNCONTACT can be called only in context of EOFCREATE. And EOF create context should revert or call RETURNCONTRACT
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.
case Instruction::RETURNCONTRACT: | ||
return true; |
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 is not enough. This switch
only handles the Operation
item. You need to add the new item to the condition above as well.
Same with terminatesControlFlow()
below.
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
returncontract("b", 0, 32) | ||
return(0, 32) |
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.
returncontract("b", 0, 32) | |
return(0, 32) | |
returncontract("b", 0, 32) |
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.
BTW. This reminds me that returncontract
cannot continue. Does yul verify that there are not unreachable instructions?
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 actually. Probably not. We have a warning at Solidity level but Yul analysis is very simple so I wouldn't be surprised if we don't detect it. I did a quick check by compiling a contract and I did not get such a warning.
Unreachable Yul code is eventually removed by the optimizer anyway.
def54bb
to
46af949
Compare
46af949
to
a764163
Compare
else | ||
{ | ||
auto const& immutableVariables = ContractType(_contract).immutableVariables(); | ||
auto const varIt = std::find_if(immutableVariables.begin(), immutableVariables.end(), [](VariableDeclaration const* _varDecl) { return _varDecl->name() == IRNames::libraryAddressImmutable(); }); | ||
solAssert(varIt != immutableVariables.end(), ""); | ||
t("library_address_offset", std::to_string(m_context.immutableMemoryOffset(*(*varIt)))); | ||
} |
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 should be a helper on IRGenerationContext
- basically a new overload of immutableMemoryOffset()
that accepts the name and searches m_immutableVariables
.
It would also be safer - searching through ContractType::immutableVariables()
will probably give you the same immutables, but I'm not 100% sure there isn't some corner case where they could differ.
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 idea was dropped and now we assume that library address is in 0 offset of aux data. The address is not stored in reserverMemory
. I though it is.
if (!eof) | ||
t("id", std::to_string(_varDecl.id())); | ||
else | ||
t("immutableDataSectionOffset", std::to_string(m_context.immutableMemoryOffset(_varDecl))); |
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 is not an offset relative to the start of data section but rather aux data. I'd rename it to avoid misunderstandings:
t("immutableDataSectionOffset", std::to_string(m_context.immutableMemoryOffset(_varDecl))); | |
t("immutableAuxDataOffset", std::to_string(m_context.immutableMemoryOffset(_varDecl))); |
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.
Also, note that immutableMemoryOffset()
gives you an absolute memory address. I'm not sure it's suitable for use as data section offset. It needs to be made relative and also, I'm not sure these offsets are contiguous.
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.
Ohh shit. You are right. It' relative. I will change accordingly.
immutables.emplace_back(std::map<std::string, std::string>{ | ||
{"immutablesMemoryStart"s, std::to_string(CompilerUtils::generalPurposeMemoryStart)}, | ||
{"immutablesOffset"s, std::to_string(m_context.immutableMemoryOffset(*immutable))}, | ||
{"value"s, "address()"} |
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 looks like it was copied from the library case. It should be the value of the immutable instead. The non-EOF branch has
"mload(" + std::to_string(m_context.immutableMemoryOffset(*immutable)) + ")"
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.
Legacy for some reason makes this the same way. I assume for library the address it not stored in reservedMemory
.
{"immutablesMemoryStart"s, std::to_string(CompilerUtils::generalPurposeMemoryStart)}, | ||
{"immutablesOffset"s, std::to_string(m_context.immutableMemoryOffset(*immutable))}, |
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.
immutablesMemoryStart
will stay the same for each element, won't it? It can be a normal template parameter, outside of immutables
.
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.
Also, immutableMemoryOffset()
already gives you an absolute memory address, so you don't even need to add immutablesMemoryStart
:
m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory; |
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's dropped now. The idea was to have an interface with auxdatastore which accepts a memory pointer where the data which is going to be appended to the container starts. Then a relative offset to immutable and the value. In such a solution you would be able to store values in any memory offset and pass it to returncontract.
<#immutables> | ||
auxdatastore(<immutablesMemoryStart>, <immutablesMemoryOffset>, <value>) | ||
</immutables> | ||
returncontract("<object>", <immutablesOffset>, <immutablesSize>) |
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.
immutablesMemoryOffset
seems undefined. On the other hand immutablesOffset
is defined for each item, but you're using it outside of the loop.
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'm not entirely sure which idea this is implementing (there were a few discussed on the EOF channel) but it does not look correct yet. The auxdatastore()
clearly copies the values of the immutables, but to where? You're not allocating any memory for them.
It would actually be best not to have to copy them in the first place. They're already stored in reserved memory so just pass that memory to returncontract
. The only complication might be whether (1) we can guarantee that only immutables are in this reserved memory and (2) that they're in the right order. I'm not sure about either without diving deeper into the code.
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 only immutables in the memory. The order does matter only on loading them to stack with auxdataloadn
. The offset is taken from IRGeneratorContext
<?eof> | ||
returncontract("<DeployedObject>", 0, 0) | ||
<!eof> |
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 don't have datasize()
/dataoffset()
on EOF yet, so I guess this case should be guarded with solUnimplemented()
.
We should also not have datasize()
, dataoffset()
, datacopy()
, setimmutable()
, loadimmutable()
defined in the Dialect on EOF since they aren't really supported there (at least not in the current form).
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 don't fully get it. For EOF in this case we don't don't use this data related function here. Or maybe I don't understand what is experimental codegen about. :)
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.
Moreover on Dialect level these functions are disabled in EOF
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.
Also note that this is in libsolidity/experimental
(and thus doesn't really matter) - we wanted to remove the entire thing anyways, didn't we? (Since we moved to an external prototype for it and all)
libyul/AsmAnalysis.cpp
Outdated
// FIXME: m_dataNames contains root object name which means we can call EOF creation function with | ||
// root container which is wrong. Is should be possible to call these functions with direct | ||
// subcontainers only. |
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 guess we need to pass in the name of the current object to the analyzer.
As for subcontainers, you can check if the name contains dots (unless it's prefixed with the current object name). This should be a static helper in Object
.
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.
Naming subcontainers with dots is just a convention and it's not required by yul right? If so we cannot rely in this assumption. I will pass current object name to the analyzer.
libyul/AsmAnalysis.cpp
Outdated
// FIXME: m_dataNames contains root object name which means we can call EOF creation function with | ||
// root container which is wrong. Is should be possible to call these functions with direct | ||
// subcontainers only. |
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 guess we need to pass in the name of the current object to the analyzer.
As for subcontainers, you can check if the name contains dots (unless it's prefixed with the current object name). This should be a static helper in Object
.
libyul/backends/evm/EVMDialect.cpp
Outdated
yulAssert(_call.arguments.size() == 3, ""); | ||
Literal const* literal = std::get_if<Literal>(&_call.arguments.front()); | ||
yulAssert(literal, ""); | ||
auto const it = context.subIDs.find(formatLiteral(*literal)); | ||
yulAssert(it != context.subIDs.end(), ""); | ||
yulAssert((*it).second <= std::numeric_limits<solidity::yul::AbstractAssembly::ContainerID>::max(), ""); | ||
_assembly.appendReturnContract(static_cast<solidity::yul::AbstractAssembly::ContainerID>((*it).second)); |
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 could be simplified a bit:
- The message in asserts is optional.
- We're in
solidity::yul
namespace here. - I'd avoid micro-optimizing
map
lookups orvariant
casts, at least not in non-performance critical code. They usually makes things hard to read and is not worth it IMO. - We also have
valueOrDefault()
,valueOrNull()
, which should actually make it just as efficient as with iterators.
yulAssert(_call.arguments.size() == 3, ""); | |
Literal const* literal = std::get_if<Literal>(&_call.arguments.front()); | |
yulAssert(literal, ""); | |
auto const it = context.subIDs.find(formatLiteral(*literal)); | |
yulAssert(it != context.subIDs.end(), ""); | |
yulAssert((*it).second <= std::numeric_limits<solidity::yul::AbstractAssembly::ContainerID>::max(), ""); | |
_assembly.appendReturnContract(static_cast<solidity::yul::AbstractAssembly::ContainerID>((*it).second)); | |
yulAssert(_call.arguments.size() == 3); | |
yulAssert(std::holds_alternative<Literal>(_call.arguments.front())); | |
std::string subObjectName = formatLiteral(std::get<Literal>(_call.arguments.front())); | |
AbstractAssembly::SubID* containerID = util::valueOrNull( context.subIDs, subObjectName); | |
yulAssert(containerID && *containerID <= std::numeric_limits<AbstractAssembly::ContainerID>::max()); | |
_assembly.appendReturnContract(static_cast<AbstractAssembly::ContainerID>(*containerID); |
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.
Changed.
libyul/backends/evm/EVMDialect.cpp
Outdated
// TODO: Can it be done better? For instructions is being done by `createEVMFunction` | ||
createdFunctionIt->second.controlFlowSideEffects.canContinue = false; | ||
createdFunctionIt->second.controlFlowSideEffects.canTerminate = true; | ||
createdFunctionIt->second.controlFlowSideEffects.canRevert = false; |
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 guess we could add ControlFlowSideEffects
as a parameter in createFunction()
. And also extract the part of createEVMFunction()
that fills it out based on SemanticInformation
of a specific instruction into a helper.
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.
In this PR?
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.
Ok. I see the last comment now. Will push PR soon
If you want, some of the things mentioned above ther could potentially be put in a separate PR(s) to make this one smaller:
|
2f36738
to
c9dd5ec
Compare
c9dd5ec
to
e4550e5
Compare
e4550e5
to
4da1267
Compare
Depends on #15456.MergedDepends on #15521DroppedDepends on: #15529MergedDepends on #15536
Depends on #15535