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

Support shift operators for variables #527

Closed
wants to merge 24 commits into from
Closed

Support shift operators for variables #527

wants to merge 24 commits into from

Conversation

axic
Copy link
Member

@axic axic commented Apr 30, 2016

Add tests for:

  • shifting constants
  • smaller types
  • checks that high order bit cleanup is performed correctly
  • right shift of negative left hand sides
  • shifts by negative amounts (both for signed and unsigned right hand sides)
  • more complex usage of shifts in some actually useful snippet
  • shift assignment (a <<= 2)

Do not merge yet - I would like to get feedback before finishing this work. Also some commits should be squashed later.

First two commits could be merged as those are only trivial fixes.

Regarding this rest:

  • SHL seems solid
  • SAR/SHR needs further testing (and not failing tests) and validation
  • the tests need to be extended and perhaps it shouldn't be in the end-to-end test

(Fixes #33.)

@chriseth
Copy link
Contributor

chriseth commented May 2, 2016

Nice! Some comments:

  • shifts are bit operations, they assume some representation of numbers in binary and thus it should not be possible to use them on integers
  • signextend should be used as part of the "higher order bits cleanup" before the operation, i.e. appendShiftOperatorCode should not take a type argument.

@axic
Copy link
Member Author

axic commented May 2, 2016

shifts are bit operations, they assume some representation of numbers in binary and thus it should not be possible to use them on integers

You mean signed integers?

My understanding is the following: all shift operations should work on the internal representation of numbers, which is two's complement for int.

The different shifts:

  1. SHL (shift left, <<) - Shouldn't care if the input is int/uint
  2. SHR (shift right, >>>) - Shouldn't care if the input is int/uint, 0 is added on the left
  3. SAR (shift arithmetic right, >>) - Should care about sign, 1 is added on the left if it is signed (i.e. highest bit was 1, depends on the width of intXX)

The above is taken from Javascript and is what the Solidity parser had. I am not fully convinced we need SAR - if omit it, I would suggest to have >> for SHR.

@VoR0220
Copy link
Member

VoR0220 commented May 4, 2016

I would hold off on this until after the fixed point and rationals are implemented....there are some major changes that kind of dork what you're trying to do in your set up :)

@chriseth
Copy link
Contributor

chriseth commented May 5, 2016

@axic no I mean integers in themselves. There is no such thing as "shifting an integer", it is just a number, you should not have to assume that it is in two's complement. At the point where you explicitly convert the integer into a bytesNN, it assumes two's complement representation, the notions of "left" and "right" get a meaning and it makes sense to "shift" in those directions.

Having said that: I realize how used people are to applying shift operators to integers...

So if we add shift operators, I would prefer to drop >>> and use x >> y to be identical to x / 2**y.

One advantage of that is that we can say that "shifting an integer" means multiplying / dividing by appropriate powers of two and thus get rid of the number representation issue.

Concerning the break with javascript: Solidity is typed, so the distinction between >> and >>> is not really needed.

@VoR0220
Copy link
Member

VoR0220 commented May 9, 2016

consider getting rid of the >>> token?

@axic
Copy link
Member Author

axic commented May 12, 2016

@chriseth It is more clear now. Coming from a C background, I only have a notion of "integers" which behave like "bytesNN".

Do I understand it right that you want a left and right shift operator, denoted by << (SHL) and >> (SHR) respectively and that it behaves according to the type information. For signed integers the right shift would behave like an arithmetic right shift (= x / 2**y).

Is this correct?

I think @VoR0220 added support for shift operations on constants as a PR to my branch. I'll merge that soon.

@axic
Copy link
Member Author

axic commented May 17, 2016

Following up with the previous question. Do we agree with the following?

  • Have logical shifts only: << (SHL) and >> (SHR)
  • Remove the SHR define
  • Remove the >>> token

If yes, I'll push an update later today.

@@ -625,6 625,7 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
{
rational value;
bool fractional = isFractional() || other.isFractional();
using boost::multiprecision::pow;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't move that up here, it is only three letters and the distance between declaration and usage is quite high.

Copy link
Member

@VoR0220 VoR0220 May 17, 2016

Choose a reason for hiding this comment

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

my bad...I did that. Mainly because its being reused...figured it would be unnecessary to have multiple declarations.

@axic
Copy link
Member Author

axic commented May 19, 2016

As per our discussion, for the moment the following assumptions are made:

  • >>> token is kept, but an exception is thrown in Types/ExpressionCompiler
  • SHL, SHR, SAR defines are unchanged, but internally we'll only use SHL and SAR which denote << and >> respectively.
  • shifting with negative right value is prohibited (throws an error)

@axic axic mentioned this pull request Jun 7, 2016
@axic
Copy link
Member Author

axic commented Jun 7, 2016

@chriseth

Should I change this to include Token::isShiftOp(c_op)?

@@ -363,7  363,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
                bool cleanupNeeded = false;
                if (Token::isCompareOp(c_op))
                        cleanupNeeded = true;
-               if (commonType.category() == Type::Category::Integer && (c_op == Token::Div || c_op == Token::Mod))
                if (commonType.category() == Type::Category::Integer && (c_op == Token::Div || c_op == Token::Mod || Token::isShiftOp(c_op)))
                        cleanupNeeded = true;

                // for commutative operators, push the literal as late as possible to allow improved optimization

Also where do I add the check for negative right value? The same visitor? After cleanup? Something like this?

@@ -386,6  386,11 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
                        leftExpression.accept(*this);
                        utils().convertType(*leftExpression.annotation().type, commonType, cleanupNeeded);
                }
                if (Token::isShiftOp(c_op))
                {
                        IntegerType const& type = dynamic_cast<IntegerType const&>(*rightExpression.annotation().type);
                        solAssert(!type.isSigned(), "Shift with negative right value is not supported.");
                }
                if (Token::isCompareOp(c_op))
                        appendCompareOperatorCode(c_op, commonType);
                else

@chriseth
Copy link
Contributor

chriseth commented Jun 9, 2016

I think it would be good to perform cleanup before every shift operation. The cleanup for the left operand is not necessary for left shifts, but I think it is fine to do it still.

The check that the right operand is not negative has to be done in asm. Even if the right operand has a signed type, it can still be positive.

Finally, I think we should ensure that the second operand has to be an integer (i.e. not a bytesN) - please add a test for that, not sure if we already do.

@chriseth
Copy link
Contributor

chriseth commented Jun 9, 2016

I think it is good to promote the appendShiftOperatorCode function out of appendOrdinaryBinaryOperatorCode and call it at the same point where that is called because it requires some type information, at least for the second operand.

@VoR0220
Copy link
Member

VoR0220 commented Jul 8, 2016

@axic what's the status of this?

@axic
Copy link
Member Author

axic commented Jul 12, 2016

@VoR0220 I've been away, will look into my open Solidity issues this week.

@axic axic mentioned this pull request Jul 28, 2016
@axic
Copy link
Member Author

axic commented Aug 2, 2016

@chriseth

Any idea how this could be broken?

/home/travis/build/ethereum/solidity/test/../test/libsolidity/SolidityExecutionFramework.h(71): fatal error in "bytes_index_access": Unknown exception thrown by m_compiler.compile(m_optimize, m_optimizeRuns)

Also, how to test with signed ints? There doesn't seem to be an i256(). Only fromHex() is the way?

Removed the catch-all and I get this:

libc  abi.dylib: terminating with uncaught exception of type std::bad_cast: std::bad_cast

@axic
Copy link
Member Author

axic commented Aug 2, 2016

@chriseth:

The check that the right operand is not negative has to be done in asm.

Do we check that in every single case or only if it a signed type?

I've added a piece of code to appendShiftOperatorCode, which invokes the errorTag.

@chriseth
Copy link
Contributor

chriseth commented Aug 3, 2016

@axic signed integers: just use u256(-1).
The test for negativeness has of course only be done for signed types.

BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0x16)) == encodeArgs(u256(0)));
BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0x17)) == encodeArgs(u256(0)));
}

Copy link
Contributor

@chriseth chriseth Aug 4, 2016

Choose a reason for hiding this comment

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

Please also add tests for:

  • smaller types
  • checks that high order bit cleanup is performed correctly
  • right shift of negative left hand sides
  • shifts by negative amounts (both for signed and unsigned right hand sides)
  • more complex usage of shifts in some actually useful snippet
  • shift assignment (a <<= 2)

@axic
Copy link
Member Author

axic commented Aug 5, 2016

Add tests for:

  • smaller types
  • checks that high order bit cleanup is performed correctly
  • right shift of negative left hand sides
  • shifts by negative amounts (both for signed and unsigned right hand sides)
  • more complex usage of shifts in some actually useful snippet
  • shift assignment (a <<= 2)

@@ -214,7 214,9 @@ a non-rational number).
String Literals
---------------

String Literals are written with double quotes (``"abc"``). As with integer literals, their type can vary, but they are implicitly convertible to ``bytes`` if they fit, to ``bytes`` and to ``string``.
String Literals are written with double quotes (``"abc"``). As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32`` if they fit, to ``bytes`` and to ``string``.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you should rebase this.

@axic
Copy link
Member Author

axic commented Sep 6, 2016

I'll squash some of the commits and rebase against develop later.

@axic axic mentioned this pull request Sep 6, 2016
// Disable >>> here.
if (_operator == Token::SHR)
return TypePointer();
if (Token::isShiftOp(_operator) && !isAddress()) // && !_other->isAddress())
Copy link
Member Author

Choose a reason for hiding this comment

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

@chriseth Should we check here for the other side to be an address?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you have to do this whole thing even before line 342. The following should be possible (please add a test):

uint x = 70;
int8 y = 2;
x << y;

x and y do not have a common type, and so currently, it would fail in line 345.

Yes, we should perform some more checks for the other side. I would say rational number is fine as long as it is an integer. Fixed point is fine, but we have to perform an integer check at run-time. Integer is fine as long as it is not an address.

// Disable >>> here.
if (_operator == Token::SHR)
return TypePointer();
if (Token::isShiftOp(_operator) && !isAddress()) // && !_other->isAddress())
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you have to do this whole thing even before line 342. The following should be possible (please add a test):

uint x = 70;
int8 y = 2;
x << y;

x and y do not have a common type, and so currently, it would fail in line 345.

Yes, we should perform some more checks for the other side. I would say rational number is fine as long as it is an integer. Fixed point is fine, but we have to perform an integer check at run-time. Integer is fine as long as it is not an address.

@pirapira
Copy link
Member

pirapira commented Dec 5, 2016

I added a test case about cleaning up higher bits.

@chriseth chriseth self-assigned this Dec 5, 2016
@chriseth chriseth mentioned this pull request Dec 5, 2016
@chriseth
Copy link
Contributor

chriseth commented Dec 5, 2016

Closing in Favour of #1487 (with a branch in this repository).

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.

Implement bit shifting
6 participants