blob: 4712026c90acf4aef1cf9b17b192f45aad2fdd53 [file] [log] [blame]
/*
* Copyright (C) 2003, 2004, 2005, 2014, 2015 Filip Pizlo. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY FILIP PIZLO ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FILIP PIZLO OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FP_GPC_H
#define FP_GPC_H
#include "tsf.h"
#include <stdio.h>
/* this enumeration must contain the same instructions in the same order
* as gpc_spec.in. */
enum gpc_cell {
/* figure this might as well be here for completeness */
GPC_I_NOP,
/* label instruction. this is translated into a nop. but
* it contains a label 'number' as an operand. these numbers
* do not have to be unique. whenever a label is used, it will be
* translated into a jump to the nearest label instruction that has
* that label as its operand, by searching either in the forward or
* backward direction, depending on whether or not it is a forward
* or backward jump. */
GPC_I_LABEL,
/* we wrap up the useful subset of tsf_internal.h's converting
* copy macros and then add conversion to them. the operands are
* as follows:
* COPY_xTOyz_INCabc <offset>
* The effect is that the pointer at the top of the stack has
* offset added to it; in the case of INCDST, this serves as the
* source, while for INCSRC it serves as the destination. */
GPC_I_COPY_HTONC_INCDST,
GPC_I_COPY_HTONS_INCDST,
GPC_I_COPY_HTONL_INCDST,
GPC_I_COPY_HTONLL_INCDST,
GPC_I_COPY_HTONF_INCDST,
GPC_I_COPY_HTOND_INCDST,
GPC_I_COPY_HTONCHOICE_TO_UI8_INCDST,
/* the rules:
* -> if we do not change size or reduce size, we can ignore the
* size.
* -> if we expand, then the signedness of the original determines
* whether or not we do sign expansion.
* -> if expanding floats to ints, the signedness of the target
* matters (though I'm not sure why - this is only a conservative
* estimate based on looking at the things that gcc does for
* float to int conversion).
*/
GPC_I_COPY_NTOHC_TO_BIT_INCSRC,
GPC_I_COPY_NTOHC_INCSRC,
GPC_I_COPY_NTOHC_TO_S_Z_INCSRC,
GPC_I_COPY_NTOHC_TO_S_E_INCSRC,
GPC_I_COPY_NTOHC_TO_L_Z_INCSRC,
GPC_I_COPY_NTOHC_TO_L_E_INCSRC,
GPC_I_COPY_NTOHC_TO_LL_Z_INCSRC,
GPC_I_COPY_NTOHC_TO_LL_E_INCSRC,
GPC_I_COPY_NTOHC_TO_F_Z_INCSRC,
GPC_I_COPY_NTOHC_TO_F_E_INCSRC,
GPC_I_COPY_NTOHC_TO_D_Z_INCSRC,
GPC_I_COPY_NTOHC_TO_D_E_INCSRC,
GPC_I_COPY_NTOHS_TO_BIT_INCSRC,
GPC_I_COPY_NTOHS_TO_C_INCSRC,
GPC_I_COPY_NTOHS_INCSRC,
GPC_I_COPY_NTOHS_TO_L_Z_INCSRC,
GPC_I_COPY_NTOHS_TO_L_E_INCSRC,
GPC_I_COPY_NTOHS_TO_LL_Z_INCSRC,
GPC_I_COPY_NTOHS_TO_LL_E_INCSRC,
GPC_I_COPY_NTOHS_TO_F_Z_INCSRC,
GPC_I_COPY_NTOHS_TO_F_E_INCSRC,
GPC_I_COPY_NTOHS_TO_D_Z_INCSRC,
GPC_I_COPY_NTOHS_TO_D_E_INCSRC,
GPC_I_COPY_NTOHL_TO_BIT_INCSRC,
GPC_I_COPY_NTOHL_TO_C_INCSRC,
GPC_I_COPY_NTOHL_TO_S_INCSRC,
GPC_I_COPY_NTOHL_INCSRC,
GPC_I_COPY_NTOHL_TO_LL_Z_INCSRC,
GPC_I_COPY_NTOHL_TO_LL_E_INCSRC,
GPC_I_COPY_NTOHL_TO_F_Z_INCSRC,
GPC_I_COPY_NTOHL_TO_F_E_INCSRC,
GPC_I_COPY_NTOHL_TO_D_Z_INCSRC,
GPC_I_COPY_NTOHL_TO_D_E_INCSRC,
GPC_I_COPY_NTOHLL_TO_BIT_INCSRC,
GPC_I_COPY_NTOHLL_TO_C_INCSRC,
GPC_I_COPY_NTOHLL_TO_S_INCSRC,
GPC_I_COPY_NTOHLL_TO_L_INCSRC,
GPC_I_COPY_NTOHLL_INCSRC,
GPC_I_COPY_NTOHLL_TO_F_Z_INCSRC,
GPC_I_COPY_NTOHLL_TO_F_E_INCSRC,
GPC_I_COPY_NTOHLL_TO_D_Z_INCSRC,
GPC_I_COPY_NTOHLL_TO_D_E_INCSRC,
GPC_I_COPY_NTOHF_TO_BIT_INCSRC,
GPC_I_COPY_NTOHF_TO_C_Z_INCSRC,
GPC_I_COPY_NTOHF_TO_C_E_INCSRC,
GPC_I_COPY_NTOHF_TO_S_Z_INCSRC,
GPC_I_COPY_NTOHF_TO_S_E_INCSRC,
GPC_I_COPY_NTOHF_TO_L_Z_INCSRC,
GPC_I_COPY_NTOHF_TO_L_E_INCSRC,
GPC_I_COPY_NTOHF_TO_LL_Z_INCSRC,
GPC_I_COPY_NTOHF_TO_LL_E_INCSRC,
GPC_I_COPY_NTOHF_INCSRC,
GPC_I_COPY_NTOHF_TO_D_INCSRC,
GPC_I_COPY_NTOHD_TO_BIT_INCSRC,
GPC_I_COPY_NTOHD_TO_C_Z_INCSRC,
GPC_I_COPY_NTOHD_TO_C_E_INCSRC,
GPC_I_COPY_NTOHD_TO_S_Z_INCSRC,
GPC_I_COPY_NTOHD_TO_S_E_INCSRC,
GPC_I_COPY_NTOHD_TO_L_Z_INCSRC,
GPC_I_COPY_NTOHD_TO_L_E_INCSRC,
GPC_I_COPY_NTOHD_TO_LL_Z_INCSRC,
GPC_I_COPY_NTOHD_TO_LL_E_INCSRC,
GPC_I_COPY_NTOHD_TO_F_INCSRC,
GPC_I_COPY_NTOHD_INCSRC,
/* below are convert and copy routines between two pointers and
* offsets; these are designed for primitives. the data is copied
* from the given offset from the topmost pointer to the given offset
* from the second-to-topmost pointer. call these guys like so:
* GPC_I_COPY_x_TO_y <dest offset> <source offset> */
GPC_I_COPY_BIT, /* makes non-zero values one - good for converting to and
from bits */
GPC_I_COPY_BIT_TO_S,
GPC_I_COPY_BIT_TO_L,
GPC_I_COPY_BIT_TO_LL,
GPC_I_COPY_BIT_TO_F,
GPC_I_COPY_BIT_TO_D,
GPC_I_COPY_C,
GPC_I_COPY_C_TO_S_Z,
GPC_I_COPY_C_TO_S_E,
GPC_I_COPY_C_TO_L_Z,
GPC_I_COPY_C_TO_L_E,
GPC_I_COPY_C_TO_LL_Z,
GPC_I_COPY_C_TO_LL_E,
GPC_I_COPY_C_TO_F_Z,
GPC_I_COPY_C_TO_F_E,
GPC_I_COPY_C_TO_D_Z,
GPC_I_COPY_C_TO_D_E,
GPC_I_COPY_S_TO_BIT,
GPC_I_COPY_S_TO_C,
GPC_I_COPY_S,
GPC_I_COPY_S_TO_L_Z,
GPC_I_COPY_S_TO_L_E,
GPC_I_COPY_S_TO_LL_Z,
GPC_I_COPY_S_TO_LL_E,
GPC_I_COPY_S_TO_F_Z,
GPC_I_COPY_S_TO_F_E,
GPC_I_COPY_S_TO_D_Z,
GPC_I_COPY_S_TO_D_E,
GPC_I_COPY_L_TO_BIT,
GPC_I_COPY_L_TO_C,
GPC_I_COPY_L_TO_S,
GPC_I_COPY_L,
GPC_I_COPY_L_TO_LL_Z,
GPC_I_COPY_L_TO_LL_E,
GPC_I_COPY_L_TO_F_Z,
GPC_I_COPY_L_TO_F_E,
GPC_I_COPY_L_TO_D_Z,
GPC_I_COPY_L_TO_D_E,
GPC_I_COPY_LL_TO_BIT,
GPC_I_COPY_LL_TO_C,
GPC_I_COPY_LL_TO_S,
GPC_I_COPY_LL_TO_L,
GPC_I_COPY_LL,
GPC_I_COPY_LL_TO_F_Z,
GPC_I_COPY_LL_TO_F_E,
GPC_I_COPY_LL_TO_D_Z,
GPC_I_COPY_LL_TO_D_E,
GPC_I_COPY_F_TO_BIT,
GPC_I_COPY_F_TO_C_Z,
GPC_I_COPY_F_TO_C_E,
GPC_I_COPY_F_TO_S_Z,
GPC_I_COPY_F_TO_S_E,
GPC_I_COPY_F_TO_L_Z,
GPC_I_COPY_F_TO_L_E,
GPC_I_COPY_F_TO_LL_Z,
GPC_I_COPY_F_TO_LL_E,
GPC_I_COPY_F,
GPC_I_COPY_F_TO_D,
GPC_I_COPY_D_TO_BIT,
GPC_I_COPY_D_TO_C_Z,
GPC_I_COPY_D_TO_C_E,
GPC_I_COPY_D_TO_S_Z,
GPC_I_COPY_D_TO_S_E,
GPC_I_COPY_D_TO_L_Z,
GPC_I_COPY_D_TO_L_E,
GPC_I_COPY_D_TO_LL_Z,
GPC_I_COPY_D_TO_LL_E,
GPC_I_COPY_D_TO_F,
GPC_I_COPY_D,
/* note that there are no 64-bit set operations. this is the
* result of cells possibly being 32-bit instead of 64-bit. */
/* set operations. these take a constant off the stream and
* shove it into the requested offset from the ptr
* on top of the stack. use it like so:
* GPC_I_SET_XX <dest offset> <constant> */
GPC_I_SET_C,
GPC_I_SET_S,
GPC_I_SET_L,
/* set operations. these take a constant off the stream and
* shove it into the requested offset from the ptr that is
* second from top of the stack. use it like so:
* GPC_I_SET_2ND_XX <dest offset> <constant> */
GPC_I_SET_2ND_C,
GPC_I_SET_2ND_S,
GPC_I_SET_2ND_L,
/* note that the read instructions below all increment the
* current buffer pointer register. */
/* do a bitvector read. the tsf_native_bitvector_t structure is
* at the given offset from the topmost pointer. call this like so:
* GPC_I_BITVECTOR_READ <offset> */
GPC_I_BITVECTOR_READ,
/* bitvector read that converts the bitvector to something other than
* a bitvector */
GPC_I_BITVECTOR_READ_TO_C,
GPC_I_BITVECTOR_READ_TO_S,
GPC_I_BITVECTOR_READ_TO_L,
GPC_I_BITVECTOR_READ_TO_LL,
GPC_I_BITVECTOR_READ_TO_F,
GPC_I_BITVECTOR_READ_TO_D,
/* bitvector read that converts something other than a bitvector into
* a bitvector */
GPC_I_BITVECTOR_READ_FROM_C,
GPC_I_BITVECTOR_READ_FROM_S,
GPC_I_BITVECTOR_READ_FROM_L,
GPC_I_BITVECTOR_READ_FROM_LL,
GPC_I_BITVECTOR_READ_FROM_F,
GPC_I_BITVECTOR_READ_FROM_D,
GPC_I_BITVECTOR_READ_FROM_TSF_INTEGER_ARRAY,
GPC_I_BITVECTOR_READ_FROM_TSF_LONG_ARRAY,
/* do a bitvector write. the tsf_native_bitvector_t structure is at
* the given offset from the topmost pointer. call this like so:
* GPC_I_BITVECTOR_WRITE <offset> */
GPC_I_BITVECTOR_WRITE,
/* do a byte array read. the tsf_native_array_t structure is at the
* given offset from the topmost pointer. the length is multiplied
* by the given multiplier to obtain the size of the array in bytes.
* call this like so:
* GPC_I_BYTE_ARRAY_READ <offset> <multiplier> */
GPC_I_BYTE_ARRAY_READ,
/* do a byte array write. the tsf_native_array_t structure is at the
* given offset from the topmost pointer. the length is multiplied
* by the given multiplier to obtain the size of the array in bytes.
* call this like so:
* GPC_I_BYTE_ARRAY_WRITE <offset> <multiplier> */
GPC_I_BYTE_ARRAY_WRITE,
/* What follows are instructions for supporting TSF integers. See the
* comment above the declaration of TSF_TK_INTEGER in tsf.h. */
/* skip a TSF integer. */
GPC_I_TSF_INTEGER_SKIP,
GPC_I_TSF_UNSIGNED_SKIP,
GPC_I_TSF_LONG_SKIP,
/* read a TSF integer and convert to a variety of things. */
GPC_I_TSF_INTEGER_READ_TO_BIT,
GPC_I_TSF_INTEGER_READ_TO_C,
GPC_I_TSF_INTEGER_READ_TO_S,
GPC_I_TSF_INTEGER_READ,
GPC_I_TSF_INTEGER_READ_TO_LL,
GPC_I_TSF_INTEGER_READ_TO_F,
GPC_I_TSF_INTEGER_READ_TO_D,
GPC_I_TSF_UNSIGNED_READ_SUB1, /* reads onto the stack, not into memory. */
GPC_I_TSF_LONG_READ_TO_BIT,
GPC_I_TSF_LONG_READ_TO_C,
GPC_I_TSF_LONG_READ_TO_S,
GPC_I_TSF_LONG_READ_TO_L,
GPC_I_TSF_LONG_READ,
GPC_I_TSF_LONG_READ_TO_F,
GPC_I_TSF_LONG_READ_TO_D,
/* increment the size register for a TSF integer. */
GPC_I_TSF_INTEGER_SIZE,
GPC_I_TSF_UNSIGNED_PLUS1_SIZE,
GPC_I_TSF_LONG_SIZE,
/* write a TSF integer. */
GPC_I_TSF_INTEGER_WRITE,
GPC_I_TSF_UNSIGNED_PLUS1_WRITE,
GPC_I_TSF_LONG_WRITE,
/* set the string to the empty string. the char* is at the given
* offset from the topmost pointer. */
GPC_I_STRING_SETEMPTY,
/* do a string read. the char* is at the given offset from the
* topmost pointer. */
GPC_I_STRING_READ,
/* do a string skip. */
GPC_I_STRING_SKIP,
/* calculate the size for a string. the char* is at the given offset
* from the topmost pointer. */
GPC_I_STRING_SIZE,
/* do a string write. the char* is at the given offset from the
* topmost pointer. */
GPC_I_STRING_WRITE,
/* do a string copy. the second to topmost pointer plus the
* destination offset points to the destination char*, while the
* topmost pointer plus source offset points to the source char*.
* call this like so:
* GPC_I_STRING_COPY <dest offset> <src offset> */
GPC_I_STRING_COPY,
/* set the buffer to a buffer just containing void. the tsf_buffer_t*
* is at the given offset frm the topmost pointer. */
GPC_I_ANY_SETEMPTY,
/* do an any read. the tsf_buffer_t* is at the given offset from the
* topmost pointer. */
GPC_I_ANY_READ,
/* do an any skip. */
GPC_I_ANY_SKIP,
/* calculate the size of an any. the tsf_buffer_t* is at the given
* offset frm the topmost pointer. */
GPC_I_ANY_SIZE,
/* do an any write. the tsf_buffer_t* is at the given offset from the
* topmost pointer. */
GPC_I_ANY_WRITE,
/* do an any copy. the second to topmost pointer plus the destination
* offset points to the destination tsf_buffer_t*, while the topmost
* pointer plus source offset points to the source tsf_buffer_t*. call
* this like so:
* GPC_I_ANY_COPY <dest offset> <src offset> */
GPC_I_ANY_COPY,
/* do a memcpy from one data structure to another. the
* destination is second to topmost, while the source is topmost.
* the operands are: offset from destination, offset from source, and
* the size. */
GPC_I_MEMCPY,
/* copy an array. the second to topmost pointer plus the
* destination offset points to the destination tsf_native_array_t,
* while the topmost pointer plus source offset points to the source
* tsf_native_array_t. the other operand is the multiplier that
* specifies the number of bytes for each array element. call this
* like so:
* GPC_I_COPY_ARRAY <dest offset> <src offset> <multiplier> */
GPC_I_COPY_ARRAY,
/* copy a bitvector. the second to topmost pointer plus the
* destination offset points to the destination tsf_native_bitvector_t,
* while the topmost pointer plus source offset points to the source
* tsf_native_bitvector_t. call this like so:
* GPC_I_COPY_ARRAY <dest offset> <src offset> */
GPC_I_COPY_BITVECTOR,
/* copy between bitvectors and other types of arrays */
GPC_I_COPY_BITVECTOR_FROM_C,
GPC_I_COPY_BITVECTOR_FROM_S,
GPC_I_COPY_BITVECTOR_FROM_L,
GPC_I_COPY_BITVECTOR_FROM_LL,
GPC_I_COPY_BITVECTOR_FROM_F,
GPC_I_COPY_BITVECTOR_FROM_D,
GPC_I_COPY_BITVECTOR_TO_C,
GPC_I_COPY_BITVECTOR_TO_S,
GPC_I_COPY_BITVECTOR_TO_L,
GPC_I_COPY_BITVECTOR_TO_LL,
GPC_I_COPY_BITVECTOR_TO_F,
GPC_I_COPY_BITVECTOR_TO_D,
/* read a 8-bit value and place it on the stack. if the value was
* 255, it is instead converted to UINT32_MAX. */
GPC_I_READC_TO_CHOICE,
/* read a 32-bit value and place it at the top of the stack. */
GPC_I_READL,
/* do an array length write. operand is the offset from the
* topmost pointer to the 32-bit array length. relevant
* bounds checks are done. */
GPC_I_ARRAY_LEN_WRITE_FIELD,
/* do an array length read. operand is the offset from the
* topmost pointer to the 32-bit array length. relevant
* bounds checks are done. */
GPC_I_ARRAY_LEN_READ_FIELD,
/* do an array length read. the array length is pushed onto
* the stack. relevant bounds checks are done. */
GPC_I_ARRAY_LEN_READ_LOCAL,
/* do an array length read. discard the array length. */
GPC_I_ARRAY_LEN_SKIP,
/* if the 8-bit value pointed to by the specified offset from
* the topmost pointer is non-zero, set set specified bit in
* the current buffer location. otherwise reset it. the
* buffer pointer is not advanced. you call this thing as
* follows:
* GPC_I_BIT_MASK_WRITE <bit index> <offset> */
GPC_I_BIT_MASK_WRITE,
/* mask out the requested bit from the current buffer location
* without advancing the pointer. if set, then set the 8-bit
* value pointed to by the specified offset from the topmost
* pointer to 1; otherwise, set it to 0. this instruction is
* used as follows:
* GPC_I_BIT_MASK_READ <bit index> <offset> */
GPC_I_BIT_MASK_READ,
/* same but store it in something other than a byte. */
GPC_I_BIT_MASK_READ_TO_S,
GPC_I_BIT_MASK_READ_TO_L,
GPC_I_BIT_MASK_READ_TO_LL,
GPC_I_BIT_MASK_READ_TO_F,
GPC_I_BIT_MASK_READ_TO_D,
/* write a bit stored in a byte. the idea is that the value
* is 'normalized', or converted to either a 1 or 0 prior to
* being placed into the buffer. the only argument is the
* offset. */
GPC_I_BIT_WRITE,
/* read a bit store it in a byte. the idea is that the value is
* 'normalized', or converted to either a 1 or 0 prior to being
* placed into the user's data structure. the only argument is
* the offset. */
GPC_I_BIT_READ,
/* read a bit store it in something other than a byte. */
GPC_I_BIT_READ_TO_S,
GPC_I_BIT_READ_TO_L,
GPC_I_BIT_READ_TO_LL,
GPC_I_BIT_READ_TO_F,
GPC_I_BIT_READ_TO_D,
/* skip some number of bytes. operand is the number of bytes. */
GPC_I_SKIP,
/* skip an array; the operands are the size of each element, and
* an offset from the topmost pointer. the array length is found
* at that offset. call this like so:
* GPC_I_ARRAY_BC_AND_SKIP_FIELD <offset> <multiplier> */
GPC_I_ARRAY_BC_AND_SKIP_FIELD,
/* skip an array; this instruction reads the array length, does
* relevant bounds checks, and skips the array. all it needs to
* know is the number of bytes required by each array element.
* call this like so:
* GPC_I_ARRAY_SKIP <constant> */
GPC_I_ARRAY_SKIP,
/* skip a bitvector; the operand is the offset from the topmost
* pointer where the number of bits can be found. */
GPC_I_BITVECTOR_BC_AND_SKIP_FIELD,
/* skip a bitvector; this instruction reads the array length, does
* relevant bounds checks, and skips the bitvector. this
* instruction takes no operands. */
GPC_I_BITVECTOR_SKIP,
/* increment the size register by the specified number of bytes. */
GPC_I_INC_SIZE,
/* examine the array length at the given offset from the topmost
* pointer, and increment the size register by the number of
* bytes that this array length would occupy in the stream. */
GPC_I_INC_SIZE_ARRAY_LEN,
/* increment the size register by the size of the array, where the
* second operand is the size in bytes of each element and the
* first operand is an offset to a tsf_native_array_t structure. */
GPC_I_INC_SIZE_ARRAY,
/* increment the size register to accomodate the number of bits
* as specified by the 32-bit value at the given offset from the
* topmost pointer. */
GPC_I_INC_SIZE_BITVECTOR,
/* bounds check. bounds check failure leads to termination of
* the interpreter with an error. operand is the number of
* bytes we expect to be able to see. */
GPC_I_BC,
/* array bounds check. bounds check failure leads to termination
* of the interpreter with an error. operands are a size
* constant and the offset from the topmost pointer to the
* length of the array (32-bit). if constant*array_len bytes
* are not available, the bounds check fails. call this like so:
* GPC_I_ARRAY_BC_FIELD <offset> <multiplier> */
GPC_I_ARRAY_BC_FIELD,
/* array bounds check. bounds check failure leads to termination
* of the interpreter with an error. operand is a size
* constant. the array length is the topmost stack element. if
* constant*array_len bytes are not available, the bounds check
* fails. */
GPC_I_ARRAY_BC_LOCAL,
/* bitvector bounds check. bounds check failure leads to termination
* of the interpreter with an error. the operand is the offset
* from the topmost pointer to a 32-bit number of bits. if
* (num_bits+7)>>3 bytes are not available, the bounds check fails.
*/
GPC_I_BITVECTOR_BC_FIELD,
/* bitvector bounds check. bounds check failure leads to termination
* of the interpreter with an error. the number of bits is the
* topmost element on the stack. if (num_bits+7)>>3 bytes are not
* available, the bounds check fails.
*/
GPC_I_BITVECTOR_BC_LOCAL,
/* malloc a buffer. will be automatically deallocated upon
* interpreter failure or if it is not returned. if allocation
* fails, the interpreter fails. the allocation size is determined
* by the size register. performing this allocation automatically sets
* the end register so that bounds checks can be performed. */
GPC_I_MALLOC_BUF,
/* pop the buffer off the stack. the current pointer, the buffer
* pointer, the end pointer, the keep bit, and the types pointer are
* all set. however, size is not set (so you cannot do a MALLOC_BUF
* after this call). */
GPC_I_POP_BUF,
/* destroy a buffer. */
GPC_I_DESTROY_BUFFER,
/* create an output map. */
GPC_I_MAKE_OUT_MAP,
/* examine the second-to-top-most pointer. if it is NULL, then perform a
* region alloc and replace the second-to-top-most pointer with the result.
* if the region pointer was NULL, then it is replaced with the result as
* well, and the region is put into keep mode. the only operand is the
* size. */
GPC_I_ALLOC_ROOT_MAYBE_2ND,
/* allocate and push. this is a region alloc. failure here
* leads to termination of the interpreter. the operand is
* the size. */
GPC_I_ALLOC,
/* allocate and push array. this is a region alloc. failure here
* leads to termination of the interpreter. the operands are
* the element size and the offset from the topmost pointer to
* the tsf_native_array_t. tsf_native_array_t::len should be set
* already, and is used to determine how many bytes to alloc.
* tsf_native_array_t::data will be set to the same thing that
* gets pushed -- that is, the newly allocated array. call this like
* so:
* GPC_I_ALLOC_ARRAY <offset> <multiplier> */
GPC_I_ALLOC_ARRAY,
/* allocate and push array. this is a region alloc. failure here
* leads to termination of the interpreter. the operands are
* the element size and the offset from the second to topmost pointer to
* the tsf_native_array_t. tsf_native_array_t::len should be set
* already, and is used to determine how many bytes to alloc.
* tsf_native_array_t::data will be set to the same thing that
* gets pushed -- that is, the newly allocated array. call this like
* so:
* GPC_I_ALLOC_ARRAY <offset> <multiplier> */
GPC_I_ALLOC_ARRAY_2ND,
/* call a function of the form void(*)(void*), passing it the given
* offset from the topmost pointer. call it like so:
* GPC_I_CALL <offset> <function pointer> */
GPC_I_CALL,
/* add callback to current region. callback must be of the form
* void(*)(void*). the argument is computed as an offset from the
* topmost pointer. call it like so:
* GPC_I_ADD_CBACK <offset> <function pointer> */
GPC_I_ADD_CBACK,
/* call free with the top of the stack. */
GPC_I_FREE_IMMEDIATE,
/* call free with the the pointer loaded from stack top + offset. */
GPC_I_FREE,
/* store the topmost pointer at the given offset from the second-to-
* topmost pointer. */
GPC_I_STORE_PTR,
/* store the topmost pointer at the given offset from the third-to-
* topmost pointer. */
GPC_I_STORE_PTR_2ND,
/* store the topmost value at the given offset from the second-to-
* topmost pointer. */
GPC_I_STORE_VAL,
/* push a pointer onto the stack. operand is an
* offset from current top-most pointer where this pointer
* can be found. */
GPC_I_PUSH_PTR,
/* push a pointer onto the stack. operand is an
* offset from current second-to-top-most pointer where this pointer
* can be found. */
GPC_I_PUSH_PTR_2ND,
/* push a pointer onto the stack. operand is an
* offset from current third-to-top-most pointer where this pointer
* can be found. */
GPC_I_PUSH_PTR_3RD,
/* push a 32-bit value onto the stack. operand is an
* offset from current top-most pointer where this
* value can be found. */
GPC_I_PUSH_VAL,
/* dup the second-to-topmost pointer */
GPC_I_DUP_PTR_2ND,
/* pop a pointer or value from the stack. */
GPC_I_POP,
/* pop two things off the stack. */
GPC_I_TWO_POP,
/* pop three things off the stack. */
GPC_I_THREE_POP,
/* first, save the topmost pointer in a register. then, add the
* 32-bit value stored at an offset from the second to
* topmost pointer multiplied by the supplied constant to the topmost
* pointer. finally, push the saved pointer onto the stack.
* this weird instruction looks like this:
* GPC_I_MULADD_PTR <offset> <constant>
* What the heck does this instruction really do? It sets you up
* for a pointer loop! */
GPC_I_REPUSH_MULADD_PTR,
/* first, save the topmost pointer in a register. then, add the
* 32-bit value stored at an offset from the third to
* topmost pointer multiplied by the supplied constant to the topmost
* pointer. finally, push the saved pointer onto the stack.
* this weird instruction looks like this:
* GPC_I_MULADD_PTR <offset> <constant> */
GPC_I_REPUSH_MULADD_PTR_2ND,
/* compare (less-than) the topmost pointer to the next-to-topmost
* pointer. if the comparison fails, jump to the given label.
* this is a forward jump. */
GPC_I_COMPFAILJUMP,
/* compare (less-than) the second to topmost pointer to the third
* to topmost pointer. if the comparison fails, jump to the given
* label. this is a forward jump. */
GPC_I_COMPFAILJUMP_2ND,
/* add the given constant to the topmost pointer, compare it
* (less-than) to the next-to-topmost pointer, and if the
* comparison is true, jump to the given label. note that
* this is a backward branch, so the label is looked for
* backwards, not forwards. here's how to call it:
* GPC_I_ADDCOMPJUMP <constant> <label> */
GPC_I_ADDCOMPJUMP,
/* add the destination constant to the second-to-topmost pointer,
* add the source constant to the topmost pointer, compare the
* second-to-topmost pointer to the third-to-topmost pointer, and
* if the comparison is true, jump to the given label. note that
* this is a backward branch, so the label is looked for backwards,
* not forwards. here's how to call it:
* GPC_I_TWO_ADDCOMPJUMP <dest const> <src const> <label> */
GPC_I_TWO_ADDCOMPJUMP,
/* if the topmost value on the stack is zero, jump forward to
* the given label. */
GPC_I_ZEROJUMP,
/* decrement the value at the top of the stack. if it is non-zero,
* jump backward to the given label. */
GPC_I_DECCOMPJUMP,
/* compares the topmost element on the stack to the supplied constant.
* if the topmost element is UINT32_MAX, nothing happens. if the
* topmost element is less than the supplied constant, nothing happens.
* otherwise, it is an error, and the interpreter fails out. */
GPC_I_CHECKCHOICE,
/* pop the 32-bit value at the top of the stack
* and use it as an index into the given table, and then place
* the looked up value into the location pointed to by the topmost
* pointer plus the offset. here's the way you call this beast:
* GPC_I_TABLESET_LOCAL_TO_FIELD <offset> <num table entries>
* <entry 0> <entry 1> ...
* If the value at the top of the stack is beyond the bounds of
* this table, then it is an error and the interpreter is
* terminated. If the value is UINT32_MAX, then the location is
* set to UINT32_MAX. */
GPC_I_TABLESET_LOCAL_TO_FIELD,
/* read the 32-bit value at the given source offset from the
* topmost pointer and use it as an index into the given table, and
* then place the looked up value into the location pointed to by
* the destination offset plus the second to topmost pointer.
* call this like so:
* GPC_I_TABLESET_FIELD_TO_FIELD <dest offset> <source offset>
* <num table entries> <entry 0> ...
* If the value at the given offset from the topmost pointer is
* beyond the bounds of this table, then it is an error and the
* interpreter is terminated. If the value is UINT32_MAX, then the
* location is set to UINT32_MAX. */
GPC_I_TABLESET_FIELD_TO_FIELD,
/* retrieve the 32-bit value from the offset off of the topmost
* pointer and use it as an index into the given table, and then jump
* to the location labeled by the looked up value. You call this
* beast like so:
* GPC_I_TABLEJUMP_FIELD <offset> <num table entries> <entry 0> ...
* If the value at the given offset from the topmost pointer is
* beyond the bounds of this table, then it is an error and the
* interpreter is terminated. If the value is UINT32_MAX, then no
* jump occurs. */
GPC_I_TABLEJUMP_FIELD,
/* pop the 32-bit value at the top of the stack and
* use it as an index into the given table, and then jump to the
* location labeled by the looked up value. You call this
* beast like so:
* GPC_I_TABLEJUMP <num table entries> <entry 0> ...
* If the value at the topmost pointer is beyond the bounds of
* this table, then it is an error and the interpreter is
* terminated. If the value is UINT32_MAX, then no jump occurs. */
GPC_I_TABLEJUMP_LOCAL,
/* jump to the given label unconditionally. this is a forward jump */
GPC_I_JUMP,
/* returns one. */
GPC_I_RETURN_ONE,
/* returns the top pointer without clearing the region. */
GPC_I_RETURN_TOP_WITH_REGION,
/* assumes that the buffer that the client sees is in the stack slot just
* below the top one. initializes it with the resulting data buffer and sets
* the types to the type_in_man empty singleton. */
GPC_I_RETURN_ONE_INIT_BUF_WITH_EMPTY_TYPES,
/* assumes that the buffer that the client sees is in the stack slot just
* below the top one. initializes it with the resulting data buffer and the
* inverse of the current running out_map. */
GPC_I_RETURN_ONE_INIT_BUF_WITH_TYPES_FROM_OUT_MAP,
/* this is not a real instruction. don't use it. but make sure that
* it is always at the end of this enumeration. */
GPC_I_LAST
};
typedef uintptr_t gpc_cell_t;
/* a list of cell pointers */
struct gpc_cell_list {
gpc_cell_t *cell;
struct gpc_cell_list *next;
};
typedef struct gpc_cell_list gpc_cell_list_t;
/* contains GPC_I_LABEL instructions. */
struct gpc_proto {
uint32_t num_args;
gpc_cell_t *stream;
uint32_t size; /* number of cells allocated for stream. this is also
* the number of cells that have code in them. */
};
/* does not contain GPC_I_LABEL instructions. */
struct gpc_intable {
uint32_t num_args;
gpc_cell_t *stream;
uint32_t size; /* number of cells allocated for stream. this is also
* the number of cells that have code in them. */
tsf_st_table *targets;
tsf_st_table *stack_heights;
intptr_t max_height;
};
typedef struct gpc_intable gpc_intable_t;
/* threaded code */
struct gpc_threaded {
uint32_t num_args;
gpc_cell_t *stream;
uint32_t size; /* number of cells allocated for stream. this is also
* the number of cells that have code in them. */
intptr_t max_height;
};
typedef struct gpc_threaded gpc_threaded_t;
/* the type for a function that implements running a gpc_program. */
typedef uintptr_t (*gpc_program_runner_t)(void *payload,
void *region,
gpc_cell_t *args);
/* the type of a function that desrtroys the payload of a gpc_program. */
typedef void (*gpc_program_destroyer_t)(void *payload);
/* this is what you use to run a GPC program. delegates to one
* of the execution engines. */
struct gpc_program {
void *payload;
gpc_program_runner_t runner;
gpc_program_destroyer_t destroyer;
};
/* returns the size, in the number of slots, that this instruction
* occupies. note that some instructions are variable length. for
* these instructions, this macro returns UINT32_MAX.
* FIXME: this macro is wrong. the nh copy opcodes take one
* operand, not two. */
uint32_t gpc_instruction_static_size(gpc_cell_t inst);
/* returns the effect on the stack from executing this instruction.
* if 0 is returned, that means that the stack height is unaffected. if
* a positive number is returned, then the stack will grow by that amount.
* if a negative number is returned, then the stack will shrink by that
* amount. */
int32_t gpc_instruction_stack_effects(gpc_cell_t inst);
/* return a string with the name of the instruction. */
const char *gpc_instruction_to_string(gpc_cell_t inst);
/* returns the size, in the number of slots, that this instruction
* occupies. note that some instructions are variable length. for
* this reason, this macro requires that you pass in a pointer to
* an instruction in the actual stream, so that the instruction
* stream can be properly queried. */
uint32_t gpc_instruction_size(gpc_cell_t *instptr);
/* function to execute for jump targets within instructions */
typedef tsf_bool_t (*gpc_target_cback_t)(gpc_cell_t *cell,void *arg);
/* executes the given jump target callback for all back branches in
* the given instruction. */
tsf_bool_t gpc_instruction_for_all_back_branches(gpc_cell_t *inst,
gpc_target_cback_t cback,
void *arg);
/* executes the given jump target callback for all forward branches in
* the given instruction. */
tsf_bool_t gpc_instruction_for_all_forward_branches(gpc_cell_t *inst,
gpc_target_cback_t cback,
void *arg);
/* executes the given jump target callback for all branches in
* the given instruction. */
tsf_bool_t gpc_instruction_for_all_branches(gpc_cell_t *inst,
gpc_target_cback_t cback,
void *arg);
/* returns tsf_true if the given instruction causes the program to
* return. */
tsf_bool_t gpc_instruction_is_return(gpc_cell_t inst);
/* returns tsf_true if the given instruction may allow control flow
* to continue to the instruction directly after it. returns tsf_false
* for instructions that do unconditional jumps and that terminate the
* program, or for anything else that will never lead to control flow
* continuing to the next instruction. */
tsf_bool_t gpc_instruction_is_continuous(gpc_cell_t inst);
/* create a prototype. this is just the beginning of the creation
* process. it is then your responsibility to drop in the proper
* opcodes and operands. */
gpc_proto_t *gpc_proto_create(uint32_t num_args);
/* destroy a prototype. */
void gpc_proto_destroy(gpc_proto_t *proto);
/* get the number of arguments */
#define gpc_proto_get_num_args(proto) \
((proto)->num_args)
/* reserve the specified number of additional slots. */
tsf_bool_t gpc_proto_reserve(gpc_proto_t *proto,
uint32_t num_slots);
/* append an instruction. you are required to supply the correct
* number of operands. */
tsf_bool_t gpc_proto_append(gpc_proto_t *proto,
gpc_cell_t inst,
...);
/* append a tableset_local_to_field instruction */
tsf_bool_t gpc_proto_append_tableset_local_to_field(gpc_proto_t *proto,
gpc_cell_t offset,
gpc_cell_t num,
const gpc_cell_t *args);
/* append a tableset_field_to_field instruction */
tsf_bool_t gpc_proto_append_tableset_field_to_field(gpc_proto_t *proto,
gpc_cell_t dest_offset,
gpc_cell_t src_offset,
gpc_cell_t num,
const gpc_cell_t *args);
/* append a tablejump_local instruction */
tsf_bool_t gpc_proto_append_tablejump_local(gpc_proto_t *proto,
gpc_cell_t num,
const gpc_cell_t *args);
/* append a tablejump_local instruction */
tsf_bool_t gpc_proto_append_tablejump_field(gpc_proto_t *proto,
gpc_cell_t offset,
gpc_cell_t num,
const gpc_cell_t *args);
/* print the prototype to the given FILE stream. */
void gpc_proto_print(gpc_proto_t *proto,
FILE *out);
/* debugging stuff */
void gpc_code_gen_debug(const char *type,
gpc_proto_t *proto);
/* create an interpretable from a prototype. */
gpc_intable_t *gpc_intable_from_proto(gpc_proto_t *proto);
/* clone an interpretable */
gpc_intable_t *gpc_intable_clone(gpc_intable_t *intable);
/* destroy an interpretable. */
void gpc_intable_destroy(gpc_intable_t *intable);
/* get the number of arguments */
#define gpc_intable_get_num_args(intable) \
((intable)->num_args)
/* get the beginning of the intable stream */
#define gpc_intable_get_stream(intable) \
((intable)->stream)
/* get the end (STL-style) of the intable stream */
#define gpc_intable_get_stream_end(intable) \
((intable)->stream+(intable)->size)
/* returns tsf_true if the given cell address is a jump target */
#define gpc_intable_is_target(intable,address) \
(tsf_st_lookup((intable)->targets,(char*)address,NULL))
/* returns tsf_true if the given cell address is live code */
#define gpc_intable_is_live(intable,address) \
(tsf_st_lookup((intable)->stack_heights,(char*)address,NULL))
/* returns the stack height at the given piece of code. will return
-1 if the code is not live. note that the error code is not set in
either case. */
intptr_t gpc_intable_get_height(gpc_intable_t *prog,
gpc_cell_t *address);
/* returns tsf_true if gpc_intable_run is supported. */
tsf_bool_t gpc_intable_run_supported();
/* interpret the intable. */
uintptr_t gpc_intable_run(gpc_intable_t *prog,
void *region,
gpc_cell_t *args);
/* write the program using the given writer. */
tsf_bool_t gpc_intable_write(gpc_intable_t *prog,
tsf_writer_t writer,
void *arg);
/* NOTE: the all_returns, predecessors, and min_remaining analyses are
* currently unused and so hence are excluded from the Makefile and from
* the distribution. They are, however, included in CVS, lest they ever
* become useful. */
/* analysis to find all returns. returns a NULL-terminated array of return
* points in the given program.
*
* FIXME: this really should not be a separate analysis. it should be just
* an extra return from gpc_intable_get_predecessors(). */
gpc_cell_t **gpc_intable_find_all_returns(gpc_intable_t *prog);
/* find predecessors. returns a hashtable mapping each instruction cell
* to a linked list of its predecessors. the linked list is typed as a
* gpc_cell_list_t. */
tsf_st_table *gpc_intable_get_predecessors(gpc_intable_t *prog);
/* you should use this function to free up the list returned by the
* gpc_intable_get_predecessors() function. */
void gpc_intable_destroy_predecessors(tsf_st_table *preds);
/* gives you a table that maps each instruction to the minimum number of
* bytes that you are guaranteed to have left in the buffer, assuming
* that the program does not overrun the buffer in the end. */
tsf_st_table *gpc_intable_get_min_remaining(gpc_intable_t *prog);
/* return true if the given intable ever does integer to float
* conversion. */
tsf_bool_t gpc_intable_can_do_int_to_float_conversion(gpc_intable_t *prog);
/* returns tsf_true if the given intable ever calls functions that require
* a pointer to the buffer pointer register. */
tsf_bool_t gpc_intable_can_call_buffer_mutator_functions(gpc_intable_t *prog);
/* returns tsf_true if the given intable ever uses the in_map (also
known as types). definitions of in_map don't con't - so this will
return tsf_false if the intable defines but does not use the
in_map. */
tsf_bool_t gpc_intable_can_use_in_map(gpc_intable_t *prog);
/* returns tsf_true if the given intable ever uses the out_map.
definitions of out_map don't con't - so this will return tsf_false
if the intable defines but does not use the out_map. */
tsf_bool_t gpc_intable_can_use_out_map(gpc_intable_t *prog);
/* returns tsf_true if gpc_threaded_t is supported. */
tsf_bool_t gpc_threaded_supported();
/* create a threaded program from an intable. this may fail if GPC
* was compiled with a compiler that does not support the address-of-
* label extension. */
gpc_threaded_t *gpc_threaded_from_intable(gpc_intable_t *intable);
/* destroy a threaded */
void gpc_threaded_destroy(gpc_threaded_t *threaded);
/* get the number of arguments */
#define gpc_threaded_get_num_args(threaded) \
((threaded)->num_args)
/* run a threaded */
uintptr_t gpc_threaded_run(gpc_threaded_t *prog,
void *region,
gpc_cell_t *args);
/* create a program using the engine of your choice. if you pass in
* TSF_EN_DEFAULT, then the default engine is used (based on the
* TSF_ENGINE_CONFIG environment variable; if it is not set, then
* the default is GPINT). if you pass in TSF_EN_NOT_SUPPORTED, then
* NULL is returned without the error being set (this allows for error
* propagation). */
gpc_program_t *gpc_program_from_proto(gpc_proto_t *proto);
/* same as above but create from an intable. */
gpc_program_t *gpc_program_from_intable(gpc_intable_t *intable);
/* destroy a program */
void gpc_program_destroy(gpc_program_t *prog);
/* run a program */
uintptr_t gpc_program_run(gpc_program_t *prog,
void *region,
gpc_cell_t *args);
#endif