Skip to content

Add support for bitwise left/right shift operators#8457

Closed
yegappan wants to merge 1 commit intovim:masterfrom
yegappan:excmdtest
Closed

Add support for bitwise left/right shift operators#8457
yegappan wants to merge 1 commit intovim:masterfrom
yegappan:excmdtest

Conversation

@yegappan
Copy link
Member

@yegappan yegappan commented Jun 26, 2021

The following bitwise functions are currently supported: and(), or(), xor() and invert().
But there is no function or operator for bitwise left/right shift.
Add the << and >> operators for bitwise left shift and right shift.

@codecov
Copy link

codecov bot commented Jun 26, 2021

Codecov Report

Merging #8457 (22313ea) into master (835ee98) will increase coverage by 0.01%.
The diff coverage is 93.63%.

❗ Current head 22313ea differs from pull request most recent head 7c5366d. Consider uploading reports for the commit 7c5366d to get more accurate results

@@            Coverage Diff             @@
##           master    #8457      +/-   ##
==========================================
+ Coverage   81.60%   81.62%   +0.01%     
==========================================
  Files         158      158              
  Lines      184991   185128     +137     
  Branches    41845    41877      +32     
==========================================
+ Hits       150968   151106     +138     
+ Misses      21503    21499       -4     
- Partials    12520    12523       +3     
Flag Coverage Δ
huge-clang-none 82.54% <92.99%> (+0.01%) ⬆️
linux 82.54% <92.99%> (+0.01%) ⬆️
mingw-x64-HUGE 0.00% <0.00%> (ø)
mingw-x64-HUGE-gui 78.05% <95.41%> (+0.01%) ⬆️
windows 76.83% <95.41%> (+0.01%) ⬆️

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

Impacted Files Coverage Δ
src/vim9expr.c 92.13% <88.88%> (-0.21%) ⬇️
src/vim9execute.c 91.81% <94.44%> (+0.01%) ⬆️
src/eval.c 92.43% <98.50%> (+0.10%) ⬆️
src/gui_w32.c 34.85% <0.00%> (-0.04%) ⬇️
src/version.c 84.16% <0.00%> (ø)
src/libvterm/src/state.c 38.46% <0.00%> (ø)
src/evalfunc.c 90.74% <0.00%> (+0.02%) ⬆️
src/terminal.c 77.07% <0.00%> (+0.02%) ⬆️
src/evalvars.c 91.54% <0.00%> (+0.04%) ⬆️
src/evalbuffer.c 91.36% <0.00%> (+0.05%) ⬆️
... and 4 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 835ee98...7c5366d. Read the comment docs.


lshift({expr1}, {expr2}) *lshift()*
Bitwise left shift {expr1} by {expr2}. The arguments must be
Numbers. Other types of arguments cause an error.
Copy link
Member

@dpelle dpelle Jun 26, 2021

Choose a reason for hiding this comment

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

Usually bit shifts in C are done with unsigned (although it's also possible with signed).
Vim script does not have unsigned.

We should document how it behaves with the sign bit. In other words, what is the result of lshift(-2, 1)?
And we should test with negative numbers too.

In Java, left-shift has 2 different operators >> (sign extension) and >>> (shifts a zero into the leftmost position). See: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html So maybe we need an extra (optional?) parameter to indicate whether to extend sign or not?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the review. Yes. We can add an option to rshift() to deal with sign extension.

src/evalfunc.c Outdated
return;
}
rettv->vval.v_number = tv_get_number(&argvars[0])
<< tv_get_number(&argvars[1]);
Copy link
Member

Choose a reason for hiding this comment

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

This will result in UB (undefined behavior) if we do e.g. lshift(1, 100) as in C, it is UB to bit-shifts by more bits than the number of bits in the number.

In Vim script, we don't want such undefined behavior. I'd say lshift(1, 100) should return 0, and we must thus return 0 if argvars[1] is too high. And we can add a test for bit shift with large number of bits.

We can verify that there is no runtime error using ubsan.

Also, what should happen when shifting by a negative number of bits? E.g. lshift(1, -1) In C, it is undefined behavior but again, we don't want UB in Vim script.

Copy link
Member Author

Choose a reason for hiding this comment

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

I will add a check for argvars[1] and return 0 if it is too large. If the shift value is negative, we can return the original value.

@brammool
Copy link
Contributor

brammool commented Jun 26, 2021 via email

@vim-ml
Copy link

vim-ml commented Jun 26, 2021 via email

@brammool
Copy link
Contributor

I appear to have missed this one. Bit shift does not apply to floats, your example does not seem representative.
lshift(val, 4) would be equivalent to "val * 16" and rshift(val, 4) equivalent to "val / 16".

@vim-ml
Copy link

vim-ml commented Nov 16, 2021 via email

@LemonBoy
Copy link
Contributor

Any update on this PR? The pow+float2nr approach works but is quite a mouthful, having the shift operators would greatly complement the other bit ops.

@brammool
Copy link
Contributor

I have no see a response to my suggestion to use "N * 16" to shift 4 bits left and "N / 16" to shift 4 bits right.

@brammool
Copy link
Contributor

I mean, why it would be needed to specify the number of bits. When do you not know that beforehand?

@LemonBoy
Copy link
Contributor

Sometimes I use bitsets to keep track of a small (< bit size of number variable) number of boolean elements, that's much faster than eg. having a list with N bool elements.
Testing whether the i-th element is present can be achieved with set & (1 << index) and unless you know all the indices beforehand you're bound to use a runtime-known shift value.

