quakeforge/tools/qfcc/source/expr_assign.c
Bill Currie 5d349efe78 [qfcc] Delay conversion of nil in assignments
It's not possible to take the address of constants (at this stage) and
trying to use a move instruction with .zero as source would result in
the VM complaining about null pointer access when bounds checking is on.
Thus, don't convert a nil source expression until it is known to be
safe, and use memset when it is not.
2020-03-11 22:57:48 +09:00

445 lines
11 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_const_ptr (expr_t *e)
{
if ((e->type != ex_value || e->e.value->lltype != ev_pointer)
|| !(POINTER_VAL (e->e.value->v.pointer) >= 0
&& POINTER_VAL (e->e.value->v.pointer) < 65536)) {
return 1;
}
return 0;
}
static __attribute__((pure)) int
is_indirect (expr_t *e)
{
if (e->type == ex_block && e->e.block.result)
return is_indirect (e->e.block.result);
if (e->type == ex_expr && e->e.expr.op == '.')
return 1;
if (!(e->type == ex_uexpr && e->e.expr.op == '.'))
return 0;
return is_const_ptr (e->e.expr.e1);
}
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) {
convert_name (src);
if (src->type == ex_error) {
return src;
}
if (options.traditional
&& (expr = check_assign_logic_precedence (dst, src))) {
return expr;
}
} else {
if (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) {
src = convert_from_bool (src, dst_type);
if (src->type == ex_error) {
return src;
}
src_type = get_type (src);
}
if (!is_struct (dst_type) && is_nil (src)) {
// nil is a type-agnostic 0
// FIXME: assignment to compound types? error or memset?
src_type = dst_type;
convert_nil (src, src_type);
}
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;
}
}
if (is_indirect (dst) && is_indirect (src)) {
debug (dst, "here");
if (is_struct (src_type)) {
dst = address_expr (dst, 0, 0);
src = address_expr (src, 0, 0);
expr = new_move_expr (dst, src, src_type, 1);
} else {
expr_t *temp = new_temp_def_expr (dst_type);
expr = new_block_expr ();
append_expr (expr, assign_expr (temp, src));
append_expr (expr, assign_expr (dst, temp));
expr->e.block.result = temp;
}
return expr;
} else if (is_indirect (dst)) {
debug (dst, "here");
if (is_struct (dst_type)) {
dst = address_expr (dst, 0, 0);
if (is_nil (src)) {
int size = type_size (dst_type);
return new_memset_expr (dst, new_integer_expr (0),
new_integer_expr (size));
} else {
src = address_expr (src, 0, 0);
return new_move_expr (dst, src, dst_type, 1);
}
}
if (is_nil (src)) {
src_type = dst_type;
convert_nil (src, src_type);
}
if (dst->type == ex_expr) {
if (get_type (dst->e.expr.e1) == &type_entity) {
dst_type = dst->e.expr.type;
dst->e.expr.type = pointer_type (dst_type);
dst->e.expr.op = '&';
}
op = PAS;
} else {
if (is_const_ptr (dst->e.expr.e1)) {
dst = dst->e.expr.e1;
op = PAS;
}
}
} else if (is_indirect (src)) {
debug (dst, "here");
if (is_struct (dst_type)) {
dst = address_expr (dst, 0, 0);
src = address_expr (src, 0, 0);
src->rvalue = 1;
return new_move_expr (dst, src, src_type, 1);
}
if (src->type == ex_uexpr) {
expr = src->e.expr.e1;
if (is_const_ptr (expr)) {
if (expr->type == ex_expr && expr->e.expr.op == '&'
&& expr->e.expr.type->type == ev_pointer
&& !is_constant (expr)) {
src = expr;
src->e.expr.op = '.';
src->e.expr.type = src_type;
src->rvalue = 1;
}
}
}
}
if (is_nil (src)) {
src_type = dst_type;
convert_nil (src, src_type);
}
if (is_struct (dst_type)) {
return new_move_expr (dst, src, dst_type, 0);
}
expr = new_binary_expr (op, dst, src);
expr->e.expr.type = dst_type;
return expr;
}