-
Notifications
You must be signed in to change notification settings - Fork 10
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
Added support for arithmetic with different types #1
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added comments to your code. Regarding tests: I don't really care about the code quality of tests, as long as it is not overly mystic (and requires a lot of time to understand when the test fails)..
pochivm/api_base.h
Outdated
typename = std::enable_if_t<AstTypeHelper::primitive_type_supports_binary_op<T, AstTypeHelper::BinaryOps::ADD>::value> > | ||
Value<typename std::common_type<T, U>::type> operator+(const Value<T> &lhs, const Value<U> &rhs) | ||
{ | ||
using return_type = typename std::common_type<T, U>::type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, when you work on a codebase, your code should follow the existing naming convention and code style.
I'm not saying that my naming convention/code style is good or that you should use it in your own project (actually, I'm using Qt style, which is not really a dominant style), but you should still follow it to be consistent with the rest of the code. So please use 'ReturnType' instead.
pochivm/api_base.h
Outdated
static_assert(std::is_signed<T>::value == std::is_signed<U>::value || | ||
std::is_floating_point<return_type>::value, | ||
"cannot add two values of different signedness"); | ||
if(!std::is_same<T, return_type>::value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The easiest way to avoid bugs is defensive programming: asserts, asserts, more asserts! The goal is that your program never fails elsewhere: if it passes all asserts, it should work correctly; and if it fails somewhere, it should always fail in an assertion, instead of running all the way down and crashes somewhere or produces a wrong result (because in that case you would have to debug where exactly it went wrong).
So please make all your assumptions explicit using asserts, even if they might be semi-obvious to you, or if it has been asserted in the callee (asserting twice doesn't hurt you, and caller-callee assumptions can change at any time).
Specifically, here, the assert you want to add is "rhs has the same type as ReturnType". So you should write
if constexpr(....) {
static_assert(.....);
return ....;
}
pochivm/api_base.h
Outdated
std::is_floating_point<return_type>::value, | ||
"cannot add two values of different signedness"); | ||
if(!std::is_same<T, return_type>::value) | ||
return Value<return_type>(new AstArithmeticExpr(AstArithmeticExprType::ADD, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Always write if (...) { ... }
Do not omit curly bracket. Omitting curly brackets is source of bugs.
test_sanity_arith_expr.cpp
Outdated
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" | ||
#pragma clang diagnostic ignored "-Wdouble-promotion" | ||
|
||
auto expected = lhs + rhs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit suspicious if this would work correctly. If I recall it correctly C++ has the weird thing that promotes everything <32bits to 32 bits (which Pochi currently does not support right now: if you add two uint8_t together you still get a uint8_t, not a uint32_t). So this will likely fail in overflow cases if you add, say, a uint8_t with a uint16_t (e.g 1 + 65535), but I'm not sure.
pochivm/api_base.h
Outdated
return Value<ReturnType>(new AstArithmeticExpr(AstArithmeticExprType::ADD, | ||
lhs.__pochivm_value_ptr, StaticCast<ReturnType>(rhs).__pochivm_value_ptr)); | ||
} | ||
return Value<ReturnType>(new AstArithmeticExpr(AstArithmeticExprType::ADD, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this branch, you should assert that T=U=ReturnType
pochivm/api_base.h
Outdated
"cannot add two values of different signedness"); | ||
if constexpr (!std::is_same<T, ReturnType>::value) | ||
{ | ||
static_assert(std::is_same<ReturnType, U>::value, "rhs type is not the same as return type"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this assertion is different from the above assertion: if this assertion fails, it indicates a bug in our code (not user code). I would recommend add "internal bug: " to the start of the message. Same below.
pochivm/ast_type_helper.h
Outdated
// | ||
template <typename T, typename U> | ||
struct ArithReturnType { | ||
typedef typename std::conditional<sizeof(T) <= sizeof(int16_t) || sizeof(U) <= sizeof(int16_t), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use C++ 'using' instead of C 'typedef': 'using' is a strictly better replacement for 'typedef' in terms of readability.
pochivm/api_base.h
Outdated
lhs.__pochivm_value_ptr, StaticCast<ReturnType>(rhs).__pochivm_value_ptr)); | ||
} | ||
else { | ||
static_assert(std::is_same<T, U>::value, "internal bug: lhs and rhs don't have the same time"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that the two if branches above is already guaranteeing that T=U=ReturnType, but I would still make the assertion be 'std::is_same<T, U>::value && std::is_same<T, ReturnType>::value'. It makes the correctness of your code obvious: the best code is the code that is obviously correct.
test_sanity_arith_expr.cpp
Outdated
#undef F | ||
} | ||
|
||
void TestAdditionUnsignedWithPromotion() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a few lines of comments explaining what you are testing?
I rewrote the tests to include testing of addition with literals. Currently the tests are much less comprehensive than they were and take roughly 3.5 times as long to complete(2000ms vs 7000 ms on my machine because of all the new permutations of types and because of the tests with literals. When I tried to make them more comprehensive/test more values, the runtime becomes way too long. I think the problem is having the literal tests significantly slowed down the process because the function had to be recompiled for every different literal. If you think it would better, I can just restore the old tests and modify those to go through all the different permutations of types. Now that I'm thinking about it, that might just be the better move because those were more comprehensive... |
Thanks for writing all the tests. I haven't reviewed your code yet, but to answer your question: I would recommend to not test LLVM backend (or only test a few of them) for the 'op with literal' case (i.e., keep the 'op with literal' cases, but for those cases only test the interpreter and c&p backend). There are two reasoning behind this: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay (I missed your updated diff). I have added comments in the code.
pochivm/api_base.h
Outdated
{ | ||
static_assert(std::is_same<ReturnType, U>::value, "internal bug: rhs type is not the same as return type"); | ||
return Value<ReturnType>(new AstArithmeticExpr(expr_type, | ||
StaticCast<ReturnType>(lhs).__pochivm_value_ptr, rhs.__pochivm_value_ptr)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
align function parameters please (you have one more space)
@@ -117,111 +117,223 @@ Value<T>::operator Value<U>() const | |||
|
|||
// Arithmetic ops convenience operator overloading | |||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move this comment to the right place, and add comment on what your 'DoArith' is doing
pochivm/api_base.h
Outdated
template<typename T, typename = std::enable_if_t< | ||
AstTypeHelper::primitive_type_supports_binary_op<T, AstTypeHelper::BinaryOps::ADD>::value> > | ||
Value<T> operator+(const Value<T>& lhs, const Value<T>& rhs) | ||
template <AstTypeHelper::BinaryOps Binop, typename T, typename U, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the point of this 'Binop' if you are having an 'expr_type' parameter already? This is extremely fragile.
pochivm/api_base.h
Outdated
{ | ||
return Value<T>(new AstArithmeticExpr(AstArithmeticExprType::ADD, lhs.__pochivm_value_ptr, rhs.__pochivm_value_ptr)); | ||
return DoArith<AstTypeHelper::BinaryOps::ADD>(lhs, rhs, AstArithmeticExprType::ADD); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said above, this is very fragile: you need to specify the 'ADD' twice and they must be kept in sync. And even worse, your 'DoArith' function does not even have an assertion that asserts that they are in sync. (the fix is not to add an assertion though. You should remove the need to pass one of the parameter.)
pochivm/api_base.h
Outdated
else if constexpr (!std::is_same<U, ReturnType>::value) | ||
{ | ||
static_assert(std::is_same<ReturnType, T>::value, "internal bug: lhs type is not the same as the computed common type"); | ||
ReturnType casted_rhs = static_cast<ReturnType>(rhs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use tab to indent, use space. As you can see, using tabs works on your computer but fails to indent correctly here on github (if you are using an IDE, you can usually find an option to change this behavior).
pochivm/api_base.h
Outdated
|
||
// Do arithmetic of form runtime value OP literal | ||
// | ||
template <AstTypeHelper::BinaryOps Binop, typename T, typename U, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you reuse the logic of DoArith, so that you write something like return DoArith(lhs, Literal<U>(rhs), expr_type)
?
I didn't try myself so let me know if you think this approach could not work.
pochivm/api_base.h
Outdated
|
||
// Do arithmetic of form literal OP runtime value | ||
// | ||
template <AstTypeHelper::BinaryOps Binop, typename T, typename U, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same question as above
test_sanity_arith_expr.cpp
Outdated
#include "test_util_helper.h" | ||
|
||
using namespace PochiVM; | ||
|
||
// TODO: Test comparison ops, unsigned math, add back all old tests that I deleted, | ||
// make signed tests more comprehensive, literal tests, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
address the TODO?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update. I have added comments.
pochivm/api_base.h
Outdated
@@ -11,12 +11,18 @@ | |||
namespace PochiVM | |||
{ | |||
|
|||
template<typename T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do you need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Literal returns a Value so Value has to be forward declared.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's why I'm saying if you can just move the 'Literal' function up a bit..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see
pochivm/api_base.h
Outdated
template<typename T> | ||
class Reference; | ||
|
||
template<typename T> | ||
class ConstPrimitiveReference; | ||
|
||
template<typename T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you just move the 'literal' function upper a bit instead of having a forward definition?
pochivm/api_base.h
Outdated
{ | ||
return Value<T>(new AstArithmeticExpr(AstArithmeticExprType::ADD, lhs.__pochivm_value_ptr, new AstLiteralExpr(TypeId::Get<T>(), &rhs))); | ||
return DoArith<AstArithmeticExprType::ADD>(lhs, Literal<U>(rhs)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one issue with your current approach is that code like 'a+1' where a is int64_t would generate AST like Add(a, StaticCast<uint64_t>(Literal<int32_t>(1)), which is kind of bad. You can fix this by doing the rewrite in DoArith. (i.e. in DoArith, check if the side to be StaticCast'ed is a Literal, and if it is, directly cast its content).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how I would directly cast a literal's content. Do you think I should add a GetAs<T>() method to the AstLiteralExpr class that returns a literal's value in the form of type T? Then I can cast that value to the desired type and put that in a new Literal? Alternatively, I could just go back to the old method with multiple overloads for ltierals on the left/right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes that's definitely one way to do it.
pochivm/api_base.h
Outdated
template <AstArithmeticExprType expr_type, typename T, typename U, | ||
typename = std::enable_if_t<AstTypeHelper::primitive_type_supports_arithmetic_expr_type<T, expr_type>::value>, | ||
typename = std::enable_if_t<AstTypeHelper::primitive_type_supports_arithmetic_expr_type<U, expr_type>::value> > | ||
Value<typename AstTypeHelper::ArithReturnType<T, U>::type> DoArith(const Value<T>& lhs, const Value<U>& rhs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'DoArith' name is very blurry (one cannot understand or even guess what this function is doing from its name). Can you change it to a more descriptive name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added comments.
pochivm/api_base.h
Outdated
static_assert(AstTypeHelper::may_static_cast<T, U>::value, "cannot static_cast T to U"); | ||
if(src.__pochivm_value_ptr->GetAstNodeType() == AstNodeType::AstLiteralExpr) | ||
{ | ||
return Literal<U>(static_cast<U>(reinterpret_cast<AstLiteralExpr *>(src.__pochivm_value_ptr)->GetAs<T>())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please go to cppreference.com and learn the different types of CPP casts. Specifically, reinterpret_cast is only used to reinterpret a pointer as an integer or vice versa.
The type of cast you want to do here is a downcast, so you should use either static_cast (silently breaks down if the type is wrong) or dynamic_cast (gracefully provides a runtime error, but incurs a large runtime cost). In our system this is addressed by our assert_cast helper, which does dynamic_cast in debug build but static_cast in non-debug build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as a side note, using reinterpret_cast
to do upcast or downcast is not a style issue but a a correctness issue because it breaks down if the cast results in a pointer shift, see example below:
struct A { int x; };
struct B { int y; };
struct C : A, B {};
C* c = new C();
c->x = 1; c->y = 2;
B* b1 = static_cast<B*>(c);
printf("%d\n", b1->y); // gives you 2 correctly
B* b2 = reinterpret_cast<B*>(c);
printf("%d\n", b2->y); // gives you 1 instead of 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry my bad. That was a stupid mistake
|
||
// Helper for Arithmetic ops operator overloading. Returns an arithmetic expression of the form | ||
// lhs OP rhs where OP is the operator specified by `ExprType` | ||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should note in the comment that it casts the operands according to the C rule. I would also rename the function to something like 'CastOperandAndDoArithmetic'
pochivm/api_base.h
Outdated
@@ -17,6 +17,7 @@ class Reference; | |||
template<typename T> | |||
class ConstPrimitiveReference; | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove
I also added checks to make sure we don't compare bools to non-bools(like 3 == true). I would assume that this is desired behavior, but C++ does allow such comparisons, so I can remove those if we wish to remain consistent with C/C++ behavior |
pochivm/api_base.h
Outdated
template<typename T> | ||
class ConstPrimitiveReference |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain what did you do in this commit? Is it undoing a previous commit you did? Since I cannot see its effect in the final "file changes"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Earlier you highlighted the extra newline under the ConstPrimitiveReference forward declaration and told me to remove it. In the previous commit I misinterpreted it as you telling me to remove the whole ConstPrimitiveReference forward declaration and move the actual class up there. But then I realized you had only highlighted that one newline. So I restored the old forward declaration and just deleted that one newline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm beside the nit comment. Well done!
pochivm/api_base.h
Outdated
static_assert(AstTypeHelper::may_static_cast<T, U>::value, "cannot static_cast T to U"); | ||
if(src.__pochivm_value_ptr->GetAstNodeType() == AstNodeType::AstLiteralExpr) | ||
{ | ||
return Literal<U>(static_cast<U>(assert_cast<AstLiteralExpr *>(src.__pochivm_value_ptr)->template GetAs<T>())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove the space between in AstLiteralExpr *
to be consistent with the existing code style
Can you rename your commit so it reflect the stuffs you did? (you actually supported not only addition but arithmetics in general, and also a few helpers like C++ Literal op Value) |
Adds support for operations like int16_t + int64_t or uint8_t + double(the operand with the smaller size is promoted to the operand with the larger size) and tests for this new functionality. Also adds a few helper functions including StaticCastOrConvertLiteral, CastOperandAndDoArithmetic, and CastOperandAndDoComparison. Also adds a GetAs<T> member to the AstLiteralExpression to get the value of a literal as type T
6519c6d
to
2e99da4
Compare
Change Pochi addition semantics to implicitly convert integers/floats/doubles when adding different types. Doesn't work for 8 bit and 16 bit values.
I'm not sure if I should refactor the tests so that they are easier to reuse if we were to ever add implicit conversion to other operations or if we're planning on doing that in the near future.