blob: 00290d7c3aded37b04c50b5c37fc328f75ae7b39 [file] [log] [blame]
/* tc-wasm32.c -- Assembler code for the wasm32 target.
Copyright (C) 2017-2021 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 GAS; see the file COPYING. If not, write to the Free
Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
02110-1301, USA. */
#include "as.h"
#include "safe-ctype.h"
#include "subsegs.h"
#include "dwarf2dbg.h"
#include "dw2gencfi.h"
#include "elf/wasm32.h"
#include <float.h>
enum wasm_class
{
wasm_typed, /* a typed opcode: block, loop, or if */
wasm_special, /* a special opcode: unreachable, nop, else,
or end */
wasm_break, /* "br" */
wasm_break_if, /* "br_if" opcode */
wasm_break_table, /* "br_table" opcode */
wasm_return, /* "return" opcode */
wasm_call, /* "call" opcode */
wasm_call_indirect, /* "call_indirect" opcode */
wasm_get_local, /* "get_local" and "get_global" */
wasm_set_local, /* "set_local" and "set_global" */
wasm_tee_local, /* "tee_local" */
wasm_drop, /* "drop" */
wasm_constant_i32, /* "i32.const" */
wasm_constant_i64, /* "i64.const" */
wasm_constant_f32, /* "f32.const" */
wasm_constant_f64, /* "f64.const" */
wasm_unary, /* unary operators */
wasm_binary, /* binary operators */
wasm_conv, /* conversion operators */
wasm_load, /* load operators */
wasm_store, /* store operators */
wasm_select, /* "select" */
wasm_relational, /* comparison operators, except for "eqz" */
wasm_eqz, /* "eqz" */
wasm_current_memory, /* "current_memory" */
wasm_grow_memory, /* "grow_memory" */
wasm_signature /* "signature", which isn't an opcode */
};
#define WASM_OPCODE(opcode, name, intype, outtype, class, signedness) \
{ name, wasm_ ## class, opcode },
struct wasm32_opcode_s
{
const char *name;
enum wasm_class clas;
unsigned char opcode;
} wasm32_opcodes[] =
{
#include "opcode/wasm.h"
{
NULL, 0, 0}
};
const char comment_chars[] = ";#";
const char line_comment_chars[] = ";#";
const char line_separator_chars[] = "";
const char *md_shortopts = "m:";
const char EXP_CHARS[] = "eE";
const char FLT_CHARS[] = "dD";
/* The target specific pseudo-ops which we support. */
const pseudo_typeS md_pseudo_table[] =
{
{NULL, NULL, 0}
};
/* Opcode hash table. */
static htab_t wasm32_hash;
struct option md_longopts[] =
{
{NULL, no_argument, NULL, 0}
};
size_t md_longopts_size = sizeof (md_longopts);
/* No relaxation/no machine-dependent frags. */
int
md_estimate_size_before_relax (fragS * fragp ATTRIBUTE_UNUSED,
asection * seg ATTRIBUTE_UNUSED)
{
abort ();
return 0;
}
void
md_show_usage (FILE * stream)
{
fprintf (stream, _("wasm32 assembler options:\n"));
}
/* No machine-dependent options. */
int
md_parse_option (int c ATTRIBUTE_UNUSED, const char *arg ATTRIBUTE_UNUSED)
{
return 0;
}
/* No machine-dependent symbols. */
symbolS *
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
{
return NULL;
}
/* IEEE little-endian floats. */
const char *
md_atof (int type, char *litP, int *sizeP)
{
return ieee_md_atof (type, litP, sizeP, false);
}
/* No machine-dependent frags. */
void
md_convert_frag (bfd * abfd ATTRIBUTE_UNUSED,
asection * sec ATTRIBUTE_UNUSED,
fragS * fragP ATTRIBUTE_UNUSED)
{
abort ();
}
/* Build opcode hash table, set some flags. */
void
md_begin (void)
{
struct wasm32_opcode_s *opcode;
wasm32_hash = str_htab_create ();
/* Insert unique names into hash table. This hash table then
provides a quick index to the first opcode with a particular name
in the opcode table. */
for (opcode = wasm32_opcodes; opcode->name; opcode++)
str_hash_insert (wasm32_hash, opcode->name, opcode, 0);
linkrelax = 0;
flag_sectname_subst = 1;
flag_no_comments = 0;
flag_keep_locals = 1;
}
/* Do the normal thing for md_section_align. */
valueT
md_section_align (asection * seg, valueT addr)
{
int align = bfd_section_alignment (seg);
return ((addr + (1 << align) - 1) & -(1 << align));
}
/* Apply a fixup, return TRUE if done (and no relocation is
needed). */
static bool
apply_full_field_fix (fixS * fixP, char *buf, bfd_vma val, int size)
{
if (fixP->fx_addsy != NULL || fixP->fx_pcrel)
{
fixP->fx_addnumber = val;
return false;
}
number_to_chars_littleendian (buf, val, size);
return true;
}
/* Apply a fixup (potentially PC-relative), set the fx_done flag if
done. */
void
md_apply_fix (fixS * fixP, valueT * valP, segT seg ATTRIBUTE_UNUSED)
{
char *buf = fixP->fx_where + fixP->fx_frag->fr_literal;
long val = (long) *valP;
if (fixP->fx_pcrel)
{
switch (fixP->fx_r_type)
{
default:
bfd_set_error (bfd_error_bad_value);
return;
case BFD_RELOC_32:
fixP->fx_r_type = BFD_RELOC_32_PCREL;
return;
}
}
if (apply_full_field_fix (fixP, buf, val, fixP->fx_size))
fixP->fx_done = 1;
}
/* Skip whitespace. */
static inline char *
skip_space (char *s)
{
while (*s == ' ' || *s == '\t')
++s;
return s;
}
/* Allow '/' in opcodes. */
static inline bool
is_part_of_opcode (char c)
{
return is_part_of_name (c) || (c == '/');
}
/* Extract an opcode. */
static char *
extract_opcode (char *from, char *to, int limit)
{
char *op_end;
int size = 0;
/* Drop leading whitespace. */
from = skip_space (from);
*to = 0;
/* Find the op code end. */
for (op_end = from; *op_end != 0 && is_part_of_opcode (*op_end);)
{
to[size++] = *op_end++;
if (size + 1 >= limit)
break;
}
to[size] = 0;
return op_end;
}
/* Produce an unsigned LEB128 integer padded to the right number of
bytes to store BITS bits, of value VALUE. Uses FRAG_APPEND_1_CHAR
to write. */
static void
wasm32_put_long_uleb128 (int bits, unsigned long value)
{
unsigned char c;
int i = 0;
do
{
c = value & 0x7f;
value >>= 7;
if (i < (bits - 1) / 7)
c |= 0x80;
FRAG_APPEND_1_CHAR (c);
}
while (++i < (bits + 6) / 7);
}
/* Produce a signed LEB128 integer, using FRAG_APPEND_1_CHAR to
write. */
static void
wasm32_put_sleb128 (long value)
{
unsigned char c;
int more;
do
{
c = (value & 0x7f);
value >>= 7;
more = !((((value == 0) && ((c & 0x40) == 0))
|| ((value == -1) && ((c & 0x40) != 0))));
if (more)
c |= 0x80;
FRAG_APPEND_1_CHAR (c);
}
while (more);
}
/* Produce an unsigned LEB128 integer, using FRAG_APPEND_1_CHAR to
write. */
static void
wasm32_put_uleb128 (unsigned long value)
{
unsigned char c;
do
{
c = value & 0x7f;
value >>= 7;
if (value)
c |= 0x80;
FRAG_APPEND_1_CHAR (c);
}
while (value);
}
/* Read an integer expression. Produce an LEB128-encoded integer if
it's a constant, a padded LEB128 plus a relocation if it's a
symbol, or a special relocation for <expr>@got, <expr>@gotcode, and
<expr>@plt{__sigchar_<signature>}. */
static bool
wasm32_leb128 (char **line, int bits, int sign)
{
char *t = input_line_pointer;
char *str = *line;
char *str0 = str;
struct reloc_list *reloc;
expressionS ex;
int gotrel = 0;
int pltrel = 0;
int code = 0;
const char *relname;
input_line_pointer = str;
expression (&ex);
if (ex.X_op == O_constant && *input_line_pointer != '@')
{
long value = ex.X_add_number;
str = input_line_pointer;
str = skip_space (str);
*line = str;
if (sign)
wasm32_put_sleb128 (value);
else
{
if (value < 0)
as_bad (_("unexpected negative constant"));
wasm32_put_uleb128 (value);
}
input_line_pointer = t;
return str != str0;
}
reloc = XNEW (struct reloc_list);
reloc->u.a.offset_sym = expr_build_dot ();
if (ex.X_op == O_symbol)
{
reloc->u.a.sym = ex.X_add_symbol;
reloc->u.a.addend = ex.X_add_number;
}
else
{
reloc->u.a.sym = make_expr_symbol (&ex);
reloc->u.a.addend = 0;
}
/* i32.const fpointer@gotcode */
if (startswith (input_line_pointer, "@gotcode"))
{
gotrel = 1;
code = 1;
input_line_pointer += 8;
}
/* i32.const data@got */
else if (startswith (input_line_pointer, "@got"))
{
gotrel = 1;
input_line_pointer += 4;
}
/* call f@plt{__sigchar_FiiiiE} */
else if (startswith (input_line_pointer, "@plt"))
{
char *end_of_sig;
pltrel = 1;
code = 1;
input_line_pointer += 4;
if (startswith (input_line_pointer, "{")
&& (end_of_sig = strchr (input_line_pointer, '}')))
{
char *signature;
struct reloc_list *reloc2;
size_t siglength = end_of_sig - (input_line_pointer + 1);
signature = strndup (input_line_pointer + 1, siglength);
reloc2 = XNEW (struct reloc_list);
reloc2->u.a.offset_sym = expr_build_dot ();
reloc2->u.a.sym = symbol_find_or_make (signature);
reloc2->u.a.addend = 0;
reloc2->u.a.howto = bfd_reloc_name_lookup
(stdoutput, "R_WASM32_PLT_SIG");
reloc2->next = reloc_list;
reloc_list = reloc2;
input_line_pointer = end_of_sig + 1;
}
else
{
as_bad (_("no function type on PLT reloc"));
}
}
if (gotrel && code)
relname = "R_WASM32_LEB128_GOT_CODE";
else if (gotrel)
relname = "R_WASM32_LEB128_GOT";
else if (pltrel)
relname = "R_WASM32_LEB128_PLT";
else
relname = "R_WASM32_LEB128";
reloc->u.a.howto = bfd_reloc_name_lookup (stdoutput, relname);
if (!reloc->u.a.howto)
as_bad (_("couldn't find relocation to use"));
reloc->file = as_where (&reloc->line);
reloc->next = reloc_list;
reloc_list = reloc;
str = input_line_pointer;
str = skip_space (str);
*line = str;
wasm32_put_long_uleb128 (bits, 0);
input_line_pointer = t;
return str != str0;
}
/* Read an integer expression and produce an unsigned LEB128 integer,
or a relocation for it. */
static bool
wasm32_uleb128 (char **line, int bits)
{
return wasm32_leb128 (line, bits, 0);
}
/* Read an integer expression and produce a signed LEB128 integer, or
a relocation for it. */
static bool
wasm32_sleb128 (char **line, int bits)
{
return wasm32_leb128 (line, bits, 1);
}
/* Read an f32. (Like float_cons ('f')). */
static void
wasm32_f32 (char **line)
{
char *t = input_line_pointer;
input_line_pointer = *line;
float_cons ('f');
*line = input_line_pointer;
input_line_pointer = t;
}
/* Read an f64. (Like float_cons ('d')). */
static void
wasm32_f64 (char **line)
{
char *t = input_line_pointer;
input_line_pointer = *line;
float_cons ('d');
*line = input_line_pointer;
input_line_pointer = t;
}
/* Assemble a signature from LINE, replacing it with the new input
pointer. Signatures are simple expressions matching the regexp
F[ilfd]*v?E, and interpreted as though they were C++-mangled
function types on a 64-bit machine. */
static void
wasm32_signature (char **line)
{
unsigned long count = 0;
char *str = *line;
char *ostr;
char *result;
if (*str++ != 'F')
as_bad (_("Not a function type"));
result = str;
ostr = str + 1;
str++;
while (*str != 'E')
{
switch (*str++)
{
case 'i':
case 'l':
case 'f':
case 'd':
count++;
break;
default:
as_bad (_("Unknown type %c\n"), str[-1]);
}
}
wasm32_put_uleb128 (count);
str = ostr;
while (*str != 'E')
{
switch (*str++)
{
case 'i':
FRAG_APPEND_1_CHAR (BLOCK_TYPE_I32);
break;
case 'l':
FRAG_APPEND_1_CHAR (BLOCK_TYPE_I64);
break;
case 'f':
FRAG_APPEND_1_CHAR (BLOCK_TYPE_F32);
break;
case 'd':
FRAG_APPEND_1_CHAR (BLOCK_TYPE_F64);
break;
default:
as_bad (_("Unknown type"));
}
}
str++;
switch (*result)
{
case 'v':
FRAG_APPEND_1_CHAR (0x00); /* no return value */
break;
case 'i':
FRAG_APPEND_1_CHAR (0x01); /* one return value */
FRAG_APPEND_1_CHAR (BLOCK_TYPE_I32);
break;
case 'l':
FRAG_APPEND_1_CHAR (0x01); /* one return value */
FRAG_APPEND_1_CHAR (BLOCK_TYPE_I64);
break;
case 'f':
FRAG_APPEND_1_CHAR (0x01); /* one return value */
FRAG_APPEND_1_CHAR (BLOCK_TYPE_F32);
break;
case 'd':
FRAG_APPEND_1_CHAR (0x01); /* one return value */
FRAG_APPEND_1_CHAR (BLOCK_TYPE_F64);
break;
default:
as_bad (_("Unknown type"));
}
*line = str;
}
/* Main operands function. Read the operands for OPCODE from LINE,
replacing it with the new input pointer. */
static void
wasm32_operands (struct wasm32_opcode_s *opcode, char **line)
{
char *str = *line;
unsigned long block_type = 0;
FRAG_APPEND_1_CHAR (opcode->opcode);
str = skip_space (str);
if (str[0] == '[')
{
if (opcode->clas == wasm_typed)
{
str++;
block_type = BLOCK_TYPE_NONE;
if (str[0] != ']')
{
str = skip_space (str);
switch (str[0])
{
case 'i':
block_type = BLOCK_TYPE_I32;
str++;
break;
case 'l':
block_type = BLOCK_TYPE_I64;
str++;
break;
case 'f':
block_type = BLOCK_TYPE_F32;
str++;
break;
case 'd':
block_type = BLOCK_TYPE_F64;
str++;
break;
}
str = skip_space (str);
if (str[0] == ']')
str++;
else
as_bad (_("only single block types allowed"));
str = skip_space (str);
}
else
{
str++;
str = skip_space (str);
}
}
else
as_bad (_("instruction does not take a block type"));
}
switch (opcode->clas)
{
case wasm_drop:
case wasm_special:
case wasm_binary:
case wasm_unary:
case wasm_relational:
case wasm_select:
case wasm_eqz:
case wasm_conv:
case wasm_return:
break;
case wasm_typed:
if (block_type == 0)
as_bad (_("missing block type"));
FRAG_APPEND_1_CHAR (block_type);
break;
case wasm_store:
case wasm_load:
if (str[0] == 'a' && str[1] == '=')
{
str += 2;
if (!wasm32_uleb128 (&str, 32))
as_bad (_("missing alignment hint"));
}
else
{
as_bad (_("missing alignment hint"));
}
str = skip_space (str);
if (!wasm32_uleb128 (&str, 32))
as_bad (_("missing offset"));
break;
case wasm_set_local:
case wasm_get_local:
case wasm_tee_local:
if (!wasm32_uleb128 (&str, 32))
as_bad (_("missing local index"));
break;
case wasm_break:
case wasm_break_if:
if (!wasm32_uleb128 (&str, 32))
as_bad (_("missing break count"));
break;
case wasm_current_memory:
case wasm_grow_memory:
if (!wasm32_uleb128 (&str, 32))
as_bad (_("missing reserved current_memory/grow_memory argument"));
break;
case wasm_call:
if (!wasm32_uleb128 (&str, 32))
as_bad (_("missing call argument"));
break;
case wasm_call_indirect:
if (!wasm32_uleb128 (&str, 32))
as_bad (_("missing call signature"));
if (!wasm32_uleb128 (&str, 32))
as_bad (_("missing table index"));
break;
case wasm_constant_i32:
wasm32_sleb128 (&str, 32);
break;
case wasm_constant_i64:
wasm32_sleb128 (&str, 64);
break;
case wasm_constant_f32:
wasm32_f32 (&str);
return;
case wasm_constant_f64:
wasm32_f64 (&str);
return;
case wasm_break_table:
{
do
{
wasm32_uleb128 (&str, 32);
str = skip_space (str);
}
while (str[0]);
break;
}
case wasm_signature:
wasm32_signature (&str);
}
str = skip_space (str);
if (*str)
as_bad (_("junk at end of line, first unrecognized character is `%c'"),
*str);
*line = str;
return;
}
/* Main assembly function. Find the opcode and call
wasm32_operands(). */
void
md_assemble (char *str)
{
char op[32];
char *t;
struct wasm32_opcode_s *opcode;
str = skip_space (extract_opcode (str, op, sizeof (op)));
if (!op[0])
as_bad (_("can't find opcode "));
opcode = (struct wasm32_opcode_s *) str_hash_find (wasm32_hash, op);
if (opcode == NULL)
{
as_bad (_("unknown opcode `%s'"), op);
return;
}
dwarf2_emit_insn (0);
t = input_line_pointer;
wasm32_operands (opcode, &str);
input_line_pointer = t;
}
/* Don't replace PLT/GOT relocations with section symbols, so they
don't get an addend. */
int
wasm32_force_relocation (fixS * f)
{
if (f->fx_r_type == BFD_RELOC_WASM32_LEB128_PLT
|| f->fx_r_type == BFD_RELOC_WASM32_LEB128_GOT)
return 1;
return 0;
}
/* Don't replace PLT/GOT relocations with section symbols, so they
don't get an addend. */
bool
wasm32_fix_adjustable (fixS * fixP)
{
if (fixP->fx_addsy == NULL)
return true;
if (fixP->fx_r_type == BFD_RELOC_WASM32_LEB128_PLT
|| fixP->fx_r_type == BFD_RELOC_WASM32_LEB128_GOT)
return false;
return true;
}
/* Generate a reloc for FIXP. */
arelent *
tc_gen_reloc (asection * sec ATTRIBUTE_UNUSED, fixS * fixp)
{
arelent *reloc;
reloc = (arelent *) xmalloc (sizeof (*reloc));
reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *));
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
/* Make sure none of our internal relocations make it this far.
They'd better have been fully resolved by this point. */
gas_assert ((int) fixp->fx_r_type > 0);
reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type);
if (reloc->howto == NULL)
{
as_bad_where (fixp->fx_file, fixp->fx_line,
_("cannot represent `%s' relocation in object file"),
bfd_get_reloc_code_name (fixp->fx_r_type));
return NULL;
}
reloc->addend = fixp->fx_offset;
return reloc;
}