Skip to content
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

Resolve #88 #109

Merged
merged 3 commits into from
Mar 19, 2023
Merged

Resolve #88 #109

merged 3 commits into from
Mar 19, 2023

Conversation

nathanielhourt
Copy link
Contributor

Change Description

Implement the CDT API wrapper for get_code_hash intrinsic and add some testing

API Changes

  • API Changes

Added get_code_hash intrinsic to contracts' C and C++ APIs

Documentation Additions

  • Documentation Additions

Copy link
Contributor

@larryk85 larryk85 left a comment

Choose a reason for hiding this comment

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

Appreciate the PR Nathan.

full_result = (code_hash_result*)alloca(sizeof(code_hash_result));

// Packed size is dynamic, so we don't know what it'll be. Try to have plenty of space.
auto struct_buffer_size = sizeof(code_hash_result)*2;
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure which one is more efficient but we have a common pattern to execute intrinsic where first we execute it with zero size first, it returns with return structure size and then we execute it again. example is get_action:

   inline action get_action( uint32_t type, uint32_t index ) {
      constexpr size_t max_stack_buffer_size = 512;
      int s = internal_use_do_not_use::get_action( type, index, nullptr, 0 );
      eosio::check( s > 0, "get_action size failed" );
      size_t size = static_cast<size_t>(s);
      char* buffer = (char*)( max_stack_buffer_size < size ? malloc(size) : alloca(size) );
      auto size2 = internal_use_do_not_use::get_action( type, index, buffer, size );
      eosio::check( size == static_cast<size_t>(size2), "get_action failed" );
      return eosio::unpack<eosio::action>( buffer, size );
   }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, well I'm not sure how the overheads stack up in the WASM VM, but calling an intrinsic is roughly equivalent in principle to a syscall to kernel space from userland, and those are notoriously slow. alloca, on the other hand, is merely ticking a register, which is pretty much the fastest instruction imaginable.

There's no substitute for benchmarking, but I would expect making an extra call out of the VM to be at least a couple of orders of magnitude slower than alloca. I think it would only make sense to make the second call if there was a possibility that the struct would be unpredictably large (especially, structs containing string or vector, etc), but even in this case, just allocating a very large heap buffer is probably much faster, just more wasteful of space. That's a hard decision for a library to make, since different clients have different constraints, but in this case the buffer's size has a tight upper bound so we don't need to concern ourselves. =)

Copy link
Contributor Author

@nathanielhourt nathanielhourt Mar 2, 2023

Choose a reason for hiding this comment

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

Is there a way to get microseconds or CPU ticks within a contract? I'd like to run some benchmarks, including the cost of calling an intrinsic, as I'm pretty certain it falls in the category of obnoxiously slow, and it might be worth my effort to go through and replace all of these "when you call the intrinsic once, call it twice" instances with something way faster. CPU time is scarce on the blockchain, so the stdlib should make an effort to conserve it for the contract.

Copy link
Member

Choose a reason for hiding this comment

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

Is there a way to get microseconds or CPU ticks within a contract?

Seems like that would be a good addition to: eosnetworkfoundation/product#134

Copy link
Contributor

Choose a reason for hiding this comment

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

@nathanielhourt I agree with your point but the easiest solution looks to me just to make it inline with other stuff. Anyway there are not a lot. If you can spend extra and tell the difference, we can go that way and redo this (probably as part of other PR).
@larryk85 any thought on that?

Copy link
Member

Choose a reason for hiding this comment

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

Since the packed size will always be the same unless we somehow manage to make it to 128 versions of the struct, which seems incredibly unlikely since each bump would be a consensus change, it does seem rather wasteful to make multiple calls. It seems unlikely to even need to *2 the size here.

I might suggest checking the return value of internal_use_do_not_use::get_code_hash(), maybe even in lieu of the version check (the possibility that the host function could in the future return a version different than the one asked for would seem to be devastating to existing contracts -- why would we ever do that).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hahaha yeah, the version check is abysmally pessimistic, but I like to make such checks where cheap. Bugs love to hide at API boundaries and deucedly so at versioned ones! =)

The *2 size is definitely overkill, and I would be amenable to reducing it. I also agree that checking the return value is prudent. Eventually, a general template could probably be improvised to do an optimistic fast path (alloca and single call) with a slow path fallback (malloc and second call) if the first call didn't allocate adequate space, and even give motivated clients the option to pass a size hint to optimize the fast path for their case, and make that template reusable for all the intrinsics. That would be nice. But for now, I can just go for a retval check and fallback slow path, and maybe dress it up to a template in a future PReq.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, check out the new version. Also, I just noticed that the get_action implementation has a memory leak in the greater than 512 byte case.

Copy link
Contributor

Choose a reason for hiding this comment

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

@nathanielhourt PR has been approved. Thanks. Do you want to add something or just letting enf dev merge by themselves? normally developer pushes their changes so you are good to go if you are done

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I don't have privs to merge it even after approval. I figured someone else would do it. =)

@nathanielhourt
Copy link
Contributor Author

Appreciate the PR Nathan

Call me Nate =)

@spoonincode spoonincode linked an issue Mar 8, 2023 that may be closed by this pull request
Ensure the buffer was adequately large for the response and if not,
allocate sufficient space and call intrinsic again.
@spoonincode spoonincode merged commit 7909340 into AntelopeIO:main Mar 19, 2023
@nathanielhourt nathanielhourt deleted the issue-88 branch March 19, 2023 23:14
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.

missing support for get_code_hash host function
5 participants