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

Add uint32_t i32 field to Value union #517

Merged
merged 2 commits into from
Jan 20, 2021
Merged

Add uint32_t i32 field to Value union #517

merged 2 commits into from
Jan 20, 2021

Conversation

chfast
Copy link
Collaborator

@chfast chfast commented Sep 2, 2020

Problems this introduces

  • stack.top() = 0 only zeros the .i32 now.
  • FizzyValue{1} only inits the .i32 part. This is excessively used in C API tests. And cannot be disabled. In C++20 you can do FizzyValue{.i64 = 1}.

@chfast
Copy link
Collaborator Author

chfast commented Sep 3, 2020

This ultimately need typed execution API because Result(0) now tests i32 result instead of i64. It is necessary to also check if we are testing correct types.

@axic
Copy link
Member

axic commented Oct 5, 2020

What was the blocker on this? It would make the Rust API a tiny bit nicer too.

@chfast
Copy link
Collaborator Author

chfast commented Oct 5, 2020

What was the blocker on this? It would make the Rust API a tiny bit nicer too.

Unit tests expectations update.

@codecov
Copy link

codecov bot commented Nov 17, 2020

Codecov Report

Merging #517 (0fd8f0d) into master (76a3b2a) will decrease coverage by 0.01%.
The diff coverage is 99.29%.

@@            Coverage Diff             @@
##           master     #517      +/-   ##
==========================================
- Coverage   99.31%   99.30%   -0.02%     
==========================================
  Files          72       72              
  Lines       10257    10304      +47     
==========================================
+ Hits        10187    10232      +45     
- Misses         70       72       +2     
Flag Coverage Δ
spectests 91.49% <100.00%> (-0.01%) ⬇️
unittests 99.30% <99.29%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
test/testfloat/testfloat.cpp 89.51% <0.00%> (-1.27%) ⬇️
lib/fizzy/execute.cpp 100.00% <100.00%> (ø)
lib/fizzy/instantiate.cpp 100.00% <100.00%> (ø)
lib/fizzy/stack.hpp 100.00% <100.00%> (ø)
lib/fizzy/value.hpp 100.00% <100.00%> (ø)
test/unittests/api_test.cpp 100.00% <100.00%> (ø)
test/unittests/capi_test.cpp 100.00% <100.00%> (ø)
test/unittests/cxx20_span_test.cpp 100.00% <100.00%> (ø)
test/unittests/execute_numeric_test.cpp 100.00% <100.00%> (ø)
test/unittests/execute_test.cpp 100.00% <100.00%> (ø)
... and 7 more

@axic
Copy link
Member

axic commented Nov 17, 2020

This is now blocked on explicitly typing number literals in the tests.

