quakeforge/tools/qfcc/source/expr_assign.c
Bill Currie 3d88c3845f [qfcc] Move struct copy/set into statement emission
Doing it in the expression trees was a big mistake for a several
reasons. For one, expression trees are meant to be target-agnostic, so
they're the wrong place for selecting instruction types. Also, the move
and memset expressions broke "a = b = c;" type expression chains.

This fixes most things (including the assignchain test) with -Werror
turned off (some issues in flow analysis uncovered by the nil
migration: memset target not extracted).
2020-03-13 18:20:38 +09:00

350 lines
8.5 KiB
C

/*
expr_assign.c
assignment expression construction and manipulations
Copyright (C) 2001 Bill Currie <bill@taniwha.org>
Author: Bill Currie <bill@taniwha.org>
Date: 2001/06/15
This program 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 2
of the License, or (at your option) any later version.
This program 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; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>
#include "QF/alloc.h"
#include "QF/dstring.h"
#include "QF/mathlib.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "qfcc.h"
#include "class.h"
#include "def.h"
#include "defspace.h"
#include "diagnostic.h"
#include "emit.h"
#include "expr.h"
#include "function.h"
#include "idstuff.h"
#include "method.h"
#include "options.h"
#include "reloc.h"
#include "shared.h"
#include "strpool.h"
#include "struct.h"
#include "symtab.h"
#include "type.h"
#include "value.h"
#include "qc-parse.h"
static expr_t *
check_assign_logic_precedence (expr_t *dst, expr_t *src)
{
if (src->type == ex_expr && !src->paren && is_logic (src->e.expr.op)) {
// traditional QuakeC gives = higher precedence than && and ||
expr_t *assignment;
notice (src, "precedence of `=' and `%s' inverted for "
"traditional code", get_op_string (src->e.expr.op));
// change {a = (b logic c)} to {(a = b) logic c}
assignment = assign_expr (dst, src->e.expr.e1);
assignment->paren = 1; // protect assignment from binary_expr
return binary_expr (src->e.expr.op, assignment, src->e.expr.e2);
}
return 0;
}
static expr_t *
check_valid_lvalue (expr_t *expr)
{
switch (expr->type) {
case ex_symbol:
switch (expr->e.symbol->sy_type) {
case sy_name:
break;
case sy_var:
return 0;
case sy_const:
break;
case sy_type:
break;
case sy_expr:
break;
case sy_func:
break;
case sy_class:
break;
case sy_convert:
break;
}
break;
case ex_temp:
return 0;
case ex_expr:
if (expr->e.expr.op == '.') {
return 0;
}
if (expr->e.expr.op == 'A') {
return check_valid_lvalue (expr->e.expr.e1);
}
break;
case ex_uexpr:
if (expr->e.expr.op == '.') {
return 0;
}
if (expr->e.expr.op == 'A') {
return check_valid_lvalue (expr->e.expr.e1);
}
break;
case ex_memset:
case ex_compound:
case ex_state:
case ex_bool:
case ex_label:
case ex_labelref:
case ex_block:
case ex_vector:
case ex_nil:
case ex_value:
case ex_error:
break;
}
if (options.traditional) {
warning (expr, "invalid lvalue in assignment");
return 0;
}
return error (expr, "invalid lvalue in assignment");
}
static expr_t *
check_types_compatible (expr_t *dst, expr_t *src)
{
type_t *dst_type = get_type (dst);
type_t *src_type = get_type (src);
if (dst_type == src_type) {
return 0;
}
if (type_assignable (dst_type, src_type)) {
if (is_scalar (dst_type) && is_scalar (src_type)) {
if (!src->implicit) {
if (is_double (src_type)) {
warning (dst, "assignment of double to %s (use a cast)\n",
dst_type->name);
}
}
// the types are different but cast-compatible
expr_t *new = cast_expr (dst_type, src);
// the cast was a no-op, so the types are compatible at the
// low level (very true for default type <-> enum)
if (new != src) {
return assign_expr (dst, new);
}
}
return 0;
}
// traditional qcc is a little sloppy
if (!options.traditional) {
return type_mismatch (dst, src, '=');
}
if (is_func (dst_type) && is_func (src_type)) {
warning (dst, "assignment between disparate function types");
return 0;
}
if (is_float (dst_type) && is_vector (src_type)) {
warning (dst, "assignment of vector to float");
src = field_expr (src, new_name_expr ("x"));
return assign_expr (dst, src);
}
if (is_vector (dst_type) && is_float (src_type)) {
warning (dst, "assignment of float to vector");
dst = field_expr (dst, new_name_expr ("x"));
return assign_expr (dst, src);
}
return type_mismatch (dst, src, '=');
}
static expr_t *
assign_vector_expr (expr_t *dst, expr_t *src)
{
expr_t *dx, *sx;
expr_t *dy, *sy;
expr_t *dz, *sz;
expr_t *dw, *sw;
expr_t *ds, *ss;
expr_t *dv, *sv;
expr_t *block;
if (src->type == ex_vector) {
src = convert_vector (src);
if (src->type != ex_vector) {
// src was constant and thus converted
return assign_expr (dst, src);
}
}
if (src->type == ex_vector && dst->type != ex_vector) {
if (src->e.vector.type == &type_vector) {
// guaranteed to have three elements
sx = src->e.vector.list;
sy = sx->next;
sz = sy->next;
dx = field_expr (dst, new_name_expr ("x"));
dy = field_expr (dst, new_name_expr ("y"));
dz = field_expr (dst, new_name_expr ("z"));
block = new_block_expr ();
append_expr (block, assign_expr (dx, sx));
append_expr (block, assign_expr (dy, sy));
append_expr (block, assign_expr (dz, sz));
block->e.block.result = dst;
return block;
}
if (src->e.vector.type == &type_quaternion) {
// guaranteed to have two or four elements
if (src->e.vector.list->next->next) {
// four vals: x, y, z, w
sx = src->e.vector.list;
sy = sx->next;
sz = sy->next;
sw = sz->next;
dx = field_expr (dst, new_name_expr ("x"));
dy = field_expr (dst, new_name_expr ("y"));
dz = field_expr (dst, new_name_expr ("z"));
dw = field_expr (dst, new_name_expr ("w"));
block = new_block_expr ();
append_expr (block, assign_expr (dx, sx));
append_expr (block, assign_expr (dy, sy));
append_expr (block, assign_expr (dz, sz));
append_expr (block, assign_expr (dw, sw));
block->e.block.result = dst;
return block;
} else {
// v, s
sv = src->e.vector.list;
ss = sv->next;
dv = field_expr (dst, new_name_expr ("v"));
ds = field_expr (dst, new_name_expr ("s"));
block = new_block_expr ();
append_expr (block, assign_expr (dv, sv));
append_expr (block, assign_expr (ds, ss));
block->e.block.result = dst;
return block;
}
}
internal_error (src, "bogus vector expression");
}
return 0;
}
static __attribute__((pure)) int
is_memset (expr_t *e)
{
return e->type == ex_memset;
}
expr_t *
assign_expr (expr_t *dst, expr_t *src)
{
int op = '=';
expr_t *expr;
type_t *dst_type, *src_type;
convert_name (dst);
if (dst->type == ex_error) {
return dst;
}
if ((expr = check_valid_lvalue (dst))) {
return expr;
}
dst_type = get_type (dst);
if (!dst_type) {
internal_error (dst, "dst_type broke in assign_expr");
}
if (src && !is_memset (src)) {
convert_name (src);
if (src->type == ex_error) {
return src;
}
if (options.traditional
&& (expr = check_assign_logic_precedence (dst, src))) {
return expr;
}
} else {
if (!src && is_scalar (dst_type)) {
return error (dst, "empty scalar initializer");
}
src = new_nil_expr ();
}
if (src->type == ex_compound) {
src = initialized_temp_expr (dst_type, src);
if (src->type == ex_error) {
return src;
}
}
src_type = get_type (src);
if (!src_type) {
internal_error (src, "src_type broke in assign_expr");
}
if (is_pointer (dst_type) && is_array (src_type)) {
// assigning an array to a pointer is the same as taking the address of
// the array but using the type of the array elements
src = address_expr (src, 0, src_type->t.fldptr.type);
src_type = get_type (src);
}
if (src->type == ex_bool) {
// boolean expressions are chains of tests, so extract the result
// of the tests
src = convert_from_bool (src, dst_type);
if (src->type == ex_error) {
return src;
}
src_type = get_type (src);
}
if (!is_nil (src)) {
if ((expr = check_types_compatible (dst, src))) {
// expr might be a valid expression, but if so,
// check_types_compatible will take care of everything
return expr;
}
if ((expr = assign_vector_expr (dst, src))) {
return expr;
}
} else {
convert_nil (src, dst_type);
}
expr = new_binary_expr (op, dst, src);
expr->e.expr.type = dst_type;
return expr;
}