On Mon, Mar 3, 2014 at 3:32 PM, Richard Biener <richard.guent...@gmail.com> wrote: > On Sun, Mar 2, 2014 at 9:13 PM, Prathamesh Kulkarni > <bilbotheelffri...@gmail.com> wrote: >> Hi, I am an undergraduate student at University of Pune, India, and would >> like to work on moving folding patterns from fold-const.c to gimple. > > I've seen the entry on our GSoC project page and edited it to discourage > people from working on that line. See > > http://gcc.gnu.org/ml/gcc/2014-02/msg00516.html > > for why. I think that open-coding the transforms isn't maintainable > in the long run. > >> If I understand correctly, constant folding is done on GENERIC (by >> routines in fold-const.c), and then GENERIC is lowered to GIMPLE. The >> purpose of this project, >> is to have constant folding to be performed on GIMPLE instead (in >> gimple-fold.c?) >> >> I have a few elementary questions to ask: >> >> a) A contrived example: >> Consider a C expression, a = ~0 (assume a is int) >> In GENERIC, this would roughly be represented as: >> modify_expr<var_decl "a", <bit_not_expr<integer_cst 0>>> >> this gets folded to: >> modify_expr<var_decl "a", integer_cst -1> >> and the corresponding gimple tuple generated is (-fdump-tree-gimple-raw): >> gimple_assign <integer_cst, x, -1, NULL, NULL> >> >> So, instead of folding performed on GENERIC, it should be >> done on GIMPLE. >> So a tuple like the following should be generated by gimplification: >> <bit_not_expr, a, 0, NULL, NULL> >> and folded to (by call to fold_stmt): >> <integer_cst, a, -1, NUL, NULL> >> Is this the expected behavior ? >> >> I have attached a rough/incomplete patch (only stage1 compiled cc1), that >> does the following foldings on bit_not_expr: >> a) ~ INTEGER_CST => folded >> b) ~~x => x >> c) ~(-x) => x - 1 >> (For the moment, I put case BIT_NOT_EXPR: return NULL_TREE >> in fold_unary_loc to avoid folding in GENERIC on bit_not_expr) >> >> Is the patch going in the correct direction ? Or have I completely missed >> the point here ? I would be grateful to receive suggestions, and start >> working >> on a fair patch. > > I think you implement what was suggested by Kai (and previously > by me and Andrew, before I changed my mind). > Hi Richard, Thanks for your reply and for pointing me out to this thread http://gcc.gnu.org/ml/gcc/2014-02/msg00516.html
I agree it's better to generate patterns from a meta-description instead of hand-coding, and the idea seems interesting to me. I was playing around with the patch and did few trivial modifications (please find the patch attached): a) use obstack in parse_c_expr. b) use @ inside c code, instead of directly writing captures (like $<num> in bison): example: /* Match and simplify CST + CST to CST'. */ (define_match_and_simplify baz (PLUS_EXPR INTEGER_CST_P@0 INTEGER_CST_P@1) { int_const_binop (PLUS_EXPR, @0, @1); }) c) Not sure if this is a good idea, conditional matching. for example: /* match (A * B) and simplify to * B if integer_zerop B is true ( A * 0 => 0) * A if integer_onep B is true (A * 1 => A) */ (define_match_and_simplify multexpr (MULT_EXPR integral_op_p@0 integral_op_p@1) [ (integer_zerop@1 @1) (integer_onep@1 @0) ]) Maybe condition can be generalized to be any operand instead of testing predicate on capture operand ? I would be grateful to receive some direction for working on this project. >From the thread, I see a few possibilities: a) Moving patterns from tree-ssa-forwprop b) Extending the DSL (handle commutative operators, conditionally enabling patterns ?) c) Targeting GENERIC (Generating patterns in fold-const.c from the description ?) d) This is a bit silly, but maybe perform more error checking ? for example the following pattern is currently accepted: (define_match px (PLUS_EXPR @0 @1 @2)) I wanted to apply to gsoc for this project and I was wondering if you would you be willing to mentor me if I did? I have a fluent grasp on C and working knowledge of flex, bison, C++, POSIX api, binutils and shell scripting (bash), I have been through most of dragon book, built an interpreter for a "c-like" language and a C-code generator for a toy language similar to python. As far as gcc goes, I had attended IIT Bombay gcc workshop 2013: http://www.cse.iitb.ac.in/grc/gcc-workshop-13/ and have been through the online docs. I have a couple of one-liner patches committed: http://gcc.gnu.org/ml/gcc-patches/2014-02/msg00490.html http://gcc.gnu.org/ml/gcc-patches/2014-02/msg01144.html A few pending patches: http://gcc.gnu.org/ml/gcc-patches/2014-02/msg00143.html http://gcc.gnu.org/ml/gcc-patches/2014-02/msg00143.html http://gcc.gnu.org/ml/gcc-patches/2014-02/msg01220.html http://gcc.gnu.org/ml/gcc/2014-01/msg00268.html and a couple of rejected ones: http://gcc.gnu.org/ml/gcc-patches/2014-02/msg00957.html http://gcc.gnu.org/ml/gcc-patches/2014-02/msg01366.html Please do let me know what you think. Thanks and Regards, Prathamesh > Richard. > >> On the following test-case: >> int main() >> { >> int a, b, c; >> a = ~~~~b; >> c = ~-a; >> return 0; >> } >> >> The following GIMPLE is generated: >> main () >> gimple_bind < >> int D.1748; >> int D.1749; >> int D.1750; >> int D.1751; >> int D.1752; >> int a; >> int b; >> int c; >> >> gimple_assign <var_decl, D.1749, b, NULL, NULL> >> gimple_assign <var_decl, a, D.1749, NULL, NULL> >> gimple_assign <plus_expr, c, a, -1, NULL> >> gimple_assign <integer_cst, D.1752, 0, NULL, NULL> >> gimple_return <D.1752> >>> >> >> The patch generates two tuples for a = ~~~~b, >> where only one is needed, and extra temporaries, which >> are not removed after the folding. How should I go about >> removing that (should I worry about that since subsequent passes, >> shall remove those ?) >> >> b) Some front-ends, C, for example, requires constant folding in certain >> places, >> like case statement. If constant folding is completely moved off to gimple, >> how shall this be handled ? Shall we gimplify the expression immediately if >> it's required to be evaluated ? >> >> Thanks and Regards, >> Prathamesh
Index: gcc/genmatch.c =================================================================== --- gcc/genmatch.c (revision 0) +++ gcc/genmatch.c (working copy) @@ -0,0 +1,610 @@ + +/* Grammar + + capture = '@' number + op = predicate | expr [capture] + match = 'define_match' name expr + c_expr = '{' ... '}' + genexpr = '(' code genop... ')' + genop = capture | genexpr | c_expr + transform = 'define_match_and_transform' name expr genop + + Match (A + B) - B + (define_match plusminus + (PLUS_EXPR (MINUS_EXPR integral_op_p@0 @1) @1)) + + Match and simplify (A + B) - B -> A + (define_match_and_simplify foo + (PLUS_EXPR (MINUS_EXPR integral_op_p@0 @1) @1) + @0) + + Match and simplify (CST + A) + CST to CST' + A + (define_match_and_simplify bar + (PLUS_EXPR INTEGER_CST_P@0 (PLUS_EXPR @1 INTEGER_CST_P@2)) + (PLUS_EXPR { int_const_binop (PLUS_EXPR, captures[0], captures[2]); } @1)) +*/ + +#include "bconfig.h" +#include "system.h" +#include "coretypes.h" +#include <cpplib.h> +#include "errors.h" +#include "hashtab.h" +#include "vec.h" + +enum op_type { OP_PREDICATE, OP_EXPR, OP_CAPTURE, OP_C_EXPR, OP_COND }; + +struct operand { + operand (enum op_type type_) : type (type_) {} + enum op_type type; + virtual void gen_gimple_match (FILE *f, const char *) = 0; + virtual void gen_gimple_transform (FILE *f) = 0; +}; + +struct predicate : public operand +{ + predicate (const char *ident_) : operand (OP_PREDICATE), ident (ident_) {} + const char *ident; + virtual void gen_gimple_match (FILE *f, const char *); + virtual void gen_gimple_transform (FILE *) { gcc_unreachable (); } +}; + +struct expr : public operand +{ + expr (const char *operation_) + : operand (OP_EXPR), operation (operation_), num_ops (0) {} + void append_op (operand *op) { ops[num_ops++] = op; } + const char *operation; + unsigned num_ops; + operand *ops[4]; + virtual void gen_gimple_match (FILE *f, const char *); + virtual void gen_gimple_transform (FILE *f); +}; + +struct c_expr : public operand +{ + c_expr (const char *code_) + : operand (OP_EXPR), code (code_) {} + const char *code; + virtual void gen_gimple_match (FILE *, const char *) { gcc_unreachable (); } + virtual void gen_gimple_transform (FILE *f); +}; + +struct capture : public operand +{ + capture (const char *where_, operand *what_) + : operand (OP_CAPTURE), where (where_), what (what_) {} + const char *where; + operand *what; + virtual void gen_gimple_match (FILE *f, const char *); + virtual void gen_gimple_transform (FILE *f); +}; + +struct condition_pair { + condition_pair (struct predicate *pred_, struct operand *op_, struct capture *capt_) + : pred (pred_), op (op_), capt (capt_) {} + struct predicate *pred; + struct operand *op; + struct capture *capt; +}; + +struct condition : public operand +{ + condition (const vec<struct condition_pair>& conds_) + : operand (OP_COND), conds (conds_) {} + vec<struct condition_pair> conds; + virtual void gen_gimple_match (FILE *f, const char *c) {} + virtual void gen_gimple_transform (FILE *f); +}; + +struct match { + match (const char *name_, struct operand *op_); + const char *name; + struct operand *op; + void gen_gimple_match (FILE *f); + virtual void gen_gimple (FILE *f) { gen_gimple_match (f); } +}; + +struct simplify : public match { + simplify (const char *name_, struct operand *op_, struct operand *op2_) + : match (name_, op_), op2 (op2_) {} + struct operand *op2; + virtual void gen_gimple (FILE *f); +}; + +match::match (const char *name_, operand *op_) +{ + name = name_; + op = op_; +} + +void +predicate::gen_gimple_match (FILE *f, const char *op) +{ + fprintf (f, "if (!%s (%s)) return false;\n", ident, op); +} + +void +expr::gen_gimple_match (FILE *f, const char *name) +{ + fprintf (f, "{\n"); + fprintf (f, "gimple def_stmt = SSA_NAME_DEF_STMT (%s);\n", name); + fprintf (f, "if (is_gimple_assign (def_stmt)\n" + "\t&& gimple_assign_rhs_code (def_stmt) == %s)\n", operation); + fprintf (f, " {\n"); + fprintf (f, "gcc_assert (gimple_num_ops (def_stmt) == %d + 1);\n", num_ops); + for (unsigned i = 0; i < num_ops; ++i) + { + fprintf (f, " {\n"); + fprintf (f, " tree op = gimple_assign_rhs%d (def_stmt);\n", i + 1); + fprintf (f, " if (valueize && TREE_CODE (op) == SSA_NAME)\n"); + fprintf (f, " {\n"); + fprintf (f, " op = valueize (op);\n"); + fprintf (f, " if (!op) return false;\n"); + fprintf (f, " }\n"); + ops[i]->gen_gimple_match (f, "op"); + fprintf (f, " }\n"); + } + fprintf (f, "}\n"); + fprintf (f, "}\n"); +} + +void +expr::gen_gimple_transform (FILE *f) +{ + fprintf (f, "({\n"); + fprintf (f, " if (!seq) return NULL_TREE;\n"); + fprintf (f, " tree ops[%d];\n", num_ops); + for (unsigned i = 0; i < num_ops; ++i) + { + fprintf (f, " ops[%u] = ", i); + ops[i]->gen_gimple_transform (f); + fprintf (f, ";\n"); + } + fprintf (f, " tree res = make_ssa_name (TREE_TYPE (ops[0]), NULL);\n" + " gimple stmt = gimple_build_assign_with_ops (%s, res", + operation); + for (unsigned i = 0; i < num_ops; ++i) + fprintf (f, ", ops[%u]", i); + fprintf (f, ");\n"); + fprintf (f, " gimple_seq_add_stmt_without_update (seq, stmt);\n"); + fprintf (f, " res;\n"); + fprintf (f, "})"); +} + +void +c_expr::gen_gimple_transform (FILE *f) +{ + fputs (code, f); +} + +void +capture::gen_gimple_transform (FILE *f) +{ + fprintf (f, "captures[%s]", this->where); +} + +void +capture::gen_gimple_match (FILE *f, const char *op) +{ + if (this->what) + this->what->gen_gimple_match (f, op); + fprintf (f, "if (!captures[%s])\n" + " captures[%s] = %s;\n" + "else if (captures[%s] != %s)\n" + " return false;", this->where, this->where, op, this->where, op); +} + + + +void +match::gen_gimple_match (FILE *f) +{ + fprintf (f, "bool\nmatch_%s (tree name, tree *captures, tree (*valueize)(tree))\n" + "{\n", this->name); + this->op->gen_gimple_match (f, "name"); + fprintf (f, " return true;\n"); + fprintf (f, "}\n"); +} + +void +condition::gen_gimple_transform (FILE *f) +{ + unsigned i; + unsigned len = this->conds.length (); + + fprintf (f, "({ tree op = NULL_TREE;\n"); + + fprintf (f, "if (%s (", this->conds[0].pred->ident); + this->conds[0].capt->gen_gimple_transform (f); + fprintf (f, "))"); + fprintf (f, "op = "); + this->conds[0].op->gen_gimple_transform (f); + fprintf (f, ";\n"); + + for (i = 1; i < len; i++) + { + fprintf (f, "else if (%s (", this->conds[i].pred->ident); + this->conds[i].capt->gen_gimple_transform (f); + fprintf (f, "))"); + fprintf (f, "op = "); + this->conds[i].op->gen_gimple_transform (f); + fprintf (f, ";\n"); + } + fprintf (f, "op;})\n"); +} + +void +simplify::gen_gimple (FILE *f) +{ + fprintf (f, "static "); + this->gen_gimple_match (f); + fprintf (f, "static tree\n" + "simplify_%s (tree name, gimple_seq *seq, tree (*valueize)(tree))\n" + "{\n", this->name); + fprintf (f, " tree captures[4] = {};\n" + " if (!match_%s (name, captures, valueize)) return NULL_TREE;\n", + this->name); + fprintf (f, " return "); + this->op2->gen_gimple_transform (f); + fprintf (f, ";\n}\n"); +} + +static const cpp_token * +next (cpp_reader *r) +{ + const cpp_token *token; + do + { + token = cpp_get_token (r); + } + while (token->type == CPP_PADDING + && token->type != CPP_EOF); +#if 0 + /* DEBUG */ + if (token->type != CPP_EOF) + printf ("got token '%s'\n", cpp_token_as_text (r, token)); +#endif + return token; +} + +static const cpp_token * +peek (cpp_reader *r) +{ + const cpp_token *token; + unsigned i = 0; + do + { + token = cpp_peek_token (r, i++); + } + while (token->type == CPP_PADDING); +#if 0 + /* DEBUG */ + if (token->type != CPP_EOF) + printf ("next token '%s'\n", cpp_token_as_text (r, token)); +#endif + return token; +} + +static bool +is_ident (const cpp_token *token, const char *id) +{ + return strcmp ((const char *)CPP_HASHNODE (token->val.node.node)->ident.str, + id) == 0; +} + +static size_t +round_alloc_size (size_t s) +{ + return s; +} + + +static const cpp_token * +expect (cpp_reader *r, enum cpp_ttype tk) +{ + const cpp_token *token = next (r); + if (token->type != tk) + fatal ("error: expected %s, got %s", + cpp_type2name (tk, 0), cpp_type2name (token->type, 0)); + + return token; +} + +static void +eat_token (cpp_reader *r, enum cpp_ttype tk) +{ + expect (r, tk); +} + +const char * +get_string (cpp_reader *r) +{ + const cpp_token *token = expect (r, CPP_STRING); + return (const char *)token->val.str.text; +} + +const char * +get_ident (cpp_reader *r) +{ + const cpp_token *token = expect (r, CPP_NAME); + return (const char *)CPP_HASHNODE (token->val.node.node)->ident.str; +} + +const char * +get_number (cpp_reader *r) +{ + const cpp_token *token = expect (r, CPP_NUMBER); + return (const char *)token->val.str.text; +} + +static const char * +parse_operation (cpp_reader *r) +{ + return get_ident (r); +} + +static struct operand * parse_op (cpp_reader *r); + +/* Parse + expr = operation op... */ +static struct operand * +parse_expr (cpp_reader *r) +{ + expr *e = new expr (parse_operation (r)); + do + { + const cpp_token *token = peek (r); + if (token->type == CPP_CLOSE_PAREN) + return e; + e->append_op (parse_op (r)); + } + while (1); + return e; +} + +static struct operand * +parse_capture (cpp_reader *r, operand *op) +{ + eat_token (r, CPP_ATSIGN); + return new capture (get_number (r), op); +} + + +static operand * +parse_c_expr (cpp_reader *r) +{ + /* ??? Use an obstack to build the string. */ + const cpp_token *token; + char *code; + struct obstack ob; + + obstack_init (&ob); + obstack_grow (&ob, "({", 2); + + do + { + token = next (r); + char *tk = (char *)cpp_token_as_text (r, token); + + if (token->flags & PREV_WHITE) + obstack_grow (&ob, " ", 1); + if (tk[0] == '@' && tk[1] == '\0') + { + const char *num = get_number(r); + obstack_grow (&ob, "captures[", strlen ("captures[")); + obstack_grow (&ob, num, strlen (num)); + obstack_grow (&ob, "]", 1); + } + else + obstack_grow (&ob, tk, strlen (tk)); + } + while (token->type != CPP_CLOSE_BRACE); +// code = (char *)xmalloc (code, strlen (code) + 1 + 1); + obstack_grow0 (&ob, ")", 1); + code = xstrdup ((const char *) obstack_finish (&ob)); + obstack_free (&ob, obstack_finish (&ob)); + return new c_expr (code); + +} + +/* Parse + op = predicate | ( expr ) */ + +static struct operand * +parse_op (cpp_reader *r) +{ + const cpp_token *token = peek (r); + struct operand *op = NULL; + if (token->type == CPP_OPEN_PAREN) + { + eat_token (r, CPP_OPEN_PAREN); + op = parse_expr (r); + eat_token (r, CPP_CLOSE_PAREN); + } + else if (token->type == CPP_OPEN_BRACE) + { + eat_token (r, CPP_OPEN_BRACE); + op = parse_c_expr (r); + } + else if (token->type == CPP_NAME) + op = new predicate (get_ident (r)); + else if (token->type == CPP_ATSIGN) + op = parse_capture (r, op); + else + fatal ("expected expression or predicate"); + + token = peek (r); + if (token->type == CPP_ATSIGN + && !(token->flags & PREV_WHITE)) + op = parse_capture (r, op); + + return op; +} + +/* + * opcond: cond | op + * cond: '[' cond-list ']' + * cond-list: condition | + cond-list condition + * condition: '(' predicate capture operand ')' + */ + +static struct operand * +parse_condition_or_op (cpp_reader *r) +{ + const cpp_token *token = peek (r); + struct operand *op = NULL; + vec<struct condition_pair> conds = vNULL; + + if (token->type == CPP_OPEN_SQUARE) + { + eat_token (r, CPP_OPEN_SQUARE); + do + { + eat_token (r, CPP_OPEN_PAREN); + struct predicate *pred = new predicate (get_ident (r)); + struct capture *capt = (struct capture *) parse_capture (r, NULL); + struct operand *result = parse_op (r); + struct condition_pair c(pred, result, capt); + conds.safe_push (c); + eat_token (r, CPP_CLOSE_PAREN); + + token = peek (r); + if (token->type == CPP_CLOSE_SQUARE) + { + eat_token (r, CPP_CLOSE_SQUARE); + break; + } + } + while (1); + op = new condition (conds); + } + else + op = parse_op (r); + + return op; +} + +/* Parse + (define_match "<ident>" + <op>) */ + +static match * +parse_match (cpp_reader *r) +{ + return new match (get_ident (r), parse_op (r)); +} + +/* Parse + (define_match_and_simplify "<ident>" + <op> <op>) */ + +static simplify * +parse_match_and_simplify (cpp_reader *r) +{ + return new simplify (get_ident (r), parse_op (r), parse_condition_or_op (r)); +} + +static void +write_gimple (FILE *f, vec<match *>& matchers, vec<simplify *>& simplifiers) +{ + fprintf (f, +"#include \"config.h\"\n" +"#include \"system.h\"\n" +"#include \"coretypes.h\"\n" +"#include \"tree.h\"\n" +"#include \"stringpool.h\"\n" +"#include \"flags.h\"\n" +"#include \"function.h\"\n" +"#include \"basic-block.h\"\n" +"#include \"tree-ssa-alias.h\"\n" +"#include \"internal-fn.h\"\n" +"#include \"gimple-expr.h\"\n" +"#include \"is-a.h\"\n" +"#include \"gimple.h\"\n" +"#include \"gimple-ssa.h\"\n" +"#include \"tree-ssanames.h\"\n" +"\n" +"#define INTEGER_CST_P(node) TREE_CODE(node) == INTEGER_CST\n" +"#define integral_op_p(node) INTEGRAL_TYPE_P(TREE_TYPE(node))\n" +"\n"); + + /* Write matchers and simplifiers. */ + for (unsigned i = 0; i < matchers.length (); ++i) + matchers[i]->gen_gimple (f); + for (unsigned i = 0; i < simplifiers.length (); ++i) + simplifiers[i]->gen_gimple (f); + + /* Write the catch-all simplifier. */ + fprintf (f, "tree\ngimple_match_and_simplify (tree name, gimple_seq *seq, tree (*valueize)(tree))\n" + "{\n" + " tree res;\n"); + for (unsigned i = 0; i < simplifiers.length (); ++i) + fprintf (f, " res = simplify_%s (name, seq, valueize);\n" + " if (res) return res;\n", simplifiers[i]->name); + fprintf (f, " return NULL_TREE;\n" + "}\n"); +} + +int main(int argc, char **argv) +{ + cpp_reader *r; + const cpp_token *token; + struct line_maps *line_table; + + progname = "genmatch"; + + if (argc != 2) + return 1; + + line_table = XCNEW (struct line_maps); + linemap_init (line_table); + line_table->reallocator = xrealloc; + line_table->round_alloc_size = round_alloc_size; + + r = cpp_create_reader (CLK_GNUC99, NULL, line_table); + + if (!cpp_read_main_file (r, argv[1])) + return 1; + +#if 0 + do + { + const cpp_token *token = cpp_get_token (r); + fprintf (stdout, "'%s'\n", cpp_token_as_text (r, token)); + } + while (token->type != CPP_EOF); +#endif + + vec<match *> matchers = vNULL; + vec<simplify *> simplifiers = vNULL; + + do + { + token = peek (r); + if (token->type == CPP_EOF) + break; + + /* All clauses start with '('. */ + eat_token (r, CPP_OPEN_PAREN); + + token = next (r); + if (is_ident (token, "define_match")) + matchers.safe_push (parse_match (r)); + else if (is_ident (token, "define_match_and_simplify")) + simplifiers.safe_push (parse_match_and_simplify (r)); + else + exit (1); + + eat_token (r, CPP_CLOSE_PAREN); + } + while (1); + + write_gimple (stdout, matchers, simplifiers); + + cpp_finish (r, NULL); + cpp_destroy (r); + + return 0; +} Index: gcc/match.pd =================================================================== --- gcc/match.pd (revision 0) +++ gcc/match.pd (working copy) @@ -0,0 +1,30 @@ +/* Match (A - B) + B only. */ +(define_match plusminus + (PLUS_EXPR (MINUS_EXPR integral_op_p@0 @1) @1)) + +/* Match and simplify (A - B) + B -> A. */ +(define_match_and_simplify foo + (PLUS_EXPR (MINUS_EXPR integral_op_p@0 @1) @1) + @0) + +/* Match and simplify CST + CST to CST'. */ +(define_match_and_simplify baz + (PLUS_EXPR INTEGER_CST_P@0 INTEGER_CST_P@1) + { int_const_binop (PLUS_EXPR, @0, @1); }) + +/* Match and simplify (CST + A) + CST to CST' + A. */ +(define_match_and_simplify bar + (PLUS_EXPR INTEGER_CST_P@0 (PLUS_EXPR @1 INTEGER_CST_P@2)) + (PLUS_EXPR { int_const_binop (PLUS_EXPR, captures[0], captures[2]); } @1)) + +/* match (A * B) and simplify to + * B if integer_zerop B is true (A * 0 => 0) + * A if integer_onep B is true (A * 1 => A) + */ +(define_match_and_simplify multexpr + (MULT_EXPR integral_op_p@0 integral_op_p@1) + [ + (integer_zerop@1 @1) + (integer_onep@1 @0) + ]) +