Skip to content

Introduce subassembly offset output artifact #15710

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

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from

Conversation

nikola-matic
Copy link
Collaborator

@nikola-matic nikola-matic commented Jan 13, 2025

Resolves #14827.

  • remove bytecode string from SubAssembly
  • implement output for CLI
  • implement in EOF
  • figure out tests (CLI is not gonna work due to each commit changing the binary hash and thus metadata) - boost?
  • test via Yul compilation
  • check whether isCreation is preserved during import
  • include metadata offset in the structure? [not in this PR]
  • docs

@nikola-matic nikola-matic force-pushed the introduce-subassembly-offset-output-artifact branch from d086ad9 to e4f8896 Compare January 17, 2025 10:39
Copy link
Member

@cameel cameel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some bugs, some missing parts and the overall implementation could be done more robustly.

I'm also not sure if the whole design actually accomplishes our goal. Only giving sub locations may not be enough to locate metadata without heuristics because data objects are not placed in separate subs at evmasm level.

See comments below for details.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the feature to the CLI too. We should keep them at parity (and it's a pain for testing/development if the only way to access a feature is through StandardJSON).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, Yul compilation is not covered. Aside from general inconsistency, this makes two-step compilation less powerful, which may be a problem for future parallelization.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be implemented for EOF as well (i.e. in assembleEOF(), or, if possible, just in assemble() covering both with the same code).

Since we're at the stage where EOF is passing semantic tests (and they will be enabled by default quite soon), we should start requiring all new features for work on EOF as well.

{
for (auto& subAssembly: _subAssemblies)
{
subAssembly.start = _currentBytecodeSize - subAssembly.length;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm either missing something or this assumes that all subassemblies overlap and extend to the end of their parent assembly. Does it even work with more than one subassembly? If you have two subassemblies of the same length then you will end up with the same start location for both. And even if they're of different lengths it will be wrong.

EDIT: Yeah, you even have a test showing this overlap (standard_subassembly_offsets/):

                                        {
                                            "isCreation": false,
                                            "length": 780,
                                            "start": 1007
                                        },
                                        {
                                            "isCreation": true,
                                            "length": 130,
                                            "start": 1657,
                                            "subs": [

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also have some asserts here. At the very least that a subassembly does not stick outside of its parent assembly.

Comment on lines 13 to 28
"subAssemblyOffsets": {
"subs": [
{
"isCreation": true,
"length": 130,
"start": 0,
"subs": [
{
"isCreation": false,
"length": 104,
"start": 26
}
]
}
]
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, is Sourcify ok with getting only subassembly locations? I've always been thinking about metadata as a separate subassembly myself, because it's separate data Object in Yul, but looking at the PR I remembered that it's actually not the case at evmasm level. Metadata goes into Assembly::m_auxiliaryData and logically becomes a part of each assembly's bytecode, not a separate object. It's simply appended after all the subs and other data. I think that due to this it will still be necessary to use heuristics to fish out its location within the assembly, even knowing location of all subassemblies.

I think we may need to include the start and length of Assembly::m_auxiliaryData separately for each sub (when it's non-empty). For completeness we may want to simply list all the data chunks (that would actually have been nice to have for compiler debugging in some cases).

CC @kuzdogan.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, we should have some tests that include data objects between assemblies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if we had the exact location of metadata, we could create a more robust, Boost-based test that would get the structure info and the bytecode and diff the CBOR bit. It's not easy to spot a problem just looking at the values in command-line tests and we're not even shown the bytecode.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed this, I'll have a look at this tomorrow

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I talked about it later with @nikola-matic and he said that the justification for not adding data locations was that Sourcify has no problem detecting metadata at the top level and it's only the nested contracts that cause problems so adding information about where whole contracts start and end is enough to extend the existing mechanism to cover them.

I still think we should include the exact location though. We do have it and it's very easy to add, I see no reason to force tools to use heuristics to find it.

One thing we agreed on though was that the info about data locations does not have to be a part of this very PR. This will still be a working feature without it and the extra fields can be added on top of it as an extension.

Copy link
Member

@kuzdogan kuzdogan Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cameel Isn't it safe to assume the CBOR will be at the end of all of the assemblies that are not creation: true? So we can just look at all of them, get the last two bytes and decode. So it's technically not a heuristic but a rule?

Of course I wouldn't say no to this and it would make our lives easier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for pointing that out. I completely forgot that CBOR is not the only thing there and that we actually also add the length ourselves. You're right. With that the check should be reliable, at least when we're talking about the contracts you compile yourself and can be sure that the metadata is supposed to be present.

Ok then, I guess it's not strictly necessary for Sourcify in that case.

I still don't see much downside in providing that information though :)

Comment on lines 12 to 20
"subAssemblyOffsets": {
"subs": [
{
"isCreation": false,
"length": 87,
"start": 0
}
]
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input file has two assemblies but the output shows only one. Is this a bug in your feature or an instance of #15725 (because the nested assembly is unreferenced)?

"subAssemblyOffsets": {
"subs": [
{
"isCreation": false,
Copy link
Member

@cameel cameel Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I wonder why this isn't true. Does exporting and reimporting asm JSON lose the creation status or is it just because of how the artificial input was crafted? If the status gets lost, this would be a bug (and should be reported).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, we also need some coverage for optimized compilation.

@cameel
Copy link
Member

cameel commented Jan 23, 2025

Some more thoughts after today's call:

  • You asked if the top-level assembly is always a creation object - just wanted to note that this is actually irrelevant to this task. Just follow whatever Assembly::isCreation() says.
  • About the complication with adding info for top-level object: I guess the thing that makes it awkward to implement is partly the fact that you're trying to store info about a complete subhierachy in every LinkerObject. You can do what we agreed on, but just FYI there's also a different way you could go about it:
    • In LinkerObject store only info about start and length of its immediate subs. No info about nested objects or the current object.
    • Move the calculation of the whole hierarchy to a helper like Assembly::structure() that would traverse the m_subs tree, creating corresponding SubAssembly structs as it goes through it. It has access to all the necessary info in each Assembly and its LinkerObject.
    • You'd just have to make sure that CompilerStack/YulStack/EVMAssemblyStack either expose the Assembly or have a method that asks the Assembly they store inside to produce this artifact.

@nikola-matic nikola-matic force-pushed the introduce-subassembly-offset-output-artifact branch 4 times, most recently from 939e863 to a624455 Compare February 5, 2025 11:03
@cameel
Copy link
Member

cameel commented Feb 10, 2025

Another thing missing here are docs. The new option should at the very least be mentioned in the Standard JSON input description. It would also be good to have a paragraph on the page about Metadata explaining that nested contracts can have metadata as well and that the output of this option (combined with the length marker at the end of metadata) can be used to locate it.

@nikola-matic nikola-matic force-pushed the introduce-subassembly-offset-output-artifact branch 2 times, most recently from 6726747 to 8733981 Compare February 19, 2025 10:30
@nikola-matic nikola-matic force-pushed the introduce-subassembly-offset-output-artifact branch 2 times, most recently from 21e2558 to 1eebef2 Compare February 28, 2025 10:31
@ethereum ethereum deleted a comment from stackenbotten Feb 28, 2025
@ethereum ethereum deleted a comment from stackenbotten Feb 28, 2025
@nikola-matic nikola-matic force-pushed the introduce-subassembly-offset-output-artifact branch 3 times, most recently from 1af3027 to 218f4d7 Compare February 28, 2025 13:21
Copy link

This pull request is stale because it has been open for 14 days with no activity.
It will be closed in 7 days unless the stale label is removed.

@github-actions github-actions bot added the stale The issue/PR was marked as stale because it has been open for too long. label Mar 15, 2025
@nikola-matic nikola-matic removed the stale The issue/PR was marked as stale because it has been open for too long. label Mar 15, 2025
@nikola-matic nikola-matic added this to the 0.8.30 milestone Mar 15, 2025
@nikola-matic nikola-matic self-assigned this Mar 15, 2025
@nikola-matic nikola-matic force-pushed the introduce-subassembly-offset-output-artifact branch from c3c04f6 to 8846e75 Compare March 17, 2025 10:46
@nikola-matic nikola-matic marked this pull request as ready for review March 17, 2025 11:51
@@ -137,6 +137,7 @@ static std::string const g_strSrcMapRuntime = "srcmap-runtime";
static std::string const g_strStorageLayout = "storage-layout";
static std::string const g_strTransientStorageLayout = "transient-storage-layout";
static std::string const g_strVersion = "version";
static std::string const g_strAssemblyStructure = "assembly-structure";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: options are in alphabetical order here.

@@ -89,6 +89,15 @@ struct LinkerObject
/// Bytecode offsets of named tags like function entry points.
std::map<std::string, FunctionDebugData> functionDebugData;

struct Structure {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe AssemblyStructure is better?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Outputting the CBOR Metadata positions in the bytecode
4 participants