@yegappan yegappan force-pushed the excmdtest branch 2 times, most recently from 22db963 to a8106c1 Compare April 27, 2022 18:58
@yegappan yegappan force-pushed the excmdtest branch 2 times, most recently from 6cb840c to c7c2bc8 Compare May 19, 2022 12:17
@brammool
Copy link
Contributor

I was trying to find another language that uses a function for bitwise shift, but it appears all the popular languages use ">>" and "<<" operators. Would that be possible in Vim as well, or would it create a conflict?
It might seem a bit inconsistent with the other bitwise functions though.

Another thing to consider is to use one function, where a negative argument is used to shift in the other direction. Again, this would depend on how other languages do this, what people are used to.

@dpelle
Copy link
Member

dpelle commented May 19, 2022

it appears all the popular languages use ">>" and "<<" operators

Java uses >>> and >> which has different behavior for the bit sign.
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html

Something to consider if Vim script has bit shift operators.

@yegappan yegappan force-pushed the excmdtest branch 4 times, most recently from 2d37bfe to 3864da5 Compare May 21, 2022 14:06
src/vim9.h Outdated
ISN_COMPAREFUNC,
ISN_COMPAREANY,

// bitwise left/right shift operation
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be another case of ISN_OPNR instead of having its own opcode.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have updated the PR to use ISN_OPNR instead of using a new opcode.

(uvarnumber_T)tv1->vval.v_number >> tv2->vval.v_number;

// clear the topmost sign bit
tv1->vval.v_number &=
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 this to be quite weird, I'd let the user deal with negative numbers rather than making the integers N-1bit wide.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is based on the following comment from Bram:

Since bitwise operators should not care about a sign bit, I would very
much prefer ">>" to just make the topmost bit zero. No need for ">>>"
then.

@yegappan yegappan changed the title Add support for bitwise left/right shift functions Add support for bitwise left/right shift operators May 21, 2022
@yegappan yegappan force-pushed the excmdtest branch 2 times, most recently from 7b6f85a to 8915b3e Compare May 21, 2022 16:41
@brammool
Copy link
Contributor

" The topmost bit (sign bit) is always cleared " that is for the ">>" operator, not for "<<".

For operator precedence it looks like we need a new level. It's in between "+" and "-"
and the comparative operators. Thus between eval4() and eval5().

This is what Ecmascript specifies:
https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-left-shift-operator

Are there languages that use a different precedence? I know have have often been confused by this, but we should stick to what most other popular languages do.

Comment on lines +4104 to +4106
if (iptr->isn_arg.op.op_type == EXPR_RSHIFT)
// clear the topmost sign bit
res &= ~((uvarnumber_T)1 << MAX_LSHIFT_BITS);
Copy link
Contributor

@errael errael May 21, 2022

Choose a reason for hiding this comment

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

This fixup only works if arg2 is 1. Something like following is needed.

result &= ~((1 << arg2) - 1 << NBITS - arg2)

If right shifted by 3 and NBITS is 8 then this should be &= 0001_1111.

If you can count on the compiler to respect unsigned and make sure zeros are shifted in, then you don't need a fixup. If you can't count on the compiler, this might be more understandable and doesn't need a fixup.

res = arg1 >> 1;
res &= ~(1 << MAX_LSHIFT_BITS);
res = res >> arg2 - 1;

None of this is tested, simulated on paper... (I looked up the precedence against +/-)

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, updated to add the missing ~ and &= 0001_1111

@LemonBoy
Copy link
Contributor

This is based on the following comment from Bram:

Ah I see, the comment didn't get posted to GH for some reason. That's fine as long as it's clearly stated in the docs that the operators perform logical shifts (opposed to arithmetic ones).

The sign-clearing is useless as you're already performing the shift on unsigned values.

The next logical step is to extend the and/or/not operators to work with numbers, right now shifts are the only exception among the bitwise ops.

@errael
Copy link
Contributor

errael commented May 21, 2022

In a performance sensitive function I recently used vim's and(arg1, arg2) and or(arg1, arg2) since I couldn't find &,|. Got me wondering what the difference is between a function and vim9 instruction execution; so I was interested in taking a look at how this instruction executes.

I guess if invoking and() involves setting up a stack frame then the difference is pretty big.

@errael
Copy link
Contributor

errael commented May 21, 2022

the and/or/not operators to work with numbers

xor gets no respect.

@yegappan yegappan force-pushed the excmdtest branch 4 times, most recently from ac0d235 to 1d30350 Compare May 22, 2022 14:10
@brammool
Copy link
Contributor

I don't spot a test case for repeating the operator, e.g. 1 << 2 << 3
I believe this should work like (1 << 2) << 3

@yegappan
Copy link
Member Author

I have updated the PR to support using multiple bit shift operators in an expression and added additional tests.

@brammool
Copy link
Contributor

Looks like it is ready to include now, thanks.

Comment on lines +4104 to +4106
if (iptr->isn_arg.op.op_type == EXPR_RSHIFT)
// clear the topmost sign bit
res &= ~((uvarnumber_T)1 << MAX_LSHIFT_BITS);
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you tried it without this post shift fixup? Since the type of the the left hand side is unsigned, no adjustment is needed.

@vim-ml
Copy link

vim-ml commented May 22, 2022 via email

@vim-ml
Copy link

vim-ml commented May 22, 2022 via email

@brammool brammool closed this in a061f34 May 22, 2022
@brammool
Copy link
Contributor

brammool commented Oct 11, 2022 via email

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.

6 participants