lib/fizzy/value.hpp Outdated Show resolved Hide resolved
CMakeLists.txt Outdated Show resolved Hide resolved
@@ -96,7 +97,6 @@ namespace fizzy::test
{
inline uint32_t as_uint32(fizzy::Value value)
Copy link
Member

Choose a reason for hiding this comment

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

Eventually we should also remove this, if we merge this PR.

Copy link
Member

Choose a reason for hiding this comment

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

@chfast do you want to remove it in a separate commit, or a separate PR? I can do the removal too.

Copy link
Member

Choose a reason for hiding this comment

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

Well, actually shouldn't all the test code be using as<uint32_t>()? We can apply that parallel to this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well, actually shouldn't all the test code be using as<uint32_t>()? We can apply that parallel to this PR.

The Result() was checking this to some distinct. But I went with additional type checking to solved it for good.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@chfast do you want to remove it in a separate commit, or a separate PR? I can do the removal too.

In #691.

@chfast chfast changed the base branch from master to test_typed_result November 23, 2020 13:32
@chfast chfast force-pushed the value_i32 branch 4 times, most recently from 1cfb307 to bf0c745 Compare November 23, 2020 16:39
@chfast chfast marked this pull request as ready for review November 24, 2020 12:30
@chfast chfast requested review from gumb0 and axic November 24, 2020 12:30
@@ -115,7 +115,7 @@ class OperandStack
m_top = m_bottom - 1;

const auto local_variables = std::copy_n(args, num_args, m_locals);
std::fill_n(local_variables, num_local_variables, 0);
std::fill_n(local_variables, num_local_variables, Value{});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hm, I'm confused by union initialization again. Does default initialization like this set the active member of the union?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm confused too. This was only found by GCC coverage build.

The 0 is definitely wrong because it calls the special constructor and inits .i32 only.

But the default constructor should be wrong too because it should only init the first field and .i32 is the first one. This works however for current compilers. I wander if MSan can help here. Would be good if you can check the spec yourself.

One solution to solve this is to provide own implementation of the default constructor which inits .i64 (or define i64 = 0;). But with that the Value looses some properties like "is trivially constructable".

Second solution is to move .i64 to the first position. This guarantees the Value{} to be fully zero-initialized. Same for FizzyValue in C API.

But I don't see any sanity solution to init locals with zero without knowing the type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't believe I'm doing it again to myself, but:

  1. The Value{} is value-initialization.
  2. if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized.
  3. Zero-initialization: If T is a union type, the first non-static named data member is zero-initialized and all padding is initialized to zero bits.

So I think the Value{} is correct here as it zeros all object's bytes.

Copy link
Collaborator

@gumb0 gumb0 Nov 30, 2020

Choose a reason for hiding this comment

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

It seems to depend on the interpretation of what is "padding" for a union - whether unused bits of inactive members are "padding". I see some people having doubts about this here: https://stackoverflow.com/questions/11812555/how-to-zero-initialize-an-union#comment15704643_11812606 I have not found a definite answer to this yet.

However, I had a concern about something else (perhaps not directly related to this PR): we zero-initialize Values of locals here, which might or might not choose the active member of the union (I have not found the answer to this either). Then function may access them right away with local.get, then some other operation may access some member of it - can be any type member. Then this may or may not be UB.

We can add a test for this, but as I remember sanitizers didn't catch union type punning.

And if we're required to set the right type, then there's no way around needing the types of locals when initializing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can add a test for this, but as I remember sanitizers didn't catch union type punning.

Well, the tests you added here (execute.local_init) already check this actually. Because checking the result of a function reads some specific member of Value union.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It seems to depend on the interpretation of what is "padding" for a union - whether unused bits of inactive members are "padding". I see some people having doubts about this here: https://stackoverflow.com/questions/11812555/how-to-zero-initialize-an-union#comment15704643_11812606 I have not found a definite answer to this yet.

Agree. This definition issues would be nice to clear.

However, I had a concern about something else (perhaps not directly related to this PR): we zero-initialize Values of locals here, which might or might not choose the active member of the union (I have not found the answer to this either).

"the object's first non-static named data member is zero initialized and padding is initialized to zero bits"

This indicates that the first member is initialized then the active one.

Then function may access them right away with local.get, then some other operation may access some member of it - can be any type member. Then this may or may not be UB.

We can add a test for this, but as I remember sanitizers didn't catch union type punning.

This is true. I have found multiple notes that GCC explicitly supports union-based type punning as some kind of C++ extension. But I cannot find it in documentation.
It is also said that Clang's UBSan should report all UB's Clang recognizes (otherwise it is a compiler bug), so this can indicate this is also supported in Clang.

And if we're required to set the right type, then there's no way around needing the types of locals when initializing.

That's would be very pedantic way of handling this. I would stay with investigating C++ standard and compiler documentation + have many tests for this.

Another alternative to investigate is to use std::memset() as a way of fixing theoretical UB (similarly to std::memcpy() being a (the only) correct way of doing type punning).

Copy link
Collaborator Author

@chfast chfast Dec 1, 2020

Choose a reason for hiding this comment

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

I did some experiments and it looks like GCC and MSVC inits the whole object while Clang only the first element: https://godbolt.org/z/cWscY8.

See also: https://stackoverflow.com/questions/37226837/union-zero-initialization-with-clang-vs-gcc

Copy link
Collaborator

Choose a reason for hiding this comment

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

This may be a good reason to make i64 the first member. Are there any advantages for it to be the second other than aesthetical?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just esthetics. I'm keeping it though for now to catch issues and add regression tests.

lib/fizzy/execute.cpp Outdated Show resolved Hide resolved
test/unittests/capi_test.cpp Outdated Show resolved Hide resolved
@chfast chfast force-pushed the value_i32 branch 2 times, most recently from 4678dff to 338d46c Compare January 11, 2021 10:24
@chfast
Copy link
Collaborator Author

chfast commented Jan 11, 2021

I don't see any significant speed difference except for noise. The GCC LTO build is much noisier.

fizzy/execute/blake2b/512_bytes_rounds_1_mean                     +0.0095         +0.0095            80            81            80            81
fizzy/execute/blake2b/512_bytes_rounds_16_mean                    +0.0085         +0.0085          1207          1217          1207          1217
fizzy/execute/ecpairing/onepoint_mean                             +0.0072         +0.0072        404734        407646        404736        407648
fizzy/execute/keccak256/512_bytes_rounds_1_mean                   +0.0201         +0.0201           101           103           101           103
fizzy/execute/keccak256/512_bytes_rounds_16_mean                  +0.0131         +0.0131          1489          1508          1489          1508
fizzy/execute/memset/256_bytes_mean                               -0.0167         -0.0167             7             7             7             7
fizzy/execute/memset/60000_bytes_mean                             -0.0209         -0.0209          1539          1507          1539          1507
fizzy/execute/mul256_opt0/input1_mean                             +0.0347         +0.0347            29            30            29            30
fizzy/execute/ramanujan_pi/33_runs_mean                           -0.0274         -0.0274           114           111           114           111
fizzy/execute/sha1/512_bytes_rounds_1_mean                        +0.0303         +0.0303            88            91            88            91
fizzy/execute/sha1/512_bytes_rounds_16_mean                       +0.0334         +0.0334          1225          1266          1225          1266
fizzy/execute/sha256/512_bytes_rounds_1_mean                      +0.0075         +0.0075            80            81            80            81
fizzy/execute/sha256/512_bytes_rounds_16_mean                     +0.0087         +0.0087          1101          1111          1101          1111
fizzy/execute/taylor_pi/pi_1000000_runs_mean                      -0.0011         -0.0011         40619         40573         40620         40573
fizzy/execute/micro/eli_interpreter/exec105_mean                  -0.0150         -0.0150             5             4             5             4
fizzy/execute/micro/factorial/20_mean                             +0.0207         +0.0206             1             1             1             1
fizzy/execute/micro/fibonacci/24_mean                             +0.0175         +0.0175          5048          5137          5048          5137
fizzy/execute/micro/host_adler32/1_mean                           +0.0211         +0.0211             0             0             0             0
fizzy/execute/micro/host_adler32/1000_mean                        -0.0042         -0.0042            31            31            31            31
fizzy/execute/micro/icall_hash/1000_steps_mean                    +0.0033         +0.0033            69            69            69            69
fizzy/execute/micro/spinner/1_mean                                -0.0603         -0.0603             0             0             0             0
fizzy/execute/micro/spinner/1000_mean                             -0.0134         -0.0134             9             9             9             9

Copy link
Collaborator

@gumb0 gumb0 left a comment

Choose a reason for hiding this comment

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

Looks fine to me :shipit:

@chfast chfast force-pushed the value_i32 branch 2 times, most recently from 52786d4 to 37a3470 Compare January 13, 2021 14:34
@@ -47,7 +47,7 @@ TEST(api, execution_result_value)
const ExecutionResult result = Value{1234_u32};
EXPECT_FALSE(result.trapped);
EXPECT_TRUE(result.has_value);
EXPECT_EQ(result.value.i64, 1234_u32);
EXPECT_EQ(result.value.i32, 1234_u32);
Copy link
Member

Choose a reason for hiding this comment

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

Notpick, we do not actually need _u32 here, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No. But using _u32 gives higher confidence (i.e. someone has thought what the result type is expected). Still gray area though.

include/fizzy/fizzy.h Outdated Show resolved Hide resolved
This allows using i32 values directly what is less confusing than
previous emulation with storing these as uint64_t.
@axic axic changed the title Value i32 Add uint32_t i32 field to Value union Jan 20, 2021
@axic axic merged commit 470bd08 into master Jan 20, 2021
@axic axic deleted the value_i32 branch January 20, 2021 12:41
@axic axic mentioned this pull request Jan 28, 2021
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.

3 participants