/*
Copyright (C) 2021-2022 Free Software Foundation, Inc.
This file is part of GAS, the GNU Assembler.
GAS is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GAS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING3. If not,
see <http://www.gnu.org/licenses/>. */
%{
#include "as.h"
#include "loongarch-lex.h"
#include "loongarch-parse.h"
static void yyerror (const char *s ATTRIBUTE_UNUSED)
{
};
int yylex (void);
static struct reloc_info *top, *end;
static expressionS const_0 =
{
.X_op = O_constant,
.X_add_number = 0
};
static int
is_const (struct reloc_info *info)
{
return (info->type == BFD_RELOC_LARCH_SOP_PUSH_ABSOLUTE
&& info->value.X_op == O_constant);
}
int
loongarch_parse_expr (const char *expr,
struct reloc_info *reloc_stack_top,
size_t max_reloc_num,
size_t *reloc_num,
offsetT *imm)
{
int ret;
struct yy_buffer_state *buffstate;
top = reloc_stack_top;
end = top + max_reloc_num;
buffstate = yy_scan_string (expr);
ret = yyparse ();
if (ret == 0)
{
if (is_const (top - 1))
*imm = (--top)->value.X_add_number;
else
*imm = 0;
*reloc_num = top - reloc_stack_top;
}
yy_delete_buffer (buffstate);
return ret;
}
static void
emit_const (offsetT imm)
{
if (end <= top)
as_fatal (_("expr too huge"));
top->type = BFD_RELOC_LARCH_SOP_PUSH_ABSOLUTE;
top->value.X_op = O_constant;
top->value.X_add_number = imm;
top++;
}
static const char *
my_getExpression (expressionS *ep, const char *str)
{
char *save_in, *ret;
if (*str == ':')
{
unsigned long j;
char *str_1 = (char *) str;
str_1++;
j = strtol (str_1, &str_1, 10);
get_internal_label (ep, j, *str_1 == 'f');
return NULL;
}
save_in = input_line_pointer;
input_line_pointer = (char *)str;
expression (ep);
ret = input_line_pointer;
input_line_pointer = save_in;
return ret;
}
static void
reloc (const char *op_c_str, const char *id_c_str, offsetT addend)
{
expressionS id_sym_expr;
if (end <= top)
as_fatal (_("expr too huge"));
if (id_c_str)
{
my_getExpression (&id_sym_expr, id_c_str);
id_sym_expr.X_add_number += addend;
}
else
{
id_sym_expr.X_op = O_constant;
id_sym_expr.X_add_number = addend;
}
if (strcmp (op_c_str, "abs") == 0)
{
top->value = id_sym_expr;
top->type = BFD_RELOC_LARCH_SOP_PUSH_ABSOLUTE;
top++;
}
else if (strcmp (op_c_str, "pcrel") == 0)
{
top->value = id_sym_expr;
top->type = BFD_RELOC_LARCH_SOP_PUSH_PCREL;
top++;
}
else if (strcmp (op_c_str, "gprel") == 0)
{
top->value = id_sym_expr;
top->type = BFD_RELOC_LARCH_SOP_PUSH_GPREL;
top++;
}
else if (strcmp (op_c_str, "tprel") == 0)
{
top->value = id_sym_expr;
top->type = BFD_RELOC_LARCH_SOP_PUSH_TLS_TPREL;
top++;
}
else if (strcmp (op_c_str, "tlsgot") == 0)
{
top->value = id_sym_expr;
top->type = BFD_RELOC_LARCH_SOP_PUSH_TLS_GOT;
top++;
}
else if (strcmp (op_c_str, "tlsgd") == 0)
{
top->value = id_sym_expr;
top->type = BFD_RELOC_LARCH_SOP_PUSH_TLS_GD;
top++;
}
else if (strcmp (op_c_str, "plt") == 0)
{
top->value = id_sym_expr;
top->type = BFD_RELOC_LARCH_SOP_PUSH_PLT_PCREL;
top++;
}
else
as_fatal (_("unknown reloc hint: %s"), op_c_str);
}
static void
emit_unary (char op)
{
struct reloc_info *s_top = top - 1;
if (is_const (s_top))
{
offsetT opr = s_top->value.X_add_number;
switch (op)
{
case '+':
break;
case '-':
opr = -opr;
break;
case '~':
opr = ~opr;
break;
case '!':
opr = !opr;
break;
default:
abort ();
}
s_top->value.X_add_number = opr;
}
else
{
if (end <= top)
as_fatal (_("expr too huge"));
switch (op)
{
case '!':
top->type = BFD_RELOC_LARCH_SOP_NOT;
break;
default:
abort ();
}
top->value = const_0;
top++;
}
}
static void
emit_bin (int op)
{
struct reloc_info *last_1st = top - 1, *last_2nd = top - 2;
if (is_const (last_1st) && is_const (last_2nd))
{
offsetT opr1 = last_2nd->value.X_add_number;
offsetT opr2 = last_1st->value.X_add_number;
switch (op)
{
case '*':
opr1 = opr1 * opr2;
break;
case '/':
opr1 = opr1 / opr2;
break;
case '%':
opr1 = opr1 % opr2;
break;
case '+':
opr1 = opr1 + opr2;
break;
case '-':
opr1 = opr1 - opr2;
break;
case LEFT_OP:
opr1 = opr1 << opr2;
break;
case RIGHT_OP:
/* Algorithm right shift. */
opr1 = (offsetT)opr1 >> (offsetT)opr2;
break;
case '<':
opr1 = opr1 < opr2;
break;
case '>':
opr1 = opr1 > opr2;
break;
case LE_OP:
opr1 = opr1 <= opr2;
break;
case GE_OP:
opr1 = opr1 >= opr2;
break;
case EQ_OP:
opr1 = opr1 == opr2;
break;
case NE_OP:
opr1 = opr1 != opr2;
break;
case '&':
opr1 = opr1 & opr2;
break;
case '^':
opr1 = opr1 ^ opr2;
break;
case '|':
opr1 = opr1 | opr2;
break;
case AND_OP:
opr1 = opr1 && opr2;
break;
case OR_OP:
opr1 = opr1 || opr2;
break;
default:
abort ();
}
last_2nd->value.X_add_number = opr1;
last_1st->type = 0;
top--;
}
else
{
if (end <= top)
as_fatal (_("expr too huge"));
switch (op)
{
case '+':
top->type = BFD_RELOC_LARCH_SOP_ADD;
break;
case '-':
top->type = BFD_RELOC_LARCH_SOP_SUB;
break;
case LEFT_OP:
top->type = BFD_RELOC_LARCH_SOP_SL;
break;
case RIGHT_OP:
top->type = BFD_RELOC_LARCH_SOP_SR;
break;
case '&':
top->type = BFD_RELOC_LARCH_SOP_AND;
break;
default:
abort ();
}
top->value = const_0;
top++;
}
}
static void
emit_if_else (void)
{
struct reloc_info *last_1st = top - 1;
struct reloc_info *last_2nd = top - 2;
struct reloc_info *last_3rd = top - 3;
if (is_const (last_1st) && is_const (last_2nd) && is_const (last_3rd))
{
offsetT opr1 = last_3rd->value.X_add_number;
offsetT opr2 = last_2nd->value.X_add_number;
offsetT opr3 = last_1st->value.X_add_number;
opr1 = opr1 ? opr2 : opr3;
last_3rd->value.X_add_number = opr1;
last_2nd->type = 0;
last_1st->type = 0;
top -= 2;
}
else
{
if (end <= top)
as_fatal (_("expr too huge"));
top->type = BFD_RELOC_LARCH_SOP_IF_ELSE;
top->value = const_0;
top++;
}
}
%}
%union {
char *c_str;
offsetT imm;
}
%token <imm> INTEGER
%token <c_str> IDENTIFIER
%type <imm> addend
%token LEFT_OP RIGHT_OP LE_OP GE_OP EQ_OP NE_OP AND_OP OR_OP
%start expression
%%
primary_expression
: INTEGER {emit_const ($1);}
| '(' expression ')'
| '%' IDENTIFIER '(' IDENTIFIER addend ')' {reloc ($2, $4, $5); free ($2); free ($4);}
| '%' IDENTIFIER '(' INTEGER addend ')' {reloc ($2, NULL, $4 + $5); free ($2);}
;
addend
: addend '-' INTEGER {$$ -= $3;}
| addend '+' INTEGER {$$ += $3;}
| {$$ = 0;}
;
unary_expression
: primary_expression
| '+' unary_expression {emit_unary ('+');}
| '-' unary_expression {emit_unary ('-');}
| '~' unary_expression {emit_unary ('~');}
| '!' unary_expression {emit_unary ('!');}
;
multiplicative_expression
: unary_expression
| multiplicative_expression '*' unary_expression {emit_bin ('*');}
| multiplicative_expression '/' unary_expression {emit_bin ('/');}
| multiplicative_expression '%' unary_expression {emit_bin ('%');}
;
additive_expression
: multiplicative_expression
| additive_expression '+' multiplicative_expression {emit_bin ('+');}
| additive_expression '-' multiplicative_expression {emit_bin ('-');}
;
shift_expression
: additive_expression
| shift_expression LEFT_OP additive_expression {emit_bin (LEFT_OP);}
| shift_expression RIGHT_OP additive_expression {emit_bin (RIGHT_OP);}
;
relational_expression
: shift_expression
| relational_expression '<' shift_expression {emit_bin ('<');}
| relational_expression '>' shift_expression {emit_bin ('>');}
| relational_expression LE_OP shift_expression {emit_bin (LE_OP);}
| relational_expression GE_OP shift_expression {emit_bin (GE_OP);}
;
equality_expression
: relational_expression
| equality_expression EQ_OP relational_expression {emit_bin (EQ_OP);}
| equality_expression NE_OP relational_expression {emit_bin (NE_OP);}
;
and_expression
: equality_expression
| and_expression '&' equality_expression {emit_bin ('&');}
;
exclusive_or_expression
: and_expression
| exclusive_or_expression '^' and_expression {emit_bin ('^');}
;
inclusive_or_expression
: exclusive_or_expression
| inclusive_or_expression '|' exclusive_or_expression {emit_bin ('|');}
;
logical_and_expression
: inclusive_or_expression
| logical_and_expression AND_OP inclusive_or_expression {emit_bin (AND_OP);}
;
logical_or_expression
: logical_and_expression
| logical_or_expression OR_OP logical_and_expression {emit_bin (OR_OP);}
;
conditional_expression
: logical_or_expression
| logical_or_expression '?' expression ':' conditional_expression {emit_if_else ();}
;
expression
: conditional_expression
;
%%