Message ID | alpine.DEB.2.02.1210161606580.7761@stedding.saclay.inria.fr |
---|---|
State | New |
Headers | show |
On 10/17/2012 12:56 PM, Marc Glisse wrote: > + if (!COMPARISON_CLASS_P (arg1)) > + { > + if (TYPE_UNSIGNED (TREE_TYPE (arg1_type))) > + { > + arg1_type = signed_type_for (arg1_type); > + arg1 = convert (arg1_type, arg1); > + } > + arg1 = build2 (LT_EXPR, arg1_type, arg1, > + build_zero_cst (arg1_type)); > + } Why LT_EXPR rather than NE_EXPR? Jason
On Wed, 17 Oct 2012, Jason Merrill wrote: > On 10/17/2012 12:56 PM, Marc Glisse wrote: >> + if (!COMPARISON_CLASS_P (arg1)) >> + { >> + if (TYPE_UNSIGNED (TREE_TYPE (arg1_type))) >> + { >> + arg1_type = signed_type_for (arg1_type); >> + arg1 = convert (arg1_type, arg1); >> + } >> + arg1 = build2 (LT_EXPR, arg1_type, arg1, >> + build_zero_cst (arg1_type)); >> + } > > Why LT_EXPR rather than NE_EXPR? Assuming we try to follow OpenCL: 6.3i The ternary selection operator (?:) operates on three expressions (exp1 ? exp2 : exp3). This operator evaluates the first expression exp1, which can be a scalar or vector result except float. If the result is a scalar value then it selects to evaluate the second expression if the result compares unequal to 0, otherwise it selects to evaluate the third expression. If the result is a vector value, then this is equivalent to calling select(exp3, exp2, exp1). The select function is described in table 6.14. The second and third expressions can be any type, as long their types match, or there is a conversion in section 6.2.1 Implicit Conversions that can be applied to one of the expressions to make their types match, or one is a vector and the other is a scalar and the scalar may be subject to the usual arithmetic conversion to the element type used by the vector operand and widened to the same type as the vector type. This resulting matching type is the type of the entire expression. table 6.14: gentype select (gentype a, gentype b, igentype c) For each component of a vector type, result[i] = if MSB of c[i] is set ? b[i] : a[i]. === We could deviate from OpenCL in a few places though, if you prefer. Note that operator! (not implemented) still means == 0. Thanks for the quick comment despite the meeting,
On 10/17/2012 10:30 PM, Marc Glisse wrote: > For each component of a vector type, > result[i] = if MSB of c[i] is set ? b[i] : a[i]. Curious. Do you know why they produce and expect -1 for true? It certainly seems to be a deliberate design choice, so I wonder what motivated it. I think people will expect a >0 value to be considered true, so I am inclined to deviate in this case. Jason
On Wed, 17 Oct 2012, Jason Merrill wrote: > On 10/17/2012 10:30 PM, Marc Glisse wrote: >> For each component of a vector type, >> result[i] = if MSB of c[i] is set ? b[i] : a[i]. > > Curious. Do you know why they produce and expect -1 for true? It certainly > seems to be a deliberate design choice, so I wonder what motivated it. (just guessing) First a look at hardware. Vector units in x86, power and arm all produce -1 from comparisons. For selection, arm expects a vector of 0 and -1, power looks at x!=0, and x86 at x<0 (so opencl matches x86). A vector of -1 can be convenient on platforms without a selection instruction so it can be implemented with and+not+or. Looking at the MSB has the advantage (compared to !=0) that using & and | has the expected result. In the middle-end, VEC_COND_EXPR is documented to take a vector of 0 and -1, and at expansion time, it gives vcond(x<0,y,z) to the target if its first argument is not a comparison. > I think people will expect a >0 value to be considered true, so I am inclined > to deviate in this case. Clang's webpage says they support ?: for vector types, but my version (3.1) rejects it, I'll compile a new one to check what they do.
On 10/17/2012 11:15 PM, Marc Glisse wrote: > In the middle-end, VEC_COND_EXPR is documented to take a vector of 0 and > -1, and at expansion time, it gives vcond(x<0,y,z) to the target if its > first argument is not a comparison. Maybe we should leave the first argument alone and let the middle end mess with it, then? Jason
(Adding Joseph in Cc: because we'll want the same behavior in C afterwards. Conversation starts here: http://gcc.gnu.org/ml/gcc-patches/2012-10/msg01665.html ) On Thu, 18 Oct 2012, Marc Glisse wrote: > Clang's webpage says they support ?: for vector types, but my version (3.1) > rejects it, I'll compile a new one to check what they do. They implement ?: for vectors only in OpenCL mode, and in that mode implement the OpenCL semantics (<0). OpenCL can be confusing. x?y:z means (x<0)?y:z, but !x means x==0 and x&&y seems to mean (x!=0)&(y!=0) (the text is not extremely clear). On Wed, 17 Oct 2012, Jason Merrill wrote: > On 10/17/2012 11:15 PM, Marc Glisse wrote: >> In the middle-end, VEC_COND_EXPR is documented to take a vector of 0 and >> -1, and at expansion time, it gives vcond(x<0,y,z) to the target if its >> first argument is not a comparison. I shouldn't have mentioned the <0 in vcond which is rather random: x is necessarily a vector of -1 and 0 there, so anything like <0, !=0, ==-1 is equivalent, we just need one because of a currently missing target interface. > Maybe we should leave the first argument alone and let the middle end mess > with it, then? VEC_COND_EXPR only accepts a vector of -1 and 0, so we can't just pass arbitrary input from the front-end. Richard B didn't want to make VEC_COND_EXPR more general either. Besides, I believe it is the front-end's responsibility to decide this. If we don't want to decide on a specific semantic, I think I'd rather sorry() if a user passes an argument that is not directly a comparison (with a note explaining that they can write <0 for OpenCL-like behavior) than have an unspecified behavior. So between the following: a) x?y:z means (x<0)?y:z b) x?y:z means (x!=0)?y:z c) x?y:z is rejected, only things like (x==t)?y:z are accepted d) other is the choice still b) ? That's an easy one, only 2 characters to change in the patch (assuming the rest is correct) :-) (well, no, actually, I'd also remove the conversion to a signed type)
On 10/19/2012 04:40 PM, Marc Glisse wrote: > So between the following: > a) x?y:z means (x<0)?y:z > b) x?y:z means (x!=0)?y:z > c) x?y:z is rejected, only things like (x==t)?y:z are accepted > d) other > > is the choice still b) ? That's an easy one, only 2 characters to change > in the patch (assuming the rest is correct) :-) That would be my inclination, but I'm not a vector programming expert, so I guess let's go with the OpenCL semantics. Jason
On Wed, 24 Oct 2012, Jason Merrill wrote: > On 10/19/2012 04:40 PM, Marc Glisse wrote: >> So between the following: >> a) x?y:z means (x<0)?y:z >> b) x?y:z means (x!=0)?y:z >> c) x?y:z is rejected, only things like (x==t)?y:z are accepted >> d) other >> >> is the choice still b) ? That's an easy one, only 2 characters to change >> in the patch (assuming the rest is correct) :-) > > That would be my inclination, but I'm not a vector programming expert, I'm not an expert at all either, and I find the OpenCL semantics quite confusing, where truth is implicitly x<0 for x?y:z but x!=0 for x&&y or !x, so in particular (x&&y)?z:t is not equivalent to x?y?z:t:t, and I am quite happy with your suggestion to deviate. I was surprised because I hadn't considered the possibility, but now I actually like it better than OpenCL ;-) > so I guess let's go with the OpenCL semantics. Ok. I take it as: let's wait a few days in case people want to comment, then you'll review the original patch? Thanks,
On Wed, Oct 24, 2012 at 08:36:19PM +0200, Marc Glisse wrote: > On Wed, 24 Oct 2012, Jason Merrill wrote: > > >On 10/19/2012 04:40 PM, Marc Glisse wrote: > >>So between the following: > >>a) x?y:z means (x<0)?y:z > >>b) x?y:z means (x!=0)?y:z > >>c) x?y:z is rejected, only things like (x==t)?y:z are accepted > >>d) other > >> > >>is the choice still b) ? That's an easy one, only 2 characters to change > >>in the patch (assuming the rest is correct) :-) > > > >That would be my inclination, but I'm not a vector programming expert, > > I'm not an expert at all either, and I find the OpenCL semantics > quite confusing, where truth is implicitly x<0 for x?y:z but x!=0 > for x&&y or !x, so in particular (x&&y)?z:t is not equivalent to > x?y?z:t:t, and I am quite happy with your suggestion to deviate. I > was surprised because I hadn't considered the possibility, but now I > actually like it better than OpenCL ;-) > > >so I guess let's go with the OpenCL semantics. > > Ok. I take it as: let's wait a few days in case people want to > comment, then you'll review the original patch? I'd prefer b) as well, but I'm not a vector programming expert either. Jakub
My guess for the reason why OpenCL has the semantics it does is that if you stored a boolean result in a variable earlier and then use it as the ? condition, that would require an extra comparison whereas if it's already a vector of 0 and -1 as expected it can be used directly. Jason
On Wed, 24 Oct 2012, Jason Merrill wrote: > My guess for the reason why OpenCL has the semantics it does is that if you > stored a boolean result in a variable earlier and then use it as the ? > condition, that would require an extra comparison whereas if it's already a > vector of 0 and -1 as expected it can be used directly. I don't understand how < 0 differs from != 0 for that purpose. In both cases, the front-end will produce the extra comparison, and the middle-end will need an optimization to detect "truth values" (vectors of -1 and 0) and simplify trivial operations on those (<0, !=0, etc). (at a point, based on Richard's words but apparently not on his suggestion, I was tempted to introduce a new boolean vector type, whose only values are 0 and -1, but that seems a bit complicated and doesn't answer whether x?y:z should mean x<0 or x!=0)
On Oct 24, 2012, at 12:38 PM, Jakub Jelinek <jakub@redhat.com> wrote: > On Wed, Oct 24, 2012 at 08:36:19PM +0200, Marc Glisse wrote: >> On Wed, 24 Oct 2012, Jason Merrill wrote: >> >>> On 10/19/2012 04:40 PM, Marc Glisse wrote: >>>> So between the following: >>>> a) x?y:z means (x<0)?y:z >>>> b) x?y:z means (x!=0)?y:z >>>> c) x?y:z is rejected, only things like (x==t)?y:z are accepted >>>> d) other >>>> >>>> is the choice still b) ? That's an easy one, only 2 characters to change >>>> in the patch (assuming the rest is correct) :-) >>> >>> That would be my inclination, but I'm not a vector programming expert, >> >> I'm not an expert at all either, and I find the OpenCL semantics >> quite confusing, where truth is implicitly x<0 for x?y:z but x!=0 >> for x&&y or !x, so in particular (x&&y)?z:t is not equivalent to >> x?y?z:t:t, and I am quite happy with your suggestion to deviate. I >> was surprised because I hadn't considered the possibility, but now I >> actually like it better than OpenCL ;-) >> >>> so I guess let's go with the OpenCL semantics. >> >> Ok. I take it as: let's wait a few days in case people want to >> comment, then you'll review the original patch? > > I'd prefer b) as well, but I'm not a vector programming expert either. Well, I suspect the OpenCL community had a ton of people sweat over the details and take into consideration the realities and the needs of people. I'd like to believe they had more people in on this and that this was a compromise for someones vector unit. I like b myself, however, following OpenCL I think might be better. Tough choice. The problem is, what if your vector unit produces 0, 1, to be compatible with C? Suddenly, spilling this onto ? is annoying, both because it doesn't match hardware, nor the expected semantics of a person that just knows C. Maybe we run a poll of vector units that prefer -1 or prefer 1 for true, and then decide. SSE has CMPPS, which likes the -1. Altivec's vec_cmpgt says true is all bits 1. Gosh, I guess we can stop there. Neon, for fun VCGE is defined to set to all ones for true. OpenCL it is.
On Wed, 24 Oct 2012, Mike Stump wrote: > Well, I suspect the OpenCL community had a ton of people sweat over the > details and take into consideration the realities and the needs of > people. I'd like to believe they had more people in on this and that > this was a compromise for someones vector unit. Intel's, apparently... > The problem is, what if your vector unit produces 0, 1, to be compatible > with C? Suddenly, spilling this onto ? is annoying, both because it > doesn't match hardware, nor the expected semantics of a person that just > knows C. Maybe we run a poll of vector units that prefer -1 or prefer 1 > for true, and then decide. SSE has CMPPS, which likes the -1. > Altivec's vec_cmpgt says true is all bits 1. Gosh, I guess we can stop > there. Neon, for fun VCGE is defined to set to all ones for true. > OpenCL it is. We already decided that comparisons return -1, most processors agree on that except sparc, which doesn't return a vector at all. The question is about the selection instructions. {-2,-1,0,1} ? {x,x,x,x} : {y,y,y,y} OpenCL says this should be {x,x,y,y}. We are considering making it {x,x,y,x} instead. Hardware selection instructions vary a lot. OpenCL follows x86, what we are considering matches Power IIRC, and ARM only has a bitwise selection (I only quickly glanced at all of these, I may have read them wrong). I am fine with both alternatives, but the choice is important...
On 10/24/2012 04:15 PM, Marc Glisse wrote: > I don't understand how < 0 differs from != 0 for that purpose. In both > cases, the front-end will produce the extra comparison, and the > middle-end will need an optimization to detect "truth values" (vectors > of -1 and 0) and simplify trivial operations on those (<0, !=0, etc). True; if we aren't going to assume that the operand is already a truthvalue vector, it would be the same. So let's go with != 0 after all. > (at a point, based on Richard's words but apparently not on his > suggestion, I was tempted to introduce a new boolean vector type, whose > only values are 0 and -1, but that seems a bit complicated and doesn't > answer whether x?y:z should mean x<0 or x!=0) That would make sense to me; it would avoid the extra comparison if the condition is a boolean vector. Jason
On Wed, Oct 24, 2012 at 10:41 PM, Marc Glisse <marc.glisse@inria.fr> wrote: > On Wed, 24 Oct 2012, Mike Stump wrote: > >> Well, I suspect the OpenCL community had a ton of people sweat over the >> details and take into consideration the realities and the needs of people. >> I'd like to believe they had more people in on this and that this was a >> compromise for someones vector unit. > > > Intel's, apparently... > > >> The problem is, what if your vector unit produces 0, 1, to be compatible >> with C? Suddenly, spilling this onto ? is annoying, both because it doesn't >> match hardware, nor the expected semantics of a person that just knows C. >> Maybe we run a poll of vector units that prefer -1 or prefer 1 for true, and >> then decide. SSE has CMPPS, which likes the -1. Altivec's vec_cmpgt says >> true is all bits 1. Gosh, I guess we can stop there. Neon, for fun VCGE is >> defined to set to all ones for true. OpenCL it is. > > > We already decided that comparisons return -1, most processors agree on that > except sparc, which doesn't return a vector at all. The question is about > the selection instructions. > > {-2,-1,0,1} ? {x,x,x,x} : {y,y,y,y} > > OpenCL says this should be {x,x,y,y}. We are considering making it {x,x,y,x} > instead. Hardware selection instructions vary a lot. OpenCL follows x86, > what we are considering matches Power IIRC, and ARM only has a bitwise > selection (I only quickly glanced at all of these, I may have read them > wrong). > > > I am fine with both alternatives, but the choice is important... I would go with != 0, too. It is what people would expect. We can deviate when someone introduces -fopencl. Richard. > -- > Marc Glisse
Index: tree.c =================================================================== --- tree.c (revision 192542) +++ tree.c (working copy) @@ -10229,20 +10229,31 @@ widest_int_cst_value (const_tree x) /* If TYPE is an integral or pointer type, return an integer type with the same precision which is unsigned iff UNSIGNEDP is true, or itself if TYPE is already an integer type of signedness UNSIGNEDP. */ tree signed_or_unsigned_type_for (int unsignedp, tree type) { if (TREE_CODE (type) == INTEGER_TYPE && TYPE_UNSIGNED (type) == unsignedp) return type; + if (TREE_CODE (type) == VECTOR_TYPE) + { + tree inner = TREE_TYPE (type); + tree inner2 = signed_or_unsigned_type_for (unsignedp, inner); + if (!inner2) + return NULL_TREE; + if (inner == inner2) + return type; + return build_vector_type (inner2, TYPE_VECTOR_SUBPARTS (type)); + } + if (!INTEGRAL_TYPE_P (type) && !POINTER_TYPE_P (type)) return NULL_TREE; return build_nonstandard_integer_type (TYPE_PRECISION (type), unsignedp); } /* If TYPE is an integral or pointer type, return an integer type with the same precision which is unsigned, or itself if TYPE is already an unsigned integer type. */ Index: c-family/c-common.c =================================================================== --- c-family/c-common.c (revision 192542) +++ c-family/c-common.c (working copy) @@ -11467,20 +11467,22 @@ scalar_to_vector (location_t loc, enum t return stv_firstarg; } break; case BIT_IOR_EXPR: case BIT_XOR_EXPR: case BIT_AND_EXPR: integer_only_op = true; /* ... fall through ... */ + case VEC_COND_EXPR: + case PLUS_EXPR: case MINUS_EXPR: case MULT_EXPR: case TRUNC_DIV_EXPR: case CEIL_DIV_EXPR: case FLOOR_DIV_EXPR: case ROUND_DIV_EXPR: case EXACT_DIV_EXPR: case TRUNC_MOD_EXPR: case FLOOR_MOD_EXPR: Index: cp/call.c =================================================================== --- cp/call.c (revision 192542) +++ cp/call.c (working copy) @@ -4366,43 +4366,120 @@ build_conditional_expr_1 (tree arg1, tre pedwarn (input_location, OPT_Wpedantic, "ISO C++ forbids omitting the middle term of a ?: expression"); /* Make sure that lvalues remain lvalues. See g++.oliva/ext1.C. */ if (real_lvalue_p (arg1)) arg2 = arg1 = stabilize_reference (arg1); else arg2 = arg1 = save_expr (arg1); } + /* If something has already gone wrong, just pass that fact up the + tree. */ + if (error_operand_p (arg1) + || error_operand_p (arg2) + || error_operand_p (arg3)) + return error_mark_node; + + orig_arg2 = arg2; + orig_arg3 = arg3; + + if (VECTOR_INTEGER_TYPE_P (TREE_TYPE (arg1))) + { + arg1 = force_rvalue (arg1, complain); + arg2 = force_rvalue (arg2, complain); + arg3 = force_rvalue (arg3, complain); + + tree arg1_type = TREE_TYPE (arg1); + arg2_type = TREE_TYPE (arg2); + arg3_type = TREE_TYPE (arg3); + + if (TREE_CODE (arg2_type) != VECTOR_TYPE + && TREE_CODE (arg3_type) != VECTOR_TYPE) + { + if (complain & tf_error) + error ("at least one operand of a vector conditional operator " + "must be a vector"); + return error_mark_node; + } + + if ((TREE_CODE (arg2_type) == VECTOR_TYPE) + != (TREE_CODE (arg3_type) == VECTOR_TYPE)) + { + enum stv_conv convert_flag = + scalar_to_vector (input_location, VEC_COND_EXPR, arg2, arg3, + complain & tf_error); + + switch (convert_flag) + { + case stv_error: + return error_mark_node; + case stv_firstarg: + { + arg2 = convert (TREE_TYPE (arg3_type), arg2); + arg2 = build_vector_from_val (arg3_type, arg2); + arg2_type = TREE_TYPE (arg2); + break; + } + case stv_secondarg: + { + arg3 = convert (TREE_TYPE (arg2_type), arg3); + arg3 = build_vector_from_val (arg2_type, arg3); + arg3_type = TREE_TYPE (arg3); + break; + } + default: + break; + } + } + + if (!same_type_p (arg2_type, arg3_type) + || TYPE_VECTOR_SUBPARTS (arg1_type) + != TYPE_VECTOR_SUBPARTS (arg2_type) + || TYPE_SIZE (arg1_type) != TYPE_SIZE (arg2_type)) + { + if (complain & tf_error) + error ("incompatible vector types in conditional expression: " + "%qT, %qT and %qT", TREE_TYPE (arg1), TREE_TYPE (orig_arg2), + TREE_TYPE (orig_arg3)); + return error_mark_node; + } + + if (!COMPARISON_CLASS_P (arg1)) + { + if (TYPE_UNSIGNED (TREE_TYPE (arg1_type))) + { + arg1_type = signed_type_for (arg1_type); + arg1 = convert (arg1_type, arg1); + } + arg1 = build2 (LT_EXPR, arg1_type, arg1, + build_zero_cst (arg1_type)); + } + return build3 (VEC_COND_EXPR, arg2_type, arg1, arg2, arg3); + } + /* [expr.cond] The first expression is implicitly converted to bool (clause _conv_). */ arg1 = perform_implicit_conversion_flags (boolean_type_node, arg1, complain, LOOKUP_NORMAL); - - /* If something has already gone wrong, just pass that fact up the - tree. */ - if (error_operand_p (arg1) - || error_operand_p (arg2) - || error_operand_p (arg3)) + if (error_operand_p (arg1)) return error_mark_node; /* [expr.cond] If either the second or the third operand has type (possibly cv-qualified) void, then the lvalue-to-rvalue (_conv.lval_), array-to-pointer (_conv.array_), and function-to-pointer (_conv.func_) standard conversions are performed on the second and third operands. */ - orig_arg2 = arg2; - orig_arg3 = arg3; arg2_type = unlowered_expr_type (arg2); arg3_type = unlowered_expr_type (arg3); if (VOID_TYPE_P (arg2_type) || VOID_TYPE_P (arg3_type)) { /* Do the conversions. We don't these for `void' type arguments since it can't have any effect and since decay_conversion does not handle that case gracefully. */ if (!VOID_TYPE_P (arg2_type)) arg2 = decay_conversion (arg2, complain); if (!VOID_TYPE_P (arg3_type)) Index: cp/typeck.c =================================================================== --- cp/typeck.c (revision 192542) +++ cp/typeck.c (working copy) @@ -5803,21 +5803,22 @@ build_x_conditional_expr (location_t loc || (op1 && type_dependent_expression_p (op1)) || type_dependent_expression_p (op2)) return build_min_nt_loc (loc, COND_EXPR, ifexp, op1, op2); ifexp = build_non_dependent_expr (ifexp); if (op1) op1 = build_non_dependent_expr (op1); op2 = build_non_dependent_expr (op2); } expr = build_conditional_expr (ifexp, op1, op2, complain); - if (processing_template_decl && expr != error_mark_node) + if (processing_template_decl && expr != error_mark_node + && TREE_CODE (expr) != VEC_COND_EXPR) { tree min = build_min_non_dep (COND_EXPR, expr, orig_ifexp, orig_op1, orig_op2); /* In C++11, remember that the result is an lvalue or xvalue. In C++98, lvalue_kind can just assume lvalue in a template. */ if (cxx_dialect >= cxx0x && lvalue_or_rvalue_with_address_p (expr) && !lvalue_or_rvalue_with_address_p (min)) TREE_TYPE (min) = cp_build_reference_type (TREE_TYPE (min), !real_lvalue_p (expr)); Index: testsuite/g++.dg/ext/vector19.C =================================================================== --- testsuite/g++.dg/ext/vector19.C (revision 0) +++ testsuite/g++.dg/ext/vector19.C (revision 0) @@ -0,0 +1,56 @@ +/* { dg-do compile { target i?86-*-* x86_64-*-* } } */ +/* { dg-options "-std=c++11 -mavx2" } */ + +// The target restrictions and the -mavx2 flag are meant to disappear +// once vector lowering is in place. + +typedef double vec __attribute__((vector_size(2*sizeof(double)))); +typedef signed char vec2 __attribute__((vector_size(16))); +typedef unsigned char vec2u __attribute__((vector_size(16))); + +void f (vec *x, vec *y, vec *z) +{ + *x = (*y < *z) ? *x : *y; +} + +void g (vec *x, vec *y, vec *z) +{ + *x = (*y < *z) ? *x : 42; +} + +void h (vec *x, vec *y, vec *z) +{ + *x = (*y < *z) ? 3. : *y; +} + +void i1 (vec *x, vec *y, vec *z) +{ + auto c = *y < *z; + *x = c ? *x : *y; +} + +void i2 (vec2 *x, vec2 *y, vec2u *z) +{ + *x = *y ? *x : *y; + *y = *z ? *x : *y; +} + +void j (vec2 *x, vec2 *y, vec2 *z, vec *t) +{ + *x = (*y < *z) ? *x : 4.2; /* { dg-error "" } */ + *y = (*x < *z) ? 2.5 : *y; /* { dg-error "" } */ + *t = *t ? *t : *t; /* { dg-error "" } */ + *z = (*x < *z) ? '1' : '0'; /* { dg-error "" } */ + // The last one may eventually be accepted. +} + +template <class A, class B> +auto k (A *a, B b) -> decltype (*a ? *a : b); + +void k (...) {} + +void l (vec2 *v, double x) +{ + k (v, x); +} +