blob: 1ba012e57a5e8f5babcbec4262278ea55c22e7a2 [file] [log] [blame]
/*
* *****************************************************************************
*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2018-2021 Gavin D. Howard and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER 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.
*
* *****************************************************************************
*
* Code to execute bc programs.
*
*/
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include <setjmp.h>
#include <signal.h>
#include <time.h>
#include <read.h>
#include <parse.h>
#include <program.h>
#include <vm.h>
/**
* Quickly sets the const and strs vector pointers in the program. This is a
* convenience function.
* @param p The program.
* @param f The new function.
*/
static inline void bc_program_setVecs(BcProgram *p, BcFunc *f) {
p->consts = &f->consts;
p->strs = &f->strs;
}
/**
* Does a type check for something that expects a number.
* @param r The result that will be checked.
* @param n The result's number.
*/
static inline void bc_program_type_num(BcResult *r, BcNum *n) {
#if BC_ENABLED
// This should have already been taken care of.
assert(r->t != BC_RESULT_VOID);
#endif // BC_ENABLED
if (BC_ERR(!BC_PROG_NUM(r, n))) bc_err(BC_ERR_EXEC_TYPE);
}
#if BC_ENABLED
/**
* Does a type check.
* @param r The result to check.
* @param t The type that the result should be.
*/
static void bc_program_type_match(BcResult *r, BcType t) {
if (BC_ERR((r->t != BC_RESULT_ARRAY) != (!t))) bc_err(BC_ERR_EXEC_TYPE);
}
#endif // BC_ENABLED
/**
* Pulls an index out of a bytecode vector and updates the index into the vector
* to point to the spot after the index. For more details on bytecode indices,
* see the development manual (manuals/development.md#bytecode-indices).
* @param code The bytecode vector.
* @param bgn An in/out parameter; the index into the vector that will be
* updated.
* @return The index at @a bgn in the bytecode vector.
*/
static size_t bc_program_index(const char *restrict code, size_t *restrict bgn)
{
uchar amt = (uchar) code[(*bgn)++], i = 0;
size_t res = 0;
for (; i < amt; ++i, ++(*bgn)) {
size_t temp = ((size_t) ((int) (uchar) code[*bgn]) & UCHAR_MAX);
res |= (temp << (i * CHAR_BIT));
}
return res;
}
/**
* Returns a string from a result and its number.
* @param p The program.
* @param n The number tied to the result.
* @return The string corresponding to the result and number.
*/
static char* bc_program_string(BcProgram *p, const BcNum *n) {
BcFunc *f = bc_vec_item(&p->fns, n->rdx);
return *((char**) bc_vec_item(&f->strs, n->scale));
}
#if BC_ENABLED
/**
* Prepares the globals for a function call. This is only called when global
* stacks are on because it pushes a copy of the current globals onto each of
* their respective stacks.
* @param p The program.
*/
static void bc_program_prepGlobals(BcProgram *p) {
size_t i;
for (i = 0; i < BC_PROG_GLOBALS_LEN; ++i)
bc_vec_push(p->globals_v + i, p->globals + i);
#if BC_ENABLE_EXTRA_MATH
bc_rand_push(&p->rng);
#endif // BC_ENABLE_EXTRA_MATH
}
/**
* Pops globals stacks on returning from a function, or in the case of reset,
* pops all but one item on each global stack.
* @param p The program.
* @param reset True if all but one item on each stack should be popped, false
* otherwise.
*/
static void bc_program_popGlobals(BcProgram *p, bool reset) {
size_t i;
for (i = 0; i < BC_PROG_GLOBALS_LEN; ++i) {
BcVec *v = p->globals_v + i;
bc_vec_npop(v, reset ? v->len - 1 : 1);
p->globals[i] = BC_PROG_GLOBAL(v);
}
#if BC_ENABLE_EXTRA_MATH
bc_rand_pop(&p->rng, reset);
#endif // BC_ENABLE_EXTRA_MATH
}
/**
* Derefeneces an array reference and returns a pointer to the real array.
* @param p The program.
* @param vec The reference vector.
* @return A pointer to the desired array.
*/
static BcVec* bc_program_dereference(const BcProgram *p, BcVec *vec) {
BcVec *v;
size_t vidx, nidx, i = 0;
// We want to be sure we have a reference vector.
assert(vec->size == sizeof(uchar));
// Get the index of the vector in arrs, then the index of the original
// referenced vector.
vidx = bc_program_index(vec->v, &i);
nidx = bc_program_index(vec->v, &i);
v = bc_vec_item(bc_vec_item(&p->arrs, vidx), nidx);
// We want to be sure we do *not* have a reference vector.
assert(v->size != sizeof(uchar));
return v;
}
#endif // BC_ENABLED
/**
* Creates a BcNum from a BcBigDig and pushes onto the results stack. This is a
* convenience function.
* @param p The program.
* @param dig The BcBigDig to push onto the results stack.
* @param type The type that the pushed result should be.
*/
static void bc_program_pushBigdig(BcProgram *p, BcBigDig dig, BcResultType type)
{
BcResult res;
res.t = type;
BC_SIG_LOCK;
bc_num_createFromBigdig(&res.d.n, dig);
bc_vec_push(&p->results, &res);
BC_SIG_UNLOCK;
}
size_t bc_program_addString(BcProgram *p, const char *str, size_t fidx) {
BcFunc *f;
char **str_ptr;
BcVec *slabs;
BC_SIG_ASSERT_LOCKED;
// Push an empty string on the proper vector.
f = bc_vec_item(&p->fns, fidx);
str_ptr = bc_vec_pushEmpty(&f->strs);
// Figure out which slab vector to use.
slabs = fidx == BC_PROG_MAIN || fidx == BC_PROG_READ ?
&vm.main_slabs : &vm.other_slabs;
*str_ptr = bc_slabvec_strdup(slabs, str);
return f->strs.len - 1;
}
size_t bc_program_search(BcProgram *p, const char *id, bool var) {
BcVec *v, *map;
size_t i;
// Grab the right vector and map.
v = var ? &p->vars : &p->arrs;
map = var ? &p->var_map : &p->arr_map;
BC_SIG_LOCK;
// We do an insert because the variable might not exist yet. This is because
// the parser calls this function. If the insert succeeds, we create a stack
// for the variable/array. But regardless, bc_map_insert() gives us the
// index of the item in i.
if (bc_map_insert(map, id, v->len, &i)) {
BcVec *temp = bc_vec_pushEmpty(v);
bc_array_init(temp, var);
}
BC_SIG_UNLOCK;
return ((BcId*) bc_vec_item(map, i))->idx;
}
/**
* Returns the correct variable or array stack for the type.
* @param p The program.
* @param idx The index of the variable or array in the variable or array
* vector.
* @param type The type of vector to return.
* @return A pointer to the variable or array stack.
*/
static inline BcVec* bc_program_vec(const BcProgram *p, size_t idx, BcType type)
{
const BcVec *v = (type == BC_TYPE_VAR) ? &p->vars : &p->arrs;
return bc_vec_item(v, idx);
}
/**
* Returns a pointer to the BcNum corresponding to the result. There is one
* case, however, where this returns a pointer to a BcVec: if the type of the
* result is array. In that case, the pointer is casted to a pointer to BcNum,
* but is never used. The function that calls this expecting an array casts the
* pointer back. This function is called a lot and needs to be as fast as
* possible.
* @param p The program.
* @param r The result whose number will be returned.
* @return The BcNum corresponding to the result.
*/
static BcNum* bc_program_num(BcProgram *p, BcResult *r) {
BcNum *n;
switch (r->t) {
case BC_RESULT_STR:
case BC_RESULT_TEMP:
case BC_RESULT_IBASE:
case BC_RESULT_SCALE:
case BC_RESULT_OBASE:
#if BC_ENABLE_EXTRA_MATH
case BC_RESULT_SEED:
#endif // BC_ENABLE_EXTRA_MATH
{
n = &r->d.n;
break;
}
case BC_RESULT_VAR:
case BC_RESULT_ARRAY:
case BC_RESULT_ARRAY_ELEM:
{
BcVec *v;
BcType type = (r->t == BC_RESULT_VAR) ? BC_TYPE_VAR : BC_TYPE_ARRAY;
// Get the correct variable or array vector.
v = bc_program_vec(p, r->d.loc.loc, type);
// Surprisingly enough, the hard case is *not* returning an array;
// it's returning an array element. This is because we have to dig
// deeper to get *to* the element. That's what the code inside this
// if statement does.
if (r->t == BC_RESULT_ARRAY_ELEM) {
size_t idx = r->d.loc.idx;
v = bc_vec_top(v);
#if BC_ENABLED
// If this is true, we have a reference vector, so dereference
// it. The reason we don't need to worry about it for returning
// a straight array is because we only care about references
// when we access elements of an array that is a reference. That
// is this code, so in essence, this line takes care of arrays
// as well.
if (v->size == sizeof(uchar)) v = bc_program_dereference(p, v);
#endif // BC_ENABLED
// We want to be sure we got a valid array of numbers.
assert(v->size == sizeof(BcNum));
// The bc spec says that if an element is accessed that does not
// exist, it should be preinitialized to 0. Well, if we access
// an element *way* out there, we have to preinitialize all
// elements between the current last element and the actual
// accessed element.
if (v->len <= idx) {
BC_SIG_LOCK;
bc_array_expand(v, bc_vm_growSize(idx, 1));
BC_SIG_UNLOCK;
}
n = bc_vec_item(v, idx);
}
// This is either a number (for a var) or an array (for an array).
// Because bc_vec_top() returns a void*, we don't need to cast.
else n = bc_vec_top(v);
break;
}
case BC_RESULT_ZERO:
{
n = &vm.zero;
break;
}
case BC_RESULT_ONE:
{
n = &vm.one;
break;
}
#if BC_ENABLED
// We should never get here; this is taken care of earlier because a
// result is expected.
case BC_RESULT_VOID:
#ifndef NDEBUG
{
abort();
}
#endif // NDEBUG
// Fallthrough
case BC_RESULT_LAST:
{
n = &p->last;
break;
}
#endif // BC_ENABLED
}
return n;
}
/**
* Prepares an operand for use.
* @param p The program.
* @param r An out parameter; this is set to the pointer to the result that
* we care about.
* @param n An out parameter; this is set to the pointer to the number that
* we care about.
* @param idx The index of the result from the top of the results stack.
*/
static void bc_program_operand(BcProgram *p, BcResult **r,
BcNum **n, size_t idx)
{
*r = bc_vec_item_rev(&p->results, idx);
#if BC_ENABLED
if (BC_ERR((*r)->t == BC_RESULT_VOID)) bc_err(BC_ERR_EXEC_VOID_VAL);
#endif // BC_ENABLED
*n = bc_program_num(p, *r);
}
/**
* Prepares the operands of a binary operator.
* @param p The program.
* @param l An out parameter; this is set to the pointer to the result for
* the left operand.
* @param ln An out parameter; this is set to the pointer to the number for
* the left operand.
* @param r An out parameter; this is set to the pointer to the result for
* the right operand.
* @param rn An out parameter; this is set to the pointer to the number for
* the right operand.
* @param idx The starting index where the operands are in the results stack,
* starting from the top.
*/
static void bc_program_binPrep(BcProgram *p, BcResult **l, BcNum **ln,
BcResult **r, BcNum **rn, size_t idx)
{
BcResultType lt;
assert(p != NULL && l != NULL && ln != NULL && r != NULL && rn != NULL);
#ifndef BC_PROG_NO_STACK_CHECK
// Check the stack for dc.
if (BC_IS_DC) {
if (BC_ERR(!BC_PROG_STACK(&p->results, idx + 2)))
bc_err(BC_ERR_EXEC_STACK);
}
#endif // BC_PROG_NO_STACK_CHECK
assert(BC_PROG_STACK(&p->results, idx + 2));
// Get the operands.
bc_program_operand(p, l, ln, idx + 1);
bc_program_operand(p, r, rn, idx);
lt = (*l)->t;
#if BC_ENABLED
// bc_program_operand() checked these for us.
assert(lt != BC_RESULT_VOID && (*r)->t != BC_RESULT_VOID);
#endif // BC_ENABLED
// We run this again under these conditions in case any vector has been
// reallocated out from under the BcNums or arrays we had. In other words,
// this is to fix pointer invalidation.
if (lt == (*r)->t && (lt == BC_RESULT_VAR || lt == BC_RESULT_ARRAY_ELEM))
*ln = bc_program_num(p, *l);
if (BC_ERR(lt == BC_RESULT_STR)) bc_err(BC_ERR_EXEC_TYPE);
}
/**
* Prepares the operands of a binary operator and type checks them. This is
* separate from bc_program_binPrep() because some places want this, others want
* bc_program_binPrep().
* @param p The program.
* @param l An out parameter; this is set to the pointer to the result for
* the left operand.
* @param ln An out parameter; this is set to the pointer to the number for
* the left operand.
* @param r An out parameter; this is set to the pointer to the result for
* the right operand.
* @param rn An out parameter; this is set to the pointer to the number for
* the right operand.
* @param idx The starting index where the operands are in the results stack,
* starting from the top.
*/
static void bc_program_binOpPrep(BcProgram *p, BcResult **l, BcNum **ln,
BcResult **r, BcNum **rn, size_t idx)
{
bc_program_binPrep(p, l, ln, r, rn, idx);
bc_program_type_num(*l, *ln);
bc_program_type_num(*r, *rn);
}
/**
* Prepares the operands of an assignment operator.
* @param p The program.
* @param l An out parameter; this is set to the pointer to the result for the
* left operand.
* @param ln An out parameter; this is set to the pointer to the number for the
* left operand.
* @param r An out parameter; this is set to the pointer to the result for the
* right operand.
* @param rn An out parameter; this is set to the pointer to the number for the
* right operand.
*/
static void bc_program_assignPrep(BcProgram *p, BcResult **l, BcNum **ln,
BcResult **r, BcNum **rn)
{
BcResultType lt, min;
// This is the min non-allowable result type. dc allows strings.
min = BC_RESULT_TEMP - ((unsigned int) (BC_IS_BC));
// Prepare the operands.
bc_program_binPrep(p, l, ln, r, rn, 0);
lt = (*l)->t;
// Typecheck the left.
if (BC_ERR(lt >= min && lt <= BC_RESULT_ONE)) bc_err(BC_ERR_EXEC_TYPE);
// Strings can be assigned to variables. We are already good if we are
// assigning a string.
bool good = ((*r)->t == BC_RESULT_STR && lt <= BC_RESULT_ARRAY_ELEM);
assert(BC_PROG_STR(*rn) || (*r)->t != BC_RESULT_STR);
// If not, type check for a number.
if (!good) bc_program_type_num(*r, *rn);
}
/**
* Prepares a single operand and type checks it. This is separate from
* bc_program_operand() because different places want one or the other.
* @param p The program.
* @param r An out parameter; this is set to the pointer to the result that
* we care about.
* @param n An out parameter; this is set to the pointer to the number that
* we care about.
* @param idx The index of the result from the top of the results stack.
*/
static void bc_program_prep(BcProgram *p, BcResult **r, BcNum **n, size_t idx) {
assert(p != NULL && r != NULL && n != NULL);
#ifndef BC_PROG_NO_STACK_CHECK
// Check the stack for dc.
if (BC_IS_DC) {
if (BC_ERR(!BC_PROG_STACK(&p->results, idx + 1)))
bc_err(BC_ERR_EXEC_STACK);
}
#endif // BC_PROG_NO_STACK_CHECK
assert(BC_PROG_STACK(&p->results, idx + 1));
bc_program_operand(p, r, n, idx);
// dc does not allow strings in this case.
bc_program_type_num(*r, *n);
}
/**
* Prepares and returns a clean result for the result of an operation.
* @param p The program.
* @return A clean result.
*/
static BcResult* bc_program_prepResult(BcProgram *p) {
BcResult *res = bc_vec_pushEmpty(&p->results);
bc_result_clear(res);
return res;
}
/**
* Prepares a constant for use. This parses the constant into a number and then
* pushes that number onto the results stack.
* @param p The program.
* @param code The bytecode vector that we will pull the index of the constant
* from.
* @param bgn An in/out parameter; marks the start of the index in the
* bytecode vector and will be updated to point to after the index.
*/
static void bc_program_const(BcProgram *p, const char *code, size_t *bgn) {
// I lied. I actually push the result first. I can do this because the
// result will be popped on error. I also get the constant itself.
BcResult *r = bc_program_prepResult(p);
BcConst *c = bc_vec_item(p->consts, bc_program_index(code, bgn));
BcBigDig base = BC_PROG_IBASE(p);
// Only reparse if the base changed.
if (c->base != base) {
// Allocate if we haven't yet.
if (c->num.num == NULL) {
BC_SIG_LOCK;
bc_num_init(&c->num, BC_NUM_RDX(strlen(c->val)));
BC_SIG_UNLOCK;
}
// bc_num_parse() should only do operations that cannot fail.
bc_num_parse(&c->num, c->val, base);
c->base = base;
}
BC_SIG_LOCK;
bc_num_createCopy(&r->d.n, &c->num);
BC_SIG_UNLOCK;
}
/**
* Executes a binary operator operation.
* @param p The program.
* @param inst The instruction corresponding to the binary operator to execute.
*/
static void bc_program_op(BcProgram *p, uchar inst) {
BcResult *opd1, *opd2, *res;
BcNum *n1, *n2;
size_t idx = inst - BC_INST_POWER;
res = bc_program_prepResult(p);
bc_program_binOpPrep(p, &opd1, &n1, &opd2, &n2, 1);
BC_SIG_LOCK;
// Initialize the number with enough space, using the correct
// BcNumBinaryOpReq function. This looks weird because it is executing an
// item of an array. Rest assured that item is a function.
bc_num_init(&res->d.n, bc_program_opReqs[idx](n1, n2, BC_PROG_SCALE(p)));
BC_SIG_UNLOCK;
assert(BC_NUM_RDX_VALID(n1));
assert(BC_NUM_RDX_VALID(n2));
// Run the operation. This also executes an item of an array.
bc_program_ops[idx](n1, n2, &res->d.n, BC_PROG_SCALE(p));
bc_program_retire(p, 1, 2);
}
/**
* Executes a read() or ? command.
* @param p The program.
*/
static void bc_program_read(BcProgram *p) {
BcStatus s;
BcInstPtr ip;
size_t i;
const char* file;
bool is_stdin;
BcFunc *f = bc_vec_item(&p->fns, BC_PROG_READ);
// If we are already executing a read, that is an error. So look for a read
// and barf.
for (i = 0; i < p->stack.len; ++i) {
BcInstPtr *ip_ptr = bc_vec_item(&p->stack, i);
if (ip_ptr->func == BC_PROG_READ) bc_err(BC_ERR_EXEC_REC_READ);
}
BC_SIG_LOCK;
// Save the filename because we are going to overwrite it.
file = vm.file;
is_stdin = vm.is_stdin;
// It is a parse error if there needs to be more than one line, so we unset
// this to tell the lexer to not request more. We set it back later.
vm.is_stdin = false;
if (!BC_PARSE_IS_INITED(&vm.read_prs, p)) {
// We need to parse, but we don't want to use the existing parser
// because it has state it needs to keep. (It could have a partial parse
// state.) So we create a new parser. This parser is in the BcVm struct
// so that it is not local, which means that a longjmp() could change
// it.
bc_parse_init(&vm.read_prs, p, BC_PROG_READ);
// We need a separate input buffer; that's why it is also in the BcVm
// struct.
bc_vec_init(&vm.read_buf, sizeof(char), BC_DTOR_NONE);
}
// This needs to be updated because the parser could have been used
// somewhere else
else bc_parse_updateFunc(&vm.read_prs, BC_PROG_READ);
BC_SETJMP_LOCKED(exec_err);
BC_SIG_UNLOCK;
// Set up the lexer and the read function.
bc_lex_file(&vm.read_prs.l, bc_program_stdin_name);
bc_vec_popAll(&f->code);
// Read a line.
if (!BC_R) s = bc_read_line(&vm.read_buf, "");
else s = bc_read_line(&vm.read_buf, BC_IS_BC ? "read> " : "?> ");
// We should *not* have run into EOF.
if (s == BC_STATUS_EOF) bc_err(BC_ERR_EXEC_READ_EXPR);
// Parse *one* expression.
bc_parse_text(&vm.read_prs, vm.read_buf.v, false);
vm.expr(&vm.read_prs, BC_PARSE_NOREAD | BC_PARSE_NEEDVAL);
// We *must* have a valid expression. A semicolon cannot end an expression,
// although EOF can.
if (BC_ERR(vm.read_prs.l.t != BC_LEX_NLINE &&
vm.read_prs.l.t != BC_LEX_EOF))
{
bc_err(BC_ERR_EXEC_READ_EXPR);
}
#if BC_ENABLED
// Push on the globals stack if necessary.
if (BC_G) bc_program_prepGlobals(p);
#endif // BC_ENABLED
// Set up a new BcInstPtr.
ip.func = BC_PROG_READ;
ip.idx = 0;
ip.len = p->results.len;
// Update this pointer, just in case.
f = bc_vec_item(&p->fns, BC_PROG_READ);
// We want a return instruction to simplify things.
bc_vec_pushByte(&f->code, vm.read_ret);
bc_vec_push(&p->stack, &ip);
#if DC_ENABLED
// We need a new tail call entry for dc.
if (BC_IS_DC) {
size_t temp = 0;
bc_vec_push(&p->tail_calls, &temp);
}
#endif // DC_ENABLED
exec_err:
BC_SIG_MAYLOCK;
vm.is_stdin = is_stdin;
vm.file = file;
BC_LONGJMP_CONT;
}
#if BC_ENABLE_EXTRA_MATH
/**
* Execute a rand().
* @param p The program.
*/
static void bc_program_rand(BcProgram *p) {
BcRand rand = bc_rand_int(&p->rng);
bc_program_pushBigdig(p, (BcBigDig) rand, BC_RESULT_TEMP);
#ifndef NDEBUG
// This is just to ensure that the generated number is correct. I also use
// braces because I declare every local at the top of the scope.
{
BcResult *r = bc_vec_top(&p->results);
assert(BC_NUM_RDX_VALID_NP(r->d.n));
}
#endif // NDEBUG
}
#endif // BC_ENABLE_EXTRA_MATH
/**
* Prints a series of characters, without escapes.
* @param str The string (series of characters).
*/
static void bc_program_printChars(const char *str) {
const char *nl;
size_t len = vm.nchars + strlen(str);
bc_file_puts(&vm.fout, bc_flush_save, str);
// We need to update the number of characters, so we find the last newline
// and set the characters accordingly.
nl = strrchr(str, '\n');
if (nl != NULL) len = strlen(nl + 1);
vm.nchars = len > UINT16_MAX ? UINT16_MAX : (uint16_t) len;
}
/**
* Prints a string with escapes.
* @param str The string.
*/
static void bc_program_printString(const char *restrict str) {
size_t i, len = strlen(str);
#if DC_ENABLED
// This is to ensure a nul byte is printed for dc's stream operation.
if (!len && BC_IS_DC) {
bc_vm_putchar('\0', bc_flush_save);
return;
}
#endif // DC_ENABLED
// Loop over the characters, processing escapes and printing the rest.
for (i = 0; i < len; ++i) {
int c = str[i];
// If we have an escape...
if (c == '\\' && i != len - 1) {
const char *ptr;
// Get the escape character and its companion.
c = str[++i];
ptr = strchr(bc_program_esc_chars, c);
// If we have a companion character...
if (ptr != NULL) {
// We need to specially handle a newline.
if (c == 'n') vm.nchars = UINT16_MAX;
// Grab the actual character.
c = bc_program_esc_seqs[(size_t) (ptr - bc_program_esc_chars)];
}
else {
// Just print the backslash if there is no companion character.
// The following character will be printed later after the outer
// if statement.
bc_vm_putchar('\\', bc_flush_save);
}
}
bc_vm_putchar(c, bc_flush_save);
}
}
/**
* Executes a print. This function handles all printing except streaming.
* @param p The program.
* @param inst The instruction for the type of print we are doing.
* @param idx The index of the result that we are printing.
*/
static void bc_program_print(BcProgram *p, uchar inst, size_t idx) {
BcResult *r;
char *str;
BcNum *n;
bool pop = (inst != BC_INST_PRINT);
assert(p != NULL);
#ifndef BC_PROG_NO_STACK_CHECK
if (BC_IS_DC) {
if (BC_ERR(!BC_PROG_STACK(&p->results, idx + 1)))
bc_err(BC_ERR_EXEC_STACK);
}
#endif // BC_PROG_NO_STACK_CHECK
assert(BC_PROG_STACK(&p->results, idx + 1));
r = bc_vec_item_rev(&p->results, idx);
#if BC_ENABLED
// If we have a void value, that's not necessarily an error. It is if pop is
// true because that means that we are executing a print statement, but
// attempting to do a print on a lone void value is allowed because that's
// exactly how we want void values used.
if (r->t == BC_RESULT_VOID) {
if (BC_ERR(pop)) bc_err(BC_ERR_EXEC_VOID_VAL);
bc_vec_pop(&p->results);
return;
}
#endif // BC_ENABLED
n = bc_program_num(p, r);
// If we have a number...
if (BC_PROG_NUM(r, n)) {
#if BC_ENABLED
assert(inst != BC_INST_PRINT_STR);
#endif // BC_ENABLED
// Print the number.
bc_num_print(n, BC_PROG_OBASE(p), !pop);
#if BC_ENABLED
// Need to store the number in last.
if (BC_IS_BC) bc_num_copy(&p->last, n);
#endif // BC_ENABLED
}
else {
// We want to flush any stuff in the stdout buffer first.
bc_file_flush(&vm.fout, bc_flush_save);
str = bc_program_string(p, n);
#if BC_ENABLED
if (inst == BC_INST_PRINT_STR) bc_program_printChars(str);
else
#endif // BC_ENABLED
{
bc_program_printString(str);
// Need to print a newline only in this case.
if (inst == BC_INST_PRINT)
bc_vm_putchar('\n', bc_flush_err);
}
}
// bc always pops.
if (BC_IS_BC || pop) bc_vec_pop(&p->results);
}
void bc_program_negate(BcResult *r, BcNum *n) {
bc_num_copy(&r->d.n, n);
if (BC_NUM_NONZERO(&r->d.n)) BC_NUM_NEG_TGL_NP(r->d.n);
}
void bc_program_not(BcResult *r, BcNum *n) {
if (!bc_num_cmpZero(n)) bc_num_one(&r->d.n);
}
#if BC_ENABLE_EXTRA_MATH
void bc_program_trunc(BcResult *r, BcNum *n) {
bc_num_copy(&r->d.n, n);
bc_num_truncate(&r->d.n, n->scale);
}
#endif // BC_ENABLE_EXTRA_MATH
/**
* Runs a unary operation.
* @param p The program.
* @param inst The unary operation.
*/
static void bc_program_unary(BcProgram *p, uchar inst) {
BcResult *res, *ptr;
BcNum *num;
res = bc_program_prepResult(p);
bc_program_prep(p, &ptr, &num, 1);
BC_SIG_LOCK;
bc_num_init(&res->d.n, num->len);
BC_SIG_UNLOCK;
// This calls a function that is in an array.
bc_program_unarys[inst - BC_INST_NEG](res, num);
bc_program_retire(p, 1, 1);
}
/**
* Executes a logical operator.
* @param p The program.
* @param inst The operator.
*/
static void bc_program_logical(BcProgram *p, uchar inst) {
BcResult *opd1, *opd2, *res;
BcNum *n1, *n2;
bool cond = 0;
ssize_t cmp;
res = bc_program_prepResult(p);
// All logical operators (except boolean not, which is taken care of by
// bc_program_unary()), are binary operators.
bc_program_binOpPrep(p, &opd1, &n1, &opd2, &n2, 1);
// Boolean and and or are not short circuiting. This is why; they can be
// implemented much easier this way.
if (inst == BC_INST_BOOL_AND)
cond = (bc_num_cmpZero(n1) && bc_num_cmpZero(n2));
else if (inst == BC_INST_BOOL_OR)
cond = (bc_num_cmpZero(n1) || bc_num_cmpZero(n2));
else {
// We have a relational operator, so do a comparison.
cmp = bc_num_cmp(n1, n2);
switch (inst) {
case BC_INST_REL_EQ:
{
cond = (cmp == 0);
break;
}
case BC_INST_REL_LE:
{
cond = (cmp <= 0);
break;
}
case BC_INST_REL_GE:
{
cond = (cmp >= 0);
break;
}
case BC_INST_REL_NE:
{
cond = (cmp != 0);
break;
}
case BC_INST_REL_LT:
{
cond = (cmp < 0);
break;
}
case BC_INST_REL_GT:
{
cond = (cmp > 0);
break;
}
#ifndef NDEBUG
default:
{
// There is a bug if we get here.
abort();
}
#endif // NDEBUG
}
}
BC_SIG_LOCK;
bc_num_init(&res->d.n, BC_NUM_DEF_SIZE);
BC_SIG_UNLOCK;
if (cond) bc_num_one(&res->d.n);
bc_program_retire(p, 1, 2);
}
/**
* Assigns a string to a variable.
* @param p The program.
* @param num The location of the string as a BcNum.
* @param v The stack for the variable.
* @param push Whether to push the string or not. To push means to move the
* string from the results stack and push it onto the variable
* stack.
*/
static void bc_program_assignStr(BcProgram *p, BcNum *num, BcVec *v, bool push)
{
BcNum *n;
assert(BC_PROG_STACK(&p->results, 1 + !push));
assert(num != NULL && num->num == NULL && num->cap == 0);
// If we are not pushing onto the variable stack, we need to replace the
// top of the variable stack.
if (!push) bc_vec_pop(v);
bc_vec_npop(&p->results, 1 + !push);
n = bc_vec_pushEmpty(v);
// We can just copy because the num should not have allocated anything.
memcpy(n, num, sizeof(BcNum));
}
/**
* Copies a value to a variable. This is used for storing in dc as well as to
* set function parameters to arguments in bc.
* @param p The program.
* @param idx The index of the variable or array to copy to.
* @param t The type to copy to. This could be a variable or an array.
* @param last Whether to grab the last item on the variable stack or not (for
* bc function parameters). This is important because if a new
* value has been pushed to the variable already, we need to grab
* the value pushed before. This happens when you have a parameter
* named something like "x", and a variable "x" is passed to
* another parameter.
*/
static void bc_program_copyToVar(BcProgram *p, size_t idx, BcType t, bool last)
{
BcResult *ptr = NULL, r;
BcVec *vec;
BcNum *n = NULL;
bool var = (t == BC_TYPE_VAR);
#if DC_ENABLED
// Check the stack for dc.
if (BC_IS_DC) {
if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) bc_err(BC_ERR_EXEC_STACK);
}
#endif
assert(BC_PROG_STACK(&p->results, 1));
bc_program_operand(p, &ptr, &n, 0);
#if BC_ENABLED
// Get the variable for a bc function call.
if (BC_IS_BC)
{
// Type match the result.
bc_program_type_match(ptr, t);
// Get the variable or array, taking care to get the real item. We take
// care of last with arrays later.
if (!last && var)
n = bc_vec_item_rev(bc_program_vec(p, ptr->d.loc.loc, t), 1);
}
#endif // BC_ENABLED
vec = bc_program_vec(p, idx, t);
// We can shortcut in dc if it's assigning a string by using
// bc_program_assignStr().
if (ptr->t == BC_RESULT_STR) {
assert(BC_PROG_STR(n));
if (BC_ERR(!var)) bc_err(BC_ERR_EXEC_TYPE);
bc_program_assignStr(p, n, vec, true);
return;
}
BC_SIG_LOCK;
// Just create and copy for a normal variable.
if (var) {
if (BC_PROG_STR(n)) memcpy(&r.d.n, n, sizeof(BcNum));
else bc_num_createCopy(&r.d.n, n);
}
else {
// If we get here, we are handling an array. This is one place we need
// to cast the number from bc_program_num() to a vector.
BcVec *v = (BcVec*) n, *rv = &r.d.v;
#if BC_ENABLED
if (BC_IS_BC) {
BcVec *parent;
bool ref, ref_size;
// We need to figure out if the parameter is a reference or not and
// construct the reference vector, if necessary. So this gets the
// parent stack for the array.
parent = bc_program_vec(p, ptr->d.loc.loc, t);
assert(parent != NULL);
// This takes care of last for arrays. Mostly.
if (!last) v = bc_vec_item_rev(parent, !last);
assert(v != NULL);
// True if we are using a reference.
ref = (v->size == sizeof(BcNum) && t == BC_TYPE_REF);
// True if we already have a reference vector. This is slightly
// (okay, a lot; it just doesn't look that way) different from
// above. The above means that we need to construct a reference
// vector, whereas this means that we have one and we might have to
// *dereference* it.
ref_size = (v->size == sizeof(uchar));
// If we *should* have a reference.
if (ref || (ref_size && t == BC_TYPE_REF)) {
// Create a new reference vector.
bc_vec_init(rv, sizeof(uchar), BC_DTOR_NONE);
// If this is true, then we need to construct a reference.
if (ref) {
assert(parent->len >= (size_t) (!last + 1));
// Make sure the pointer was not invalidated.
vec = bc_program_vec(p, idx, t);
// Push the indices onto the reference vector. This takes
// care of last; it ensures the reference goes to the right
// place.
bc_vec_pushIndex(rv, ptr->d.loc.loc);
bc_vec_pushIndex(rv, parent->len - !last - 1);
}
// If we get here, we are copying a ref to a ref. Just push a
// copy of all of the bytes.
else bc_vec_npush(rv, v->len * sizeof(uchar), v->v);
// Push the reference vector onto the array stack and pop the
// source.
bc_vec_push(vec, &r.d);
bc_vec_pop(&p->results);
// We need to return early to avoid executing code that we must
// not touch.
BC_SIG_UNLOCK;
return;
}
// If we get here, we have a reference, but we need an array, so
// dereference the array.
else if (ref_size && t != BC_TYPE_REF)
v = bc_program_dereference(p, v);
}
#endif // BC_ENABLED
// If we get here, we need to copy the array because in bc, all
// arguments are passed by value. Yes, this is expensive.
bc_array_init(rv, true);
bc_array_copy(rv, v);
}
// Push the vector onto the array stack and pop the source.
bc_vec_push(vec, &r.d);
bc_vec_pop(&p->results);
BC_SIG_UNLOCK;
}
/**
* Executes an assignment operator.
* @param p The program.
* @param inst The assignment operator to execute.
*/
static void bc_program_assign(BcProgram *p, uchar inst) {
// The local use_val is true when the assigned value needs to be copied.
BcResult *left, *right, res;
BcNum *l, *r;
bool ob, sc, use_val = BC_INST_USE_VAL(inst);
bc_program_assignPrep(p, &left, &l, &right, &r);
// Assigning to a string should be impossible simply because of the parse.
assert(left->t != BC_RESULT_STR);
// If we are assigning a string...
if (right->t == BC_RESULT_STR) {
assert(BC_PROG_STR(r));
#if BC_ENABLED
if (inst != BC_INST_ASSIGN && inst != BC_INST_ASSIGN_NO_VAL)
bc_err(BC_ERR_EXEC_TYPE);
#endif // BC_ENABLED
// If we are assigning to an array element...
if (left->t == BC_RESULT_ARRAY_ELEM) {
BC_SIG_LOCK;
// We need to free the number and clear it.
bc_num_free(l);
memcpy(l, r, sizeof(BcNum));
// Now we can pop the results.
bc_vec_npop(&p->results, 2);
BC_SIG_UNLOCK;
}
else {
// If we get here, we are assigning to a variable, which we can use
// bc_program_assignStr() for.
BcVec *v = bc_program_vec(p, left->d.loc.loc, BC_TYPE_VAR);
bc_program_assignStr(p, r, v, false);
}
#if BC_ENABLED
// If this is true, the value is going to be used again, so we want to
// push a temporary with the string.
if (inst == BC_INST_ASSIGN) {
res.t = BC_RESULT_STR;
memcpy(&res.d.n, r, sizeof(BcNum));
bc_vec_push(&p->results, &res);
}
#endif // BC_ENABLED
// By using bc_program_assignStr(), we short-circuited this, so return.
return;
}
// If we have a normal assignment operator, not a math one...
if (BC_INST_IS_ASSIGN(inst)) {
// Assigning to a variable that has a string here is fine because there
// is no math done on it.
// BC_RESULT_TEMP, BC_RESULT_IBASE, BC_RESULT_OBASE, BC_RESULT_SCALE,
// and BC_RESULT_SEED all have temporary copies. Because that's the
// case, we can free the left and just move the value over. We set the
// type of right to BC_RESULT_ZERO in order to prevent it from being
// freed. We also don't have to worry about BC_RESULT_STR because it's
// take care of above.
if (right->t == BC_RESULT_TEMP || right->t >= BC_RESULT_IBASE) {
BC_SIG_LOCK;
bc_num_free(l);
memcpy(l, r, sizeof(BcNum));
right->t = BC_RESULT_ZERO;
BC_SIG_UNLOCK;
}
// Copy over.
else bc_num_copy(l, r);
}
#if BC_ENABLED
else {
// If we get here, we are doing a math assignment (+=, -=, etc.). So
// we need to prepare for a binary operator.
BcBigDig scale = BC_PROG_SCALE(p);
// At this point, the left side could still be a string because it could
// be a variable that has the string. If that's the case, we have a type
// error.
if (BC_PROG_STR(l)) bc_err(BC_ERR_EXEC_TYPE);
// Get the right type of assignment operator, whether val is used or
// NO_VAL for performance.
if (!use_val)
inst -= (BC_INST_ASSIGN_POWER_NO_VAL - BC_INST_ASSIGN_POWER);
assert(BC_NUM_RDX_VALID(l));
assert(BC_NUM_RDX_VALID(r));
// Run the actual operation. We do not need worry about reallocating l
// because bc_num_binary() does that behind the scenes for us.
bc_program_ops[inst - BC_INST_ASSIGN_POWER](l, r, l, scale);
}
#endif // BC_ENABLED
ob = (left->t == BC_RESULT_OBASE);
sc = (left->t == BC_RESULT_SCALE);
// The globals need special handling, especially the non-seed ones. The
// first part of the if statement handles them.
if (ob || sc || left->t == BC_RESULT_IBASE) {
BcVec *v;
BcBigDig *ptr, *ptr_t, val, max, min;
// Get the actual value.
val = bc_num_bigdig(l);
// Scale needs handling separate from ibase and obase.
if (sc) {
// Set the min and max.
min = 0;
max = vm.maxes[BC_PROG_GLOBALS_SCALE];
// Get a pointer to the stack and to the current value.
v = p->globals_v + BC_PROG_GLOBALS_SCALE;
ptr_t = p->globals + BC_PROG_GLOBALS_SCALE;
}
else {
// Set the min and max.
min = BC_NUM_MIN_BASE;
if (BC_ENABLE_EXTRA_MATH && ob && (BC_IS_DC || !BC_IS_POSIX))
min = 0;
max = vm.maxes[ob + BC_PROG_GLOBALS_IBASE];
// Get a pointer to the stack and to the current value.
v = p->globals_v + BC_PROG_GLOBALS_IBASE + ob;
ptr_t = p->globals + BC_PROG_GLOBALS_IBASE + ob;
}
// Check for error.
if (BC_ERR(val > max || val < min)) {
// This grabs the right error.
BcErr e = left->t - BC_RESULT_IBASE + BC_ERR_EXEC_IBASE;
bc_verr(e, min, max);
}
// Set the top of the stack and the actual global value.
ptr = bc_vec_top(v);
*ptr = val;
*ptr_t = val;
}
#if BC_ENABLE_EXTRA_MATH
// To assign to steed, let bc_num_rng() do its magic.
else if (left->t == BC_RESULT_SEED) bc_num_rng(l, &p->rng);
#endif // BC_ENABLE_EXTRA_MATH
BC_SIG_LOCK;
// If we needed to use the value, then we need to copy it. Otherwise, we can
// pop indiscriminately. Oh, and the copy should be a BC_RESULT_TEMP.
if (use_val) {
bc_num_createCopy(&res.d.n, l);
res.t = BC_RESULT_TEMP;
bc_vec_npop(&p->results, 2);
bc_vec_push(&p->results, &res);
}
else bc_vec_npop(&p->results, 2);
BC_SIG_UNLOCK;
}
/**
* Pushes a variable's value onto the results stack.
* @param p The program.
* @param code The bytecode vector to pull the variable's index out of.
* @param bgn An in/out parameter; the start of the index in the bytecode
* vector, and will be updated to point after the index on return.
* @param pop True if the variable's value should be popped off its stack.
* This is only used in dc.
* @param copy True if the variable's value should be copied to the results
* stack. This is only used in dc.
*/
static void bc_program_pushVar(BcProgram *p, const char *restrict code,
size_t *restrict bgn, bool pop, bool copy)
{
BcResult r;
size_t idx = bc_program_index(code, bgn);
// Set the result appropriately.
r.t = BC_RESULT_VAR;
r.d.loc.loc = idx;
#if DC_ENABLED
// If this condition is true, then we have the hard case, where we have to
// adjust dc registers.
if (BC_IS_DC && (pop || copy)) {
// Get the stack for the variable and the number at the top.
BcVec *v = bc_program_vec(p, idx, BC_TYPE_VAR);
BcNum *num = bc_vec_top(v);
// Ensure there are enough elements on the stack.
if (BC_ERR(!BC_PROG_STACK(v, 2 - copy))) {
const char *name = bc_map_name(&p->var_map, idx);
bc_verr(BC_ERR_EXEC_STACK_REGISTER, name);
}
assert(BC_PROG_STACK(v, 2 - copy));
// If the top of the stack is actually a number...
if (!BC_PROG_STR(num)) {
BC_SIG_LOCK;
// Create a copy to go onto the results stack as appropriate.
r.t = BC_RESULT_TEMP;
bc_num_createCopy(&r.d.n, num);
// If we are not actually copying, we need to do a replace, so pop.
if (!copy) bc_vec_pop(v);
bc_vec_push(&p->results, &r);
BC_SIG_UNLOCK;
return;
}
else {
// Set the string result. We can just memcpy because all of the
// fields in the num should be cleared.
memcpy(&r.d.n, num, sizeof(BcNum));
r.t = BC_RESULT_STR;
}
// If we are not actually copying, we need to do a replace, so pop.
if (!copy) bc_vec_pop(v);
}
#endif // DC_ENABLED
bc_vec_push(&p->results, &r);
}
/**
* Pushes an array or an array element onto the results stack.
* @param p The program.
* @param code The bytecode vector to pull the variable's index out of.
* @param bgn An in/out parameter; the start of the index in the bytecode
* vector, and will be updated to point after the index on return.
* @param inst The instruction; whether to push an array or an array element.
*/
static void bc_program_pushArray(BcProgram *p, const char *restrict code,
size_t *restrict bgn, uchar inst)
{
BcResult r, *operand;
BcNum *num;
BcBigDig temp;
// Get the index of the array.
r.d.loc.loc = bc_program_index(code, bgn);
// Doing an array is easy; just set the result type and finish.
if (inst == BC_INST_ARRAY) {
r.t = BC_RESULT_ARRAY;
bc_vec_push(&p->results, &r);
return;
}
// Grab the top element of the results stack for the array index.
bc_program_prep(p, &operand, &num, 0);
temp = bc_num_bigdig(num);
// Set the result.
r.t = BC_RESULT_ARRAY_ELEM;
r.d.loc.idx = (size_t) temp;
BC_SIG_LOCK;
// Pop the index and push the element.
bc_vec_pop(&p->results);
bc_vec_push(&p->results, &r);
BC_SIG_UNLOCK;
}
#if BC_ENABLED
/**
* Executes an increment or decrement operator. This only handles postfix
* inc/dec because the parser translates prefix inc/dec into an assignment where
* the value is used.
* @param p The program.
* @param inst The instruction; whether to do an increment or decrement.
*/
static void bc_program_incdec(BcProgram *p, uchar inst) {
BcResult *ptr, res, copy;
BcNum *num;
uchar inst2;
bc_program_prep(p, &ptr, &num, 0);
BC_SIG_LOCK;
// We need a copy from *before* the operation.
copy.t = BC_RESULT_TEMP;
bc_num_createCopy(&copy.d.n, num);
BC_SETJMP_LOCKED(exit);
BC_SIG_UNLOCK;
// Create the proper assignment.
res.t = BC_RESULT_ONE;
inst2 = BC_INST_ASSIGN_PLUS_NO_VAL + (inst & 0x01);
bc_vec_push(&p->results, &res);
bc_program_assign(p, inst2);
BC_SIG_LOCK;
bc_vec_push(&p->results, &copy);
BC_UNSETJMP;
BC_SIG_UNLOCK;
// No need to free the copy here because we pushed it onto the stack.
return;
exit:
BC_SIG_MAYLOCK;
bc_num_free(&copy.d.n);
BC_LONGJMP_CONT;
}
/**
* Executes a function call for bc.
* @param p The program.
* @param code The bytecode vector to pull the number of arguments and the
* function index out of.
* @param bgn An in/out parameter; the start of the indices in the bytecode
* vector, and will be updated to point after the indices on
* return.
*/
static void bc_program_call(BcProgram *p, const char *restrict code,
size_t *restrict bgn)
{
BcInstPtr ip;
size_t i, nargs;
BcFunc *f;
BcVec *v;
BcAuto *a;
BcResult *arg;
// Pull the number of arguments out of the bytecode vector.
nargs = bc_program_index(code, bgn);
// Set up instruction pointer.
ip.idx = 0;
ip.func = bc_program_index(code, bgn);
f = bc_vec_item(&p->fns, ip.func);
// Error checking.
if (BC_ERR(!f->code.len)) bc_verr(BC_ERR_EXEC_UNDEF_FUNC, f->name);
if (BC_ERR(nargs != f->nparams))
bc_verr(BC_ERR_EXEC_PARAMS, f->nparams, nargs);
// Set the length of the results stack. We discount the argument, of course.
ip.len = p->results.len - nargs;
assert(BC_PROG_STACK(&p->results, nargs));
// Prepare the globals' stacks.
if (BC_G) bc_program_prepGlobals(p);
// Push the arguments onto the stacks of their respective parameters.
for (i = 0; i < nargs; ++i) {
size_t j;
bool last = true;
arg = bc_vec_top(&p->results);
if (BC_ERR(arg->t == BC_RESULT_VOID)) bc_err(BC_ERR_EXEC_VOID_VAL);
// Get the corresponding parameter.
a = bc_vec_item(&f->autos, nargs - 1 - i);
// If I have already pushed to a var, I need to make sure I
// get the previous version, not the already pushed one. This condition
// must be true for that to even be possible.
if (arg->t == BC_RESULT_VAR || arg->t == BC_RESULT_ARRAY) {
// Loop through all of the previous parameters.
for (j = 0; j < i && last; ++j) {
BcAuto *aptr = bc_vec_item(&f->autos, nargs - 1 - j);
// This condition is true if there is a previous parameter with
// the same name *and* type because variables and arrays do not
// interfere with each other.
last = (arg->d.loc.loc != aptr->idx ||
(!aptr->type) != (arg->t == BC_RESULT_VAR));
}
}
// Actually push the value onto the parameter's stack.
bc_program_copyToVar(p, a->idx, a->type, last);
}
BC_SIG_LOCK;
// Push zeroes onto the stacks of the auto variables.
for (; i < f->autos.len; ++i) {
// Get the auto and its stack.
a = bc_vec_item(&f->autos, i);
v = bc_program_vec(p, a->idx, a->type);
// If a variable, just push a 0; otherwise, push an array.
if (a->type == BC_TYPE_VAR) {
BcNum *n = bc_vec_pushEmpty(v);
bc_num_init(n, BC_NUM_DEF_SIZE);
}
else {
BcVec *v2;
assert(a->type == BC_TYPE_ARRAY);
v2 = bc_vec_pushEmpty(v);
bc_array_init(v2, true);
}
}
// Push the instruction pointer onto the execution stack.
bc_vec_push(&p->stack, &ip);
BC_SIG_UNLOCK;
}
/**
* Executes a return instruction.
* @param p The program.
* @param inst The return instruction. bc can return void, and we need to know
* if it is.
*/
static void bc_program_return(BcProgram *p, uchar inst) {
BcResult *res;
BcFunc *f;
BcInstPtr *ip;
size_t i, nresults;
// Get the instruction pointer.
ip = bc_vec_top(&p->stack);
// Get the difference between the actual number of results and the number of
// results the caller expects.
nresults = p->results.len - ip->len;
// If this isn't true, there was a missing call somewhere.
assert(BC_PROG_STACK(&p->stack, 2));
// If this isn't true, the parser screwed by giving us no value when we
// expected one, or giving us a value when we expected none.
assert(BC_PROG_STACK(&p->results, ip->len + (inst == BC_INST_RET)));
// Get the function we are returning from.
f = bc_vec_item(&p->fns, ip->func);
res = bc_program_prepResult(p);
// If we are returning normally...
if (inst == BC_INST_RET) {
BcNum *num;
BcResult *operand;
// Prepare and copy the return value.
bc_program_operand(p, &operand, &num, 1);
if (BC_PROG_STR(num)) {
// We need to set this because otherwise, it will be a
// BC_RESULT_TEMP, and BC_RESULT_TEMP needs an actual number to make
// it easier to do type checking.
res->t = BC_RESULT_STR;
memcpy(&res->d.n, num, sizeof(BcNum));
}
else {
BC_SIG_LOCK;
bc_num_createCopy(&res->d.n, num);
}
}
// Void is easy; set the result.
else if (inst == BC_INST_RET_VOID) res->t = BC_RESULT_VOID;
else {
BC_SIG_LOCK;
// If we get here, the instruction is for returning a zero, so do that.
bc_num_init(&res->d.n, BC_NUM_DEF_SIZE);
}
BC_SIG_MAYUNLOCK;
// We need to pop items off of the stacks of arguments and autos as well.
for (i = 0; i < f->autos.len; ++i) {
BcAuto *a = bc_vec_item(&f->autos, i);
BcVec *v = bc_program_vec(p, a->idx, a->type);
bc_vec_pop(v);
}
// When we retire, pop all of the unused results.
bc_program_retire(p, 1, nresults);
// Pop the globals, if necessary.
if (BC_G) bc_program_popGlobals(p, false);
// Pop the stack. This is what causes the function to actually "return."
bc_vec_pop(&p->stack);
}
#endif // BC_ENABLED
/**
* Executes a builtin function.
* @param p The program.
* @param inst The builtin to execute.
*/
static void bc_program_builtin(BcProgram *p, uchar inst) {
BcResult *opd, *res;
BcNum *num;
bool len = (inst == BC_INST_LENGTH);
// Ensure we have a valid builtin.
#if BC_ENABLE_EXTRA_MATH
assert(inst >= BC_INST_LENGTH && inst <= BC_INST_IRAND);
#else // BC_ENABLE_EXTRA_MATH
assert(inst >= BC_INST_LENGTH && inst <= BC_INST_ABS);
#endif // BC_ENABLE_EXTRA_MATH
#ifndef BC_PROG_NO_STACK_CHECK
// Check stack for dc.
if (BC_IS_DC && BC_ERR(!BC_PROG_STACK(&p->results, 1)))
bc_err(BC_ERR_EXEC_STACK);
#endif // BC_PROG_NO_STACK_CHECK
assert(BC_PROG_STACK(&p->results, 1));
res = bc_program_prepResult(p);
bc_program_operand(p, &opd, &num, 1);
assert(num != NULL);
// We need to ensure that strings and arrays aren't passed to most builtins.
// The scale function can take strings in dc.
if (!len && (inst != BC_INST_SCALE_FUNC || BC_IS_BC))
bc_program_type_num(opd, num);
// Square root is easy.
if (inst == BC_INST_SQRT) bc_num_sqrt(num, &res->d.n, BC_PROG_SCALE(p));
// Absolute value is easy.
else if (inst == BC_INST_ABS) {
BC_SIG_LOCK;
bc_num_createCopy(&res->d.n, num);
BC_SIG_UNLOCK;
BC_NUM_NEG_CLR_NP(res->d.n);
}
#if BC_ENABLE_EXTRA_MATH
// irand() is easy.
else if (inst == BC_INST_IRAND) {
BC_SIG_LOCK;
bc_num_init(&res->d.n, num->len - BC_NUM_RDX_VAL(num));
BC_SIG_UNLOCK;
bc_num_irand(num, &res->d.n, &p->rng);
}
#endif // BC_ENABLE_EXTRA_MATH
// Everything else is...not easy.
else {
BcBigDig val = 0;
// Well, scale() is easy, but length() is not.
if (len) {
// If we are bc and we have an array...
if (opd->t == BC_RESULT_ARRAY) {
// Yes, this is one place where we need to cast the number from
// bc_program_num() to a vector.
BcVec *v = (BcVec*) num;
#if BC_ENABLED
// Dereference the array, if necessary.
if (BC_IS_BC && v->size == sizeof(uchar))
v = bc_program_dereference(p, v);
#endif // BC_ENABLED
assert(v->size == sizeof(BcNum));
val = (BcBigDig) v->len;
}
else {
// If the item is a string...
if (!BC_PROG_NUM(opd, num)) {
char *str;
// Get the string, then get the length.
str = bc_program_string(p, num);
val = (BcBigDig) strlen(str);
}
else
{
// Calculate the length of the number.
val = (BcBigDig) bc_num_len(num);
}
}
}
// Like I said; scale() is actually easy. It just also needs the integer
// conversion that length() does.
else if (BC_IS_BC || BC_PROG_NUM(opd, num))
val = (BcBigDig) bc_num_scale(num);
BC_SIG_LOCK;
// Create the result.
bc_num_createFromBigdig(&res->d.n, val);
BC_SIG_UNLOCK;
}
bc_program_retire(p, 1, 1);
}
/**
* Executes a divmod.
* @param p The program.
*/
static void bc_program_divmod(BcProgram *p) {
BcResult *opd1, *opd2, *res, *res2;
BcNum *n1, *n2;
size_t req;
// We grow first to avoid pointer invalidation.
bc_vec_grow(&p->results, 2);
// We don't need to update the pointer because
// the capacity is enough due to the line above.
res2 = bc_program_prepResult(p);
res = bc_program_prepResult(p);
// Prepare the operands.
bc_program_binOpPrep(p, &opd1, &n1, &opd2, &n2, 2);
req = bc_num_mulReq(n1, n2, BC_PROG_SCALE(p));
BC_SIG_LOCK;
// Initialize the results.
bc_num_init(&res->d.n, req);
bc_num_init(&res2->d.n, req);
BC_SIG_UNLOCK;
// Execute.
bc_num_divmod(n1, n2, &res2->d.n, &res->d.n, BC_PROG_SCALE(p));
bc_program_retire(p, 2, 2);
}
/**
* Executes modular exponentiation.
* @param p The program.
*/
static void bc_program_modexp(BcProgram *p) {
BcResult *r1, *r2, *r3, *res;
BcNum *n1, *n2, *n3;
#if DC_ENABLED
// Check the stack.
if (BC_IS_DC && BC_ERR(!BC_PROG_STACK(&p->results, 3)))
bc_err(BC_ERR_EXEC_STACK);
#endif // DC_ENABLED
assert(BC_PROG_STACK(&p->results, 3));
res = bc_program_prepResult(p);
// Get the first operand and typecheck.
bc_program_operand(p, &r1, &n1, 3);
bc_program_type_num(r1, n1);
// Get the last two operands.
bc_program_binOpPrep(p, &r2, &n2, &r3, &n3, 1);
// Make sure that the values have their pointers updated, if necessary.
// Only array elements are possible because this is dc.
if (r1->t == BC_RESULT_ARRAY_ELEM && (r1->t == r2->t || r1->t == r3->t))
n1 = bc_program_num(p, r1);
BC_SIG_LOCK;
bc_num_init(&res->d.n, n3->len);
BC_SIG_UNLOCK;
bc_num_modexp(n1, n2, n3, &res->d.n);
bc_program_retire(p, 1, 3);
}
/**
* Asciifies a number for dc. This is a helper for bc_program_asciify().
* @param p The program.
* @param n The number to asciify.
*/
static uchar bc_program_asciifyNum(BcProgram *p, BcNum *n) {
BcNum num;
BcBigDig val;
#ifndef NDEBUG
// This is entirely to satisfy a useless scan-build error.
val = 0;
#endif // NDEBUG
bc_num_clear(&num);
BC_SETJMP(num_err);
BC_SIG_LOCK;
bc_num_createCopy(&num, n);
BC_SIG_UNLOCK;
// We want to clear the scale and sign for easy mod later.
bc_num_truncate(&num, num.scale);
BC_NUM_NEG_CLR_NP(num);
// This is guaranteed to not have a divide by 0
// because strmb is equal to 256.
bc_num_mod(&num, &p->strmb, &num, 0);
// This is also guaranteed to not error because num is in the range
// [0, UCHAR_MAX], which is definitely in range for a BcBigDig. And
// it is not negative.
val = bc_num_bigdig2(&num);
num_err:
BC_SIG_MAYLOCK;
bc_num_free(&num);
BC_LONGJMP_CONT;
return (uchar) val;
}
/**
* Executes the "asciify" command in dc.
* @param p The program.
* @param fidx The index of the current function.
*/
static void bc_program_asciify(BcProgram *p, size_t fidx) {
BcResult *r, res;
BcNum *n;
char str[2], *str2;
uchar c;
size_t idx;
// Check the stack.
if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) bc_err(BC_ERR_EXEC_STACK);
assert(BC_PROG_STACK(&p->results, 1));
// Get the top of the results stack.
bc_program_operand(p, &r, &n, 0);
assert(n != NULL);
// Asciify.
if (BC_PROG_NUM(r, n)) c = bc_program_asciifyNum(p, n);
else {
// Get the string itself, then the first character.
str2 = bc_program_string(p, n);
c = (uchar) str2[0];
}
// Fill the resulting string.
str[0] = (char) c;
str[1] = '\0';
// Add the string to the data structures.
BC_SIG_LOCK;
idx = bc_program_addString(p, str, fidx);
BC_SIG_UNLOCK;
// Set the result
res.t = BC_RESULT_STR;
bc_num_clear(&res.d.n);
res.d.n.rdx = fidx;
res.d.n.scale = idx;
// Pop and push.
bc_vec_pop(&p->results);
bc_vec_push(&p->results, &res);
}
/**
* Streams a number or a string to stdout.
* @param p The program.
*/
static void bc_program_printStream(BcProgram *p) {
BcResult *r;
BcNum *n;
// Check the stack.
if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) bc_err(BC_ERR_EXEC_STACK);
assert(BC_PROG_STACK(&p->results, 1));
// Get the top of the results stack.
bc_program_operand(p, &r, &n, 0);
assert(n != NULL);
// Stream appropriately.
if (BC_PROG_NUM(r, n)) bc_num_stream(n);
else bc_program_printChars(bc_program_string(p, n));
// Pop the operand.
bc_vec_pop(&p->results);
}
#if DC_ENABLED
/**
* Gets the length of a register in dc and pushes it onto the results stack.
* @param p The program.
* @param code The bytecode vector to pull the register's index out of.
* @param bgn An in/out parameter; the start of the index in the bytecode
* vector, and will be updated to point after the index on return.
*/
static void bc_program_regStackLen(BcProgram *p, const char *restrict code,
size_t *restrict bgn)
{
size_t idx = bc_program_index(code, bgn);
BcVec *v = bc_program_vec(p, idx, BC_TYPE_VAR);
bc_program_pushBigdig(p, (BcBigDig) v->len, BC_RESULT_TEMP);
}
/**
* Pushes the length of the results stack onto the results stack.
* @param p The program.
*/
static void bc_program_stackLen(BcProgram *p) {
bc_program_pushBigdig(p, (BcBigDig) p->results.len, BC_RESULT_TEMP);
}
/**
* Pops a certain number of elements off the execution stack.
* @param p The program.
* @param inst The instruction to tell us how many. There is one to pop up to
* 2, and one to pop the amount equal to the number at the top of
* the results stack.
*/
static void bc_program_nquit(BcProgram *p, uchar inst) {
BcResult *opnd;
BcNum *num;
BcBigDig val;
size_t i;
// Ensure that the tail calls stack is correct.
assert(p->stack.len == p->tail_calls.len);
// Get the number of executions to pop.
if (inst == BC_INST_QUIT) val = 2;
else {
bc_program_prep(p, &opnd, &num, 0);
val = bc_num_bigdig(num);
bc_vec_pop(&p->results);
}
// Loop over the tail call stack and adjust the quit value appropriately.
for (i = 0; val && i < p->tail_calls.len; ++i) {
// Get the number of tail calls for this one.
size_t calls = *((size_t*) bc_vec_item_rev(&p->tail_calls, i)) + 1;
// Adjust the value.
if (calls >= val) val = 0;
else val -= (BcBigDig) calls;
}
// If we don't have enough executions, just quit.
if (i == p->stack.len) {
vm.status = BC_STATUS_QUIT;
BC_JMP;
}
else {
// We can always pop the last item we reached on the tail call stack
// because these are for tail calls. That means that any executions that
// we would not have quit in that position on the stack would have quit
// anyway.
bc_vec_npop(&p->stack, i);
bc_vec_npop(&p->tail_calls, i);
}
}
/**
* Pushes the depth of the execution stack onto the stack.
* @param p The program.
*/
static void bc_program_execStackLen(BcProgram *p) {
size_t i, amt, len = p->tail_calls.len;
amt = len;
for (i = 0; i < len; ++i)
amt += *((size_t*) bc_vec_item(&p->tail_calls, i));
bc_program_pushBigdig(p, (BcBigDig) amt, BC_RESULT_TEMP);
}
/**
*
* @param p The program.
* @param code The bytecode vector to pull the register's index out of.
* @param bgn An in/out parameter; the start of the index in the bytecode
* vector, and will be updated to point after the index on return.
* @param cond True if the execution is conditional, false otherwise.
* @param len The number of bytes in the bytecode vector.
*/
static void bc_program_execStr(BcProgram *p, const char *restrict code,
size_t *restrict bgn, bool cond, size_t len)
{
BcResult *r;
char *str;
BcFunc *f;
BcInstPtr ip;
size_t fidx;
BcNum *n;
assert(p->stack.len == p->tail_calls.len);
// Check the stack.
if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) bc_err(BC_ERR_EXEC_STACK);
assert(BC_PROG_STACK(&p->results, 1));
// Get the operand.
bc_program_operand(p, &r, &n, 0);
// If execution is conditional...
if (cond) {
bool exec;
size_t idx, then_idx, else_idx;
// Get the index of the "then" var and "else" var.
then_idx = bc_program_index(code, bgn);
else_idx = bc_program_index(code, bgn);
// Figure out if we should execute.
exec = (r->d.n.len != 0);
idx = exec ? then_idx : else_idx;
BC_SIG_LOCK;
BC_SETJMP_LOCKED(exit);
// If we are supposed to execute, execute. If else_idx == SIZE_MAX, that
// means there was no else clause, so if execute is false and else does
// not exist, we don't execute. The goto skips all of the setup for the
// execution.
if (exec || (else_idx != SIZE_MAX))
n = bc_vec_top(bc_program_vec(p, idx, BC_TYPE_VAR));
else goto exit;
if (BC_ERR(!BC_PROG_STR(n))) bc_err(BC_ERR_EXEC_TYPE);
BC_UNSETJMP;
BC_SIG_UNLOCK;
}
else {
// In non-conditional situations, only the top of stack can be executed,
// and in those cases, variables are not allowed to be "on the stack";
// they are only put on the stack to be assigned to.
assert(r->t != BC_RESULT_VAR);
if (r->t != BC_RESULT_STR) return;
}
assert(BC_PROG_STR(n));
// Get the string.
str = bc_program_string(p, n);
// Get the function index and function.
BC_SIG_LOCK;
fidx = bc_program_insertFunc(p, str);
BC_SIG_UNLOCK;
f = bc_vec_item(&p->fns, fidx);
// If the function has not been parsed yet...
if (!f->code.len) {
BC_SIG_LOCK;
if (!BC_PARSE_IS_INITED(&vm.read_prs, p)) {
bc_parse_init(&vm.read_prs, p, fidx);
// Initialize this too because bc_vm_shutdown() expects them to be
// initialized togther.
bc_vec_init(&vm.read_buf, sizeof(char), BC_DTOR_NONE);
}
// This needs to be updated because the parser could have been used
// somewhere else
else bc_parse_updateFunc(&vm.read_prs, fidx);
bc_lex_file(&vm.read_prs.l, vm.file);
BC_SETJMP_LOCKED(err);
BC_SIG_UNLOCK;
// Parse.
bc_parse_text(&vm.read_prs, str, false);
vm.expr(&vm.read_prs, BC_PARSE_NOCALL);
BC_SIG_LOCK;
BC_UNSETJMP;
// We can just assert this here because
// dc should parse everything until EOF.
assert(vm.read_prs.l.t == BC_LEX_EOF);
BC_SIG_UNLOCK;
}
// Set the instruction pointer.
ip.idx = 0;
ip.len = p->results.len;
ip.func = fidx;
// Pop the operand.
bc_vec_pop(&p->results);
// Tail call processing. This condition means that there is more on the
// execution stack, and we are at the end of the bytecode vector, and the
// last instruction is just a BC_INST_POP_EXEC, which would return.
if (p->stack.len > 1 && *bgn == len - 1 && code[*bgn] == BC_INST_POP_EXEC) {
size_t *call_ptr = bc_vec_top(&p->tail_calls);
// Add one to the tail call.
*call_ptr += 1;
// Pop the execution stack before pushing the new instruction pointer
// on.
bc_vec_pop(&p->stack);
}
// If not a tail call, just push a new one.
else bc_vec_push(&p->tail_calls, &ip.idx);
// Push the new function onto the execution stack and return.
bc_vec_push(&p->stack, &ip);
return;
err:
BC_SIG_MAYLOCK;
f = bc_vec_item(&p->fns, fidx);
// Make sure to erase the bytecode vector so dc knows it is not parsed.
bc_vec_popAll(&f->code);
exit:
bc_vec_pop(&p->results);
BC_LONGJMP_CONT;
}
/**
* Prints every item on the results stack, one per line.
* @param p The program.
*/
static void bc_program_printStack(BcProgram *p) {
size_t idx;
for (idx = 0; idx < p->results.len; ++idx)
bc_program_print(p, BC_INST_PRINT, idx);
}
#endif // DC_ENABLED
/**
* Pushes the value of a global onto the results stack.
* @param p The program.
* @param inst Which global to push, as an instruction.
*/
static void bc_program_pushGlobal(BcProgram *p, uchar inst) {
BcResultType t;
// Make sure the instruction is valid.
assert(inst >= BC_INST_IBASE && inst <= BC_INST_SCALE);
// Push the global.
t = inst - BC_INST_IBASE + BC_RESULT_IBASE;
bc_program_pushBigdig(p, p->globals[inst - BC_INST_IBASE], t);
}
#if BC_ENABLE_EXTRA_MATH
/**
* Pushes the value of seed on the stack.
* @param p The program.
*/
static void bc_program_pushSeed(BcProgram *p) {
BcResult *res;
res = bc_program_prepResult(p);
res->t = BC_RESULT_SEED;
BC_SIG_LOCK;
// We need 2*BC_RAND_NUM_SIZE because of the size of the state.
bc_num_init(&res->d.n, 2 * BC_RAND_NUM_SIZE);
BC_SIG_UNLOCK;
bc_num_createFromRNG(&res->d.n, &p->rng);
}
#endif // BC_ENABLE_EXTRA_MATH
/**
* Adds a function to the fns array. The function's ID must have already been
* inserted into the map.
* @param p The program.
* @param id_ptr The ID of the function as inserted into the map.
*/
static void bc_program_addFunc(BcProgram *p, BcId *id_ptr) {
BcInstPtr *ip;
BcFunc *f;
BC_SIG_ASSERT_LOCKED;
// Push and init.
f = bc_vec_pushEmpty(&p->fns);
bc_func_init(f, id_ptr->name);
// This is to make sure pointers are updated if the array was moved.
if (p->stack.len) {
ip = bc_vec_top(&p->stack);
bc_program_setVecs(p, (BcFunc*) bc_vec_item(&p->fns, ip->func));
}
}
size_t bc_program_insertFunc(BcProgram *p, const char *name) {
BcId *id_ptr;
bool new;
size_t idx;
BC_SIG_ASSERT_LOCKED;
assert(p != NULL && name != NULL);
// Insert into the map and get the resulting ID.
new = bc_map_insert(&p->fn_map, name, p->fns.len, &idx);
id_ptr = (BcId*) bc_vec_item(&p->fn_map, idx);
idx = id_ptr->idx;
// If the function is new...
if (new) {
// Add the function to the fns array.
bc_program_addFunc(p, id_ptr);
}
#if BC_ENABLED
// bc has to reset the function because it's about to be redefined.
else if (BC_IS_BC) {
BcFunc *func = bc_vec_item(&p->fns, idx);
bc_func_reset(func);
}
#endif // BC_ENABLED
return idx;
}
#ifndef NDEBUG
void bc_program_free(BcProgram *p) {
size_t i;
BC_SIG_ASSERT_LOCKED;
assert(p != NULL);
// Free the globals stacks.
for (i = 0; i < BC_PROG_GLOBALS_LEN; ++i) bc_vec_free(p->globals_v + i);
bc_vec_free(&p->fns);
bc_vec_free(&p->fn_map);
bc_vec_free(&p->vars);
bc_vec_free(&p->var_map);
bc_vec_free(&p->arrs);
bc_vec_free(&p->arr_map);
bc_vec_free(&p->results);
bc_vec_free(&p->stack);
#if BC_ENABLED
if (BC_IS_BC) bc_num_free(&p->last);
#endif // BC_ENABLED
#if BC_ENABLE_EXTRA_MATH
bc_rand_free(&p->rng);
#endif // BC_ENABLE_EXTRA_MATH
#if DC_ENABLED
if (BC_IS_DC) bc_vec_free(&p->tail_calls);
#endif // DC_ENABLED
}
#endif // NDEBUG
void bc_program_init(BcProgram *p) {
BcInstPtr ip;
size_t i;
BC_SIG_ASSERT_LOCKED;
assert(p != NULL);
// We want this clear.
memset(&ip, 0, sizeof(BcInstPtr));
// Setup the globals stacks and the current values.
for (i = 0; i < BC_PROG_GLOBALS_LEN; ++i) {
BcBigDig val = i == BC_PROG_GLOBALS_SCALE ? 0 : BC_BASE;
bc_vec_init(p->globals_v + i, sizeof(BcBigDig), BC_DTOR_NONE);
bc_vec_push(p->globals_v + i, &val);
p->globals[i] = val;
}
#if DC_ENABLED
// dc-only setup.
if (BC_IS_DC) {
bc_vec_init(&p->tail_calls, sizeof(size_t), BC_DTOR_NONE);
// We want an item for the main function on the tail call stack.
i = 0;
bc_vec_push(&p->tail_calls, &i);
}
#endif // DC_ENABLED
bc_num_setup(&p->strmb, p->strmb_num, BC_NUM_BIGDIG_LOG10);
bc_num_bigdig2num(&p->strmb, BC_NUM_STREAM_BASE);
#if BC_ENABLE_EXTRA_MATH
// We need to initialize srand() just in case /dev/urandom and /dev/random
// are not available.
srand((unsigned int) time(NULL));
bc_rand_init(&p->rng);
#endif // BC_ENABLE_EXTRA_MATH
#if BC_ENABLED
if (BC_IS_BC) bc_num_init(&p->last, BC_NUM_DEF_SIZE);
#endif // BC_ENABLED
#ifndef NDEBUG
bc_vec_init(&p->fns, sizeof(BcFunc), BC_DTOR_FUNC);
#else // NDEBUG
bc_vec_init(&p->fns, sizeof(BcFunc), BC_DTOR_NONE);
#endif // NDEBUG
bc_map_init(&p->fn_map);
bc_program_insertFunc(p, bc_func_main);
bc_program_insertFunc(p, bc_func_read);
bc_vec_init(&p->vars, sizeof(BcVec), BC_DTOR_VEC);
bc_map_init(&p->var_map);
bc_vec_init(&p->arrs, sizeof(BcVec), BC_DTOR_VEC);
bc_map_init(&p->arr_map);
bc_vec_init(&p->results, sizeof(BcResult), BC_DTOR_RESULT);
// Push the first instruction pointer onto the execution stack.
bc_vec_init(&p->stack, sizeof(BcInstPtr), BC_DTOR_NONE);
bc_vec_push(&p->stack, &ip);
// Make sure the pointers are properly set up.
bc_program_setVecs(p, (BcFunc*) bc_vec_item(&p->fns, BC_PROG_MAIN));
assert(p->consts != NULL && p->strs != NULL);
}
void bc_program_reset(BcProgram *p) {
BcFunc *f;
BcInstPtr *ip;
BC_SIG_ASSERT_LOCKED;
// Pop all but the last execution and all results.
bc_vec_npop(&p->stack, p->stack.len - 1);
bc_vec_popAll(&p->results);
#if BC_ENABLED
// Clear the globals' stacks.
if (BC_G) bc_program_popGlobals(p, true);
#endif // BC_ENABLED
// Clear the bytecode vector of the main function.
f = bc_vec_item(&p->fns, BC_PROG_MAIN);
bc_vec_npop(&f->code, f->code.len);
// Reset the instruction pointer.
ip = bc_vec_top(&p->stack);
bc_program_setVecs(p, f);
memset(ip, 0, sizeof(BcInstPtr));
// Write the ready message for a signal, and clear the signal.
if (vm.sig) {
bc_file_write(&vm.fout, bc_flush_none, bc_program_ready_msg,
bc_program_ready_msg_len);
bc_file_flush(&vm.fout, bc_flush_err);
vm.sig = 0;
}
}
void bc_program_exec(BcProgram *p) {
size_t idx;
BcResult r, *ptr;
BcInstPtr *ip;
BcFunc *func;
char *code;
bool cond = false;
uchar inst;
#if BC_ENABLED
BcNum *num;
#endif // BC_ENABLED
#if !BC_HAS_COMPUTED_GOTO
#ifndef NDEBUG
size_t jmp_bufs_len;
#endif // NDEBUG
#endif // !BC_HAS_COMPUTED_GOTO
#if BC_HAS_COMPUTED_GOTO
BC_PROG_LBLS;
BC_PROG_LBLS_ASSERT;
// BC_INST_INVALID is a marker for the end so that we don't have to have an
// execution loop.
func = (BcFunc*) bc_vec_item(&p->fns, BC_PROG_MAIN);
bc_vec_pushByte(&func->code, BC_INST_INVALID);
#endif // BC_HAS_COMPUTED_GOTO
ip = bc_vec_top(&p->stack);
func = (BcFunc*) bc_vec_item(&p->fns, ip->func);
code = func->code.v;
// Ensure the pointers are correct.
bc_program_setVecs(p, func);
#if !BC_HAS_COMPUTED_GOTO
#ifndef NDEBUG
jmp_bufs_len = vm.jmp_bufs.len;
#endif // NDEBUG
// This loop is the heart of the execution engine. It *is* the engine. For
// computed goto, it is ignored.
while (ip->idx < func->code.len)
#endif // !BC_HAS_COMPUTED_GOTO
{
BC_SIG_ASSERT_NOT_LOCKED;
#if BC_HAS_COMPUTED_GOTO
BC_PROG_JUMP(inst, code, ip);
#else // BC_HAS_COMPUTED_GOTO
// Get the next instruction and increment the index.
inst = (uchar) code[(ip->idx)++];
#endif // BC_HAS_COMPUTED_GOTO
#if BC_DEBUG_CODE
bc_file_printf(&vm.ferr, "inst: %s\n", bc_inst_names[inst]);
bc_file_flush(&vm.ferr, bc_flush_none);
#endif // BC_DEBUG_CODE
#if !BC_HAS_COMPUTED_GOTO
switch (inst)
#endif // !BC_HAS_COMPUTED_GOTO
{
#if BC_ENABLED
// This just sets up the condition for the unconditional jump below,
// which checks the condition, if necessary.
BC_PROG_LBL(BC_INST_JUMP_ZERO):
{
bc_program_prep(p, &ptr, &num, 0);
cond = !bc_num_cmpZero(num);
bc_vec_pop(&p->results);
BC_PROG_DIRECT_JUMP(BC_INST_JUMP)
}
// Fallthrough.
BC_PROG_FALLTHROUGH
BC_PROG_LBL(BC_INST_JUMP):
{
idx = bc_program_index(code, &ip->idx);
// If a jump is required...
if (inst == BC_INST_JUMP || cond) {
// Get the address to jump to.
size_t *addr = bc_vec_item(&func->labels, idx);
// If this fails, then the parser failed to set up the
// labels correctly.
assert(*addr != SIZE_MAX);
// Set the new address.
ip->idx = *addr;
}
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_CALL):
{
assert(BC_IS_BC);
bc_program_call(p, code, &ip->idx);
// Because we changed the execution stack and where we are
// executing, we have to update all of this.
ip = bc_vec_top(&p->stack);
func = bc_vec_item(&p->fns, ip->func);
code = func->code.v;
bc_program_setVecs(p, func);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_INC):
BC_PROG_LBL(BC_INST_DEC):
{
bc_program_incdec(p, inst);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_HALT):
{
vm.status = BC_STATUS_QUIT;
// Just jump out. The jump series will take care of everything.
BC_JMP;
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_RET):
BC_PROG_LBL(BC_INST_RET0):
BC_PROG_LBL(BC_INST_RET_VOID):
{
bc_program_return(p, inst);
// Because we changed the execution stack and where we are
// executing, we have to update all of this.
ip = bc_vec_top(&p->stack);
func = bc_vec_item(&p->fns, ip->func);
code = func->code.v;
bc_program_setVecs(p, func);
BC_PROG_JUMP(inst, code, ip);
}
#endif // BC_ENABLED
BC_PROG_LBL(BC_INST_BOOL_OR):
BC_PROG_LBL(BC_INST_BOOL_AND):
BC_PROG_LBL(BC_INST_REL_EQ):
BC_PROG_LBL(BC_INST_REL_LE):
BC_PROG_LBL(BC_INST_REL_GE):
BC_PROG_LBL(BC_INST_REL_NE):
BC_PROG_LBL(BC_INST_REL_LT):
BC_PROG_LBL(BC_INST_REL_GT):
{
bc_program_logical(p, inst);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_READ):
{
// We want to flush output before
// this in case there is a prompt.
bc_file_flush(&vm.fout, bc_flush_save);
bc_program_read(p);
// Because we changed the execution stack and where we are
// executing, we have to update all of this.
ip = bc_vec_top(&p->stack);
func = bc_vec_item(&p->fns, ip->func);
code = func->code.v;
bc_program_setVecs(p, func);
BC_PROG_JUMP(inst, code, ip);
}
#if BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_RAND):
{
bc_program_rand(p);
BC_PROG_JUMP(inst, code, ip);
}
#endif // BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_MAXIBASE):
BC_PROG_LBL(BC_INST_MAXOBASE):
BC_PROG_LBL(BC_INST_MAXSCALE):
#if BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_MAXRAND):
#endif // BC_ENABLE_EXTRA_MATH
{
BcBigDig dig = vm.maxes[inst - BC_INST_MAXIBASE];
bc_program_pushBigdig(p, dig, BC_RESULT_TEMP);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_VAR):
{
bc_program_pushVar(p, code, &ip->idx, false, false);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_ARRAY_ELEM):
BC_PROG_LBL(BC_INST_ARRAY):
{
bc_program_pushArray(p, code, &ip->idx, inst);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_IBASE):
BC_PROG_LBL(BC_INST_SCALE):
BC_PROG_LBL(BC_INST_OBASE):
{
bc_program_pushGlobal(p, inst);
BC_PROG_JUMP(inst, code, ip);
}
#if BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_SEED):
{
bc_program_pushSeed(p);
BC_PROG_JUMP(inst, code, ip);
}
#endif // BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_LENGTH):
BC_PROG_LBL(BC_INST_SCALE_FUNC):
BC_PROG_LBL(BC_INST_SQRT):
BC_PROG_LBL(BC_INST_ABS):
#if BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_IRAND):
#endif // BC_ENABLE_EXTRA_MATH
{
bc_program_builtin(p, inst);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_ASCIIFY):
{
bc_program_asciify(p, ip->func);
// Because we changed the execution stack and where we are
// executing, we have to update all of this.
ip = bc_vec_top(&p->stack);
func = bc_vec_item(&p->fns, ip->func);
code = func->code.v;
bc_program_setVecs(p, func);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_NUM):
{
bc_program_const(p, code, &ip->idx);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_ZERO):
BC_PROG_LBL(BC_INST_ONE):
#if BC_ENABLED
BC_PROG_LBL(BC_INST_LAST):
#endif // BC_ENABLED
{
r.t = BC_RESULT_ZERO + (inst - BC_INST_ZERO);
bc_vec_push(&p->results, &r);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_PRINT):
BC_PROG_LBL(BC_INST_PRINT_POP):
#if BC_ENABLED
BC_PROG_LBL(BC_INST_PRINT_STR):
#endif // BC_ENABLED
{
bc_program_print(p, inst, 0);
// We want to flush right away to save the output for history,
// if history must preserve it when taking input.
bc_file_flush(&vm.fout, bc_flush_save);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_STR):
{
// Set up the result and push.
r.t = BC_RESULT_STR;
bc_num_clear(&r.d.n);
r.d.n.rdx = bc_program_index(code, &ip->idx);
r.d.n.scale = bc_program_index(code, &ip->idx);
bc_vec_push(&p->results, &r);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_POWER):
BC_PROG_LBL(BC_INST_MULTIPLY):
BC_PROG_LBL(BC_INST_DIVIDE):
BC_PROG_LBL(BC_INST_MODULUS):
BC_PROG_LBL(BC_INST_PLUS):
BC_PROG_LBL(BC_INST_MINUS):
#if BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_PLACES):
BC_PROG_LBL(BC_INST_LSHIFT):
BC_PROG_LBL(BC_INST_RSHIFT):
#endif // BC_ENABLE_EXTRA_MATH
{
bc_program_op(p, inst);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_NEG):
BC_PROG_LBL(BC_INST_BOOL_NOT):
#if BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_TRUNC):
#endif // BC_ENABLE_EXTRA_MATH
{
bc_program_unary(p, inst);
BC_PROG_JUMP(inst, code, ip);
}
#if BC_ENABLED
BC_PROG_LBL(BC_INST_ASSIGN_POWER):
BC_PROG_LBL(BC_INST_ASSIGN_MULTIPLY):
BC_PROG_LBL(BC_INST_ASSIGN_DIVIDE):
BC_PROG_LBL(BC_INST_ASSIGN_MODULUS):
BC_PROG_LBL(BC_INST_ASSIGN_PLUS):
BC_PROG_LBL(BC_INST_ASSIGN_MINUS):
#if BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_ASSIGN_PLACES):
BC_PROG_LBL(BC_INST_ASSIGN_LSHIFT):
BC_PROG_LBL(BC_INST_ASSIGN_RSHIFT):
#endif // BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_ASSIGN):
BC_PROG_LBL(BC_INST_ASSIGN_POWER_NO_VAL):
BC_PROG_LBL(BC_INST_ASSIGN_MULTIPLY_NO_VAL):
BC_PROG_LBL(BC_INST_ASSIGN_DIVIDE_NO_VAL):
BC_PROG_LBL(BC_INST_ASSIGN_MODULUS_NO_VAL):
BC_PROG_LBL(BC_INST_ASSIGN_PLUS_NO_VAL):
BC_PROG_LBL(BC_INST_ASSIGN_MINUS_NO_VAL):
#if BC_ENABLE_EXTRA_MATH
BC_PROG_LBL(BC_INST_ASSIGN_PLACES_NO_VAL):
BC_PROG_LBL(BC_INST_ASSIGN_LSHIFT_NO_VAL):
BC_PROG_LBL(BC_INST_ASSIGN_RSHIFT_NO_VAL):
#endif // BC_ENABLE_EXTRA_MATH
#endif // BC_ENABLED
BC_PROG_LBL(BC_INST_ASSIGN_NO_VAL):
{
bc_program_assign(p, inst);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_POP):
{
#ifndef BC_PROG_NO_STACK_CHECK
// dc must do a stack check, but bc does not.
if (BC_IS_DC) {
if (BC_ERR(!BC_PROG_STACK(&p->results, 1)))
bc_err(BC_ERR_EXEC_STACK);
}
#endif // BC_PROG_NO_STACK_CHECK
assert(BC_PROG_STACK(&p->results, 1));
bc_vec_pop(&p->results);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_SWAP):
{
BcResult *ptr2;
// Check the stack.
if (BC_ERR(!BC_PROG_STACK(&p->results, 2)))
bc_err(BC_ERR_EXEC_STACK);
assert(BC_PROG_STACK(&p->results, 2));
// Get the two items.
ptr = bc_vec_item_rev(&p->results, 0);
ptr2 = bc_vec_item_rev(&p->results, 1);
// Swap. It's just easiest to do it this way.
memcpy(&r, ptr, sizeof(BcResult));
memcpy(ptr, ptr2, sizeof(BcResult));
memcpy(ptr2, &r, sizeof(BcResult));
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_MODEXP):
{
bc_program_modexp(p);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_DIVMOD):
{
bc_program_divmod(p);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_PRINT_STREAM):
{
bc_program_printStream(p);
BC_PROG_JUMP(inst, code, ip);
}
#if DC_ENABLED
BC_PROG_LBL(BC_INST_POP_EXEC):
{
// If this fails, the dc parser got something wrong.
assert(BC_PROG_STACK(&p->stack, 2));
// Pop the execution stack and tail call stack.
bc_vec_pop(&p->stack);
bc_vec_pop(&p->tail_calls);
// Because we changed the execution stack and where we are
// executing, we have to update all of this.
ip = bc_vec_top(&p->stack);
func = bc_vec_item(&p->fns, ip->func);
code = func->code.v;
bc_program_setVecs(p, func);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_EXECUTE):
BC_PROG_LBL(BC_INST_EXEC_COND):
{
cond = (inst == BC_INST_EXEC_COND);
bc_program_execStr(p, code, &ip->idx, cond, func->code.len);
// Because we changed the execution stack and where we are
// executing, we have to update all of this.
ip = bc_vec_top(&p->stack);
func = bc_vec_item(&p->fns, ip->func);
code = func->code.v;
bc_program_setVecs(p, func);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_PRINT_STACK):
{
bc_program_printStack(p);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_CLEAR_STACK):
{
bc_vec_popAll(&p->results);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_REG_STACK_LEN):
{
bc_program_regStackLen(p, code, &ip->idx);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_STACK_LEN):
{
bc_program_stackLen(p);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_DUPLICATE):
{
// Check the stack.
if (BC_ERR(!BC_PROG_STACK(&p->results, 1)))
bc_err(BC_ERR_EXEC_STACK);
assert(BC_PROG_STACK(&p->results, 1));
// Get the top of the stack.
ptr = bc_vec_top(&p->results);
BC_SIG_LOCK;
// Copy and push.
bc_result_copy(&r, ptr);
bc_vec_push(&p->results, &r);
BC_SIG_UNLOCK;
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_LOAD):
BC_PROG_LBL(BC_INST_PUSH_VAR):
{
bool copy = (inst == BC_INST_LOAD);
bc_program_pushVar(p, code, &ip->idx, true, copy);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_PUSH_TO_VAR):
{
idx = bc_program_index(code, &ip->idx);
bc_program_copyToVar(p, idx, BC_TYPE_VAR, true);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_QUIT):
BC_PROG_LBL(BC_INST_NQUIT):
{
bc_program_nquit(p, inst);
// Because we changed the execution stack and where we are
// executing, we have to update all of this.
ip = bc_vec_top(&p->stack);
func = bc_vec_item(&p->fns, ip->func);
code = func->code.v;
bc_program_setVecs(p, func);
BC_PROG_JUMP(inst, code, ip);
}
BC_PROG_LBL(BC_INST_EXEC_STACK_LEN):
{
bc_program_execStackLen(p);
BC_PROG_JUMP(inst, code, ip);
}
#endif // DC_ENABLED
#if BC_HAS_COMPUTED_GOTO
BC_PROG_LBL(BC_INST_INVALID):
{
return;
}
#else // BC_HAS_COMPUTED_GOTO
default:
{
BC_UNREACHABLE
#ifndef NDEBUG
abort();
#endif // NDEBUG
}
#endif // BC_HAS_COMPUTED_GOTO
}
#if !BC_HAS_COMPUTED_GOTO
#ifndef NDEBUG
// This is to allow me to use a debugger to see the last instruction,
// which will point to which function was the problem. But it's also a
// good smoke test for error handling changes.
assert(jmp_bufs_len == vm.jmp_bufs.len);
#endif // NDEBUG
#endif // !BC_HAS_COMPUTED_GOTO
}
}
#if BC_DEBUG_CODE
#if BC_ENABLED && DC_ENABLED
void bc_program_printStackDebug(BcProgram *p) {
bc_file_puts(&vm.fout, bc_flush_err, "-------------- Stack ----------\n");
bc_program_printStack(p);
bc_file_puts(&vm.fout, bc_flush_err, "-------------- Stack End ------\n");
}
static void bc_program_printIndex(const char *restrict code,
size_t *restrict bgn)
{
uchar byte, i, bytes = (uchar) code[(*bgn)++];
ulong val = 0;
for (byte = 1, i = 0; byte && i < bytes; ++i) {
byte = (uchar) code[(*bgn)++];
if (byte) val |= ((ulong) byte) << (CHAR_BIT * i);
}
bc_vm_printf(" (%lu) ", val);
}
static void bc_program_printStr(const BcProgram *p, const char *restrict code,
size_t *restrict bgn)
{
size_t idx = bc_program_index(code, bgn);
char *s;
s = *((char**) bc_vec_item(p->strs, idx));
bc_vm_printf(" (\"%s\") ", s);
}
void bc_program_printInst(const BcProgram *p, const char *restrict code,
size_t *restrict bgn)
{
uchar inst = (uchar) code[(*bgn)++];
bc_vm_printf("Inst[%zu]: %s [%lu]; ", *bgn - 1,
bc_inst_names[inst], (unsigned long) inst);
if (inst == BC_INST_VAR || inst == BC_INST_ARRAY_ELEM ||
inst == BC_INST_ARRAY)
{
bc_program_printIndex(code, bgn);
}
else if (inst == BC_INST_STR) bc_program_printStr(p, code, bgn);
else if (inst == BC_INST_NUM) {
size_t idx = bc_program_index(code, bgn);
BcConst *c = bc_vec_item(p->consts, idx);
bc_vm_printf("(%s)", c->val);
}
else if (inst == BC_INST_CALL ||
(inst > BC_INST_STR && inst <= BC_INST_JUMP_ZERO))
{
bc_program_printIndex(code, bgn);
if (inst == BC_INST_CALL) bc_program_printIndex(code, bgn);
}
bc_vm_putchar('\n', bc_flush_err);
}
void bc_program_code(const BcProgram* p) {
BcFunc *f;
char *code;
BcInstPtr ip;
size_t i;
for (i = 0; i < p->fns.len; ++i) {
ip.idx = ip.len = 0;
ip.func = i;
f = bc_vec_item(&p->fns, ip.func);
code = f->code.v;
bc_vm_printf("func[%zu]:\n", ip.func);
while (ip.idx < f->code.len) bc_program_printInst(p, code, &ip.idx);
bc_file_puts(&vm.fout, bc_flush_err, "\n\n");
}
}
#endif // BC_ENABLED && DC_ENABLED
#endif // BC_DEBUG_CODE