cfad47cfa3/t3compiler/tads3/tct3.cpp
Commiter: Nikos Chantziaras
Author: Nikos Chantziaras
Revision: cfad47cfa3
File Size: 282 KB
(June 01, 2009 20:54 UTC) Almost 3 years ago
Initial commit.
Showing without highlighting since it looks like a big file and may slow your browser - show with highlighting
Show/hide line numbers#ifdef RCSID
static char RCSid[] =
"$Header: d:/cvsroot/tads/tads3/tct3.cpp,v 1.5 1999/07/11 00:46:58 MJRoberts Exp $";
#endif
/*
* Copyright (c) 1999, 2002 Michael J. Roberts. All Rights Reserved.
*
* Please see the accompanying license file, LICENSE.TXT, for information
* on using and copying this software.
*/
/*
Name
tct3.cpp - TADS 3 Compiler - T3 VM Code Generator
Function
Generate code for the T3 VM
Notes
Modified
05/08/99 MJRoberts - Creation
*/
#include <stdio.h>
#include <assert.h>
#include "t3std.h"
#include "os.h"
#include "tcprs.h"
#include "tct3.h"
#include "tcgen.h"
#include "vmtype.h"
#include "vmwrtimg.h"
#include "vmfile.h"
#include "tcmain.h"
#include "tcerr.h"
#include "vmbignum.h"
#include "vmrunsym.h"
#include "tct3unas.h"
/* ------------------------------------------------------------------------ */
/*
* T3 Target Code Generator class
*/
/*
* initialize the code generator
*/
CTcGenTarg::CTcGenTarg()
{
int i;
/*
* we haven't written any instructions yet - fill the pipe with
* no-op instructions so that we don't think we can combine the
* first instruction with anything previous
*/
last_op_ = OPC_NOP;
second_last_op_ = OPC_NOP;
/*
* we haven't seen any strings or lists yet - set the initial
* maximum lengths to zero
*/
max_str_len_ = 0;
max_list_cnt_ = 0;
/* we haven't generated any code yet */
max_bytecode_len_ = 0;
/* there are no metaclasses defined yet */
meta_head_ = meta_tail_ = 0;
meta_cnt_ = 0;
/* no function sets defined yet */
fnset_head_ = fnset_tail_ = 0;
fnset_cnt_ = 0;
/*
* Add the built-in metaclass entries. The order of these entries
* is fixed to match the TCT3_METAID_xxx constants - if this order
* changes, those constants must change to match.
*/
add_meta("tads-object");
add_meta("list");
add_meta("dictionary2/030000");
add_meta("grammar-production/030000");
add_meta("vector");
add_meta("anon-func-ptr");
add_meta("int-class-mod/030000");
/*
* Set up the initial translation table for translating from our
* internal metaclass index values - TCT3_METAID_xxx - to the *actual*
* image file dependency index. When we're compiling a program, these
* are simply in our own internal order - index N in our scheme is
* simply index N in the actual image file - because we get to lay out
* exactly what the dependency table looks like. However, we need the
* translation table for when we're being used as part of the debugger
* - in those cases, we don't get to dictate the dependency table
* layout, because the image file (and thus the dependency table) might
* have been generated by a different version of the compiler. In
* those cases, the image file loader will have to set up the
* translation information for us.
*/
for (i = 0 ; i <= TCT3_METAID_LAST ; ++i)
predef_meta_idx_[i] = i;
/* start at the first valid property ID */
next_prop_ = 1;
/* start at the first valid object ID */
next_obj_ = 1;
/* allocate an initial sort buffer */
sort_buf_size_ = 4096;
sort_buf_ = (char *)t3malloc(sort_buf_size_);
/* not in a constructor */
in_constructor_ = FALSE;
/* no debug line record pointers yet */
debug_line_cnt_ = 0;
debug_line_head_ = debug_line_tail_ = 0;
/* normal (non-debug) evaluation mode */
eval_for_debug_ = FALSE;
speculative_ = FALSE;
debug_stack_level_ = 0;
/* no multi-method initializer object yet */
mminit_obj_ = VM_INVALID_OBJ;
}
/*
* delete the code generator
*/
CTcGenTarg::~CTcGenTarg()
{
/* delete all of the metaclass list entries */
while (meta_head_ != 0)
{
tc_meta_entry *nxt;
/* remember the next item */
nxt = meta_head_->nxt;
/* delete this item */
t3free(meta_head_);
/* move on */
meta_head_ = nxt;
}
/* delete all of the function set list entries */
while (fnset_head_ != 0)
{
tc_fnset_entry *nxt;
/* remember the next item */
nxt = fnset_head_->nxt;
/* delete this item */
t3free(fnset_head_);
/* move on */
fnset_head_ = nxt;
}
/* delete our sort buffer */
t3free(sort_buf_);
/* delete the debug line record pointers */
while (debug_line_head_ != 0)
{
tct3_debug_line_page *nxt;
/* remember the next one */
nxt = debug_line_head_->nxt;
/* delete this one */
delete debug_line_head_;
/* move on */
debug_line_head_ = nxt;
}
}
/* start loading the image file metaclass dependency table */
void CTcGenTarg::start_image_file_meta_table()
{
int i;
/*
* clear out all of the entries - set them to -1 to indicate that
* they're invalid
*/
for (i = 0 ; i <= TCT3_METAID_LAST ; ++i)
predef_meta_idx_[i] = -1;
}
/*
* Load an image file metaclass dependency table. When we're being used in
* a debugger, the image loader must call this with the run-time dependency
* table to establish the translation from our internal metaclass
* identifiers (TCT3_METAID_xxx) to the actual run-time index values.
*/
void CTcGenTarg::load_image_file_meta_table(
const char *nm, size_t len, int idx)
{
/*
* Check the name against our known names. If it matches one of the
* known types, store the actual index in our translation table under
* our internal index entry. If it's not a known name, ignore it - we
* only care about the names we actually pre-define, because those are
* the only ones we need to generate our own code for.
*/
if (len == 11 && memcmp(nm, "tads-object", 11) == 0)
predef_meta_idx_[TCT3_METAID_TADSOBJ] = idx;
else if (len == 4 && memcmp(nm, "list", 4) == 0)
predef_meta_idx_[TCT3_METAID_LIST] = idx;
else if (len == 11 && memcmp(nm, "dictionary2", 11) == 0)
predef_meta_idx_[TCT3_METAID_DICT] = idx;
else if (len == 18 && memcmp(nm, "grammar-production", 18) == 0)
predef_meta_idx_[TCT3_METAID_GRAMPROD] = idx;
else if (len == 6 && memcmp(nm, "vector", 6) == 0)
predef_meta_idx_[TCT3_METAID_VECTOR] = idx;
else if (len == 13 && memcmp(nm, "anon-func-ptr", 13) == 0)
predef_meta_idx_[TCT3_METAID_ANONFN] = idx;
else if (len == 13 && memcmp(nm, "int-class-mod", 13) == 0)
predef_meta_idx_[TCT3_METAID_ICMOD] = idx;
}
/*
* End the image file metaclass dependency table.
*/
void CTcGenTarg::end_image_file_meta_table()
{
int i;
/*
* Scan the metaclass translation table and make sure they've all been
* set. If any are unset, it means that these metaclasses aren't
* available; since we depend upon these metaclasses, this means that
* we can't generate code interactively, so we can't act as a debugger.
*/
for (i = 0 ; i <= TCT3_METAID_LAST ; ++i)
{
/* if this entry hasn't been set properly, abort */
if (predef_meta_idx_[i] == -1)
err_throw(VMERR_IMAGE_INCOMPAT_VSN_DBG);
}
}
/*
* Add an entry to the metaclass dependency table
*/
int CTcGenTarg::add_meta(const char *nm, size_t len,
CTcSymMetaclass *sym)
{
tc_meta_entry *ent;
size_t extra_len;
const char *extra_ptr;
const char *p;
size_t rem;
/*
* if the name string doesn't contain a slash, allocate enough space
* to add an implied version suffix of "/000000"
*/
for (p = nm, rem = len ; rem != 0 && *p != '/' ; ++p, --rem) ;
if (rem == 0)
{
/* we didn't find a version suffix - add space for one */
extra_len = 7;
extra_ptr = "/000000";
}
else
{
/*
* there's already a version suffix - but make sure we have
* space for a six-character string
*/
if (rem < 7)
{
/* add zeroes to pad out to a six-place version string */
extra_len = 7 - rem;
extra_ptr = "000000";
}
else
{
/* we need nothing extra */
extra_len = 0;
extra_ptr = 0;
}
}
/* allocate a new entry for the item */
ent = (tc_meta_entry *)t3malloc(sizeof(tc_meta_entry) + len + extra_len);
if (ent == 0)
err_throw(TCERR_CODEGEN_NO_MEM);
/* copy the name into the entry */
memcpy(ent->nm, nm, len);
/* add any extra version suffix information */
if (extra_len != 0)
memcpy(ent->nm + len, extra_ptr, extra_len);
/* null-terminate the name string in the entry */
ent->nm[len + extra_len] = '\0';
/* remember the symbol */
ent->sym = sym;
/* link the entry in at the end of the list */
ent->nxt = 0;
if (meta_tail_ != 0)
meta_tail_->nxt = ent;
else
meta_head_ = ent;
meta_tail_ = ent;
/* count the entry, returning the index of the entry in the list */
return meta_cnt_++;
}
/*
* Find a metaclass index given the global identifier.
*/
tc_meta_entry *CTcGenTarg::find_meta_entry(const char *nm, size_t len,
int update_vsn, int *entry_idx)
{
tc_meta_entry *ent;
int idx;
size_t name_len;
const char *p;
const char *vsn;
size_t vsn_len;
size_t rem;
/* find the version suffix, if any */
for (rem = len, p = nm ; rem != 0 && *p != '/' ; --rem, ++p) ;
/* note the length of the name portion (up to the '/') */
name_len = len - rem;
/* note the version string, if there is one */
if (rem != 0)
{
vsn = p + 1;
vsn_len = rem - 1;
}
else
{
vsn = 0;
vsn_len = 0;
}
/* search the existing entries */
for (idx = 0, ent = meta_head_ ; ent != 0 ; ent = ent->nxt, ++idx)
{
size_t ent_name_len;
char *ent_vsn;
/* find the version suffix in the entry */
for (ent_vsn = ent->nm ; *ent_vsn != '\0' && *ent_vsn != '/' ;
++ent_vsn) ;
/* the name is the part up to the '/' */
ent_name_len = ent_vsn - ent->nm;
/* note the length of the name and the version suffix */
if (*ent_vsn == '/')
{
/* the version is what follows the '/' */
++ent_vsn;
}
else
{
/* there is no version suffix */
ent_vsn = 0;
}
/* if this is the one, return it */
if (ent_name_len == name_len && memcmp(ent->nm, nm, name_len) == 0)
{
/*
* if this version number is higher than the version number
* we previously recorded, remember the new, higher version
* number
*/
if (update_vsn && ent_vsn != 0 && strlen(ent_vsn) == vsn_len
&& memcmp(vsn, ent_vsn, vsn_len) > 0)
{
/* store the new version string */
memcpy(ent_vsn, vsn, vsn_len);
}
/* tell the caller the index, and return the entry */
*entry_idx = idx;
return ent;
}
}
/* we didn't find it */
return 0;
}
/*
* Find a metaclass symbol given the global identifier
*/
class CTcSymMetaclass *CTcGenTarg::find_meta_sym(const char *nm, size_t len)
{
tc_meta_entry *ent;
int idx;
/* find the entry */
ent = find_meta_entry(nm, len, TRUE, &idx);
/*
* if we found it, return the associated metaclass symbol; if
* there's no entry, there's no symbol
*/
if (ent != 0)
return ent->sym;
else
return 0;
}
/*
* Find or add a metaclass entry
*/
int CTcGenTarg::find_or_add_meta(const char *nm, size_t len,
CTcSymMetaclass *sym)
{
tc_meta_entry *ent;
int idx;
/* find the entry */
ent = find_meta_entry(nm, len, TRUE, &idx);
/* if we found it, return the index */
if (ent != 0)
{
/*
* found it - if it didn't already have a symbol mapping, use
* the new symbol; if there is a symbol in the table entry
* already, however, do not change it
*/
if (ent->sym == 0)
ent->sym = sym;
/* return the index */
return idx;
}
/* we didn't find an existing entry - add a new one */
return add_meta(nm, len, sym);
}
/*
* Get the symbol for a given metaclass dependency table entry
*/
CTcSymMetaclass *CTcGenTarg::get_meta_sym(int meta_idx)
{
tc_meta_entry *ent;
/* find the list entry at the given index */
for (ent = meta_head_ ; ent != 0 && meta_idx != 0 ;
ent = ent->nxt, --meta_idx) ;
/* if we didn't find the entry, do nothing */
if (ent == 0 || meta_idx != 0)
return 0;
/* return this entry's symbol */
return ent->sym;
}
/*
* Get the external name for the given metaclass index
*/
const char *CTcGenTarg::get_meta_name(int meta_idx) const
{
tc_meta_entry *ent;
/* find the list entry at the given index */
for (ent = meta_head_ ; ent != 0 && meta_idx != 0 ;
ent = ent->nxt, --meta_idx) ;
/* if we didn't find the entry, do nothing */
if (ent == 0 || meta_idx != 0)
return 0;
/* return this entry's external name */
return ent->nm;
}
/*
* Set the symbol for a given metaclass dependency table entry
*/
void CTcGenTarg::set_meta_sym(int meta_idx, class CTcSymMetaclass *sym)
{
tc_meta_entry *ent;
/* find the list entry at the given index */
for (ent = meta_head_ ; ent != 0 && meta_idx != 0 ;
ent = ent->nxt, --meta_idx) ;
/* if we didn't find the entry, do nothing */
if (ent == 0 || meta_idx != 0)
return;
/* set this entry's symbol */
ent->sym = sym;
}
/*
* Add an entry to the function set dependency table
*/
int CTcGenTarg::add_fnset(const char *nm, size_t len)
{
tc_fnset_entry *ent;
const char *sl;
size_t sl_len;
size_t idx;
/* find the version part of the new name, if present */
for (sl = nm, sl_len = len ; sl_len != 0 && *sl != '/' ; ++sl, --sl_len) ;
/* look for an existing entry with the same name prefix */
for (idx = 0, ent = fnset_head_ ; ent != 0 ; ent = ent->nxt, ++idx)
{
char *ent_sl;
/* find the version part of this entry's name, if present */
for (ent_sl = ent->nm ; *ent_sl != '\0' && *ent_sl != '/' ;
++ent_sl) ;
/* check to see if the prefixes match */
if (ent_sl - ent->nm == sl - nm
&& memcmp(ent->nm, nm, sl - nm) == 0)
{
/*
* This one matches. Keep the one with the higher version
* number. If one has a version number and the other doesn't,
* keep the one with the version number.
*/
if (*ent_sl == '/' && sl_len != 0)
{
/*
* Both have version numbers - keep the higher version.
* Limit the version length to 6 characters plus the
* slash.
*/
if (sl_len > 7)
sl_len = 7;
/* check if the new version number is higher */
if (memcmp(sl, ent_sl, sl_len) > 0)
{
/* the new one is higher - copy it over the old one */
memcpy(ent_sl, sl, sl_len);
}
/*
* in any case, we're going to keep the existing entry, so
* we're done - just return the existing entry's index
*/
return idx;
}
else if (*ent_sl == '/')
{
/*
* only the old entry has a version number, so keep it and
* ignore the new definition - this means we're done, so
* just return the existing item's index
*/
return idx;
}
else
{
/*
* Only the new entry has a version number, so store the
* new version number. To do this, simply copy the new
* entry over the old entry, but limit the version number
* field to 7 characters including the slash.
*/
if (sl_len > 7)
len -= (sl_len - 7);
/* copy the new value */
memcpy(ent->nm, nm, len);
/* done - return the existing item's index */
return idx;
}
}
}
/*
* Allocate a new entry for the item. Always allocate space for a
* version number, even if the entry doesn't have a version number -
* if the part from the slash on is 7 characters or more, add nothing,
* else add enough to pad it out to seven characters.
*/
ent = (tc_fnset_entry *)t3malloc(sizeof(tc_fnset_entry) + len
+ (sl_len < 7 ? 7 - sl_len : 0));
if (ent == 0)
err_throw(TCERR_CODEGEN_NO_MEM);
/* copy the name into the entry */
memcpy(ent->nm, nm, len);
ent->nm[len] = '\0';
/* link the entry in at the end of the list */
ent->nxt = 0;
if (fnset_tail_ != 0)
fnset_tail_->nxt = ent;
else
fnset_head_ = ent;
fnset_tail_ = ent;
/* count the entry, returning the index of the entry in the list */
return fnset_cnt_++;
}
/*
* get a function set's name given its index
*/
const char *CTcGenTarg::get_fnset_name(int idx) const
{
tc_fnset_entry *ent;
/* scan the linked list to find the given index */
for (ent = fnset_head_ ; idx != 0 && ent != 0 ; ent = ent->nxt, --idx) ;
/* return the one we found */
return ent->nm;
}
/*
* Determine if we can skip an opcode because it is unreachable from the
* previous instruction.
*/
int CTcGenTarg::can_skip_op()
{
/*
* if the previous instruction was a return or throw of some kind,
* we can skip any subsequent opcodes until a label is defined
*/
switch(last_op_)
{
case OPC_RET:
case OPC_RETVAL:
case OPC_RETTRUE:
case OPC_RETNIL:
case OPC_THROW:
case OPC_JMP:
case OPC_LRET:
/* it's a return, throw, or jump - this new op is unreachable */
return TRUE;
default:
/* this new op is reachable */
return FALSE;
}
}
/*
* Remove the last JMP instruction
*/
void CTcGenTarg::remove_last_jmp()
{
/* a JMP instruction is three bytes long, so back up three bytes */
G_cs->dec_ofs(3);
}
/*
* Add a line record
*/
void CTcGenTarg::add_line_rec(CTcTokFileDesc *file, long linenum)
{
/* include line records only in debug mode */
if (G_debug)
{
/*
* clear the peephole, to ensure that the line boundary isn't
* blurred by code optimization
*/
clear_peephole();
/* add the record to the code stream */
G_cs->add_line_rec(file, linenum);
}
}
/*
* Write an opcode to the output stream. We'll watch for certain
* combinations of opcodes being generated, and apply peephole
* optimization when we see sequences that can be collapsed to more
* efficient single instructions.
*/
void CTcGenTarg::write_op(uchar opc)
{
int prv_len;
int op_len;
/* write the new opcode byte to the output stream */
G_cs->write((char)opc);
/* we've only written one byte so far for the current instruction */
op_len = 1;
/* presume the previous instruction length is just one byte */
prv_len = 1;
/*
* check for pairs of instructions that we can reduce to more
* efficient single instructions
*/
try_combine:
switch(opc)
{
case OPC_JF:
/*
* if the last instruction was a comparison, we can use the
* opposite compare-and-jump instruction
*/
switch(last_op_)
{
case OPC_NOT:
/* invert the sense of the test */
opc = OPC_JT;
goto combine;
combine:
/*
* delete the new opcode we wrote, since we're going to combine
* it with the preceding opcode
*/
G_cs->dec_ofs(op_len);
/* overwrite the preceding opcode with the new combined opcode */
G_cs->write_at(G_cs->get_ofs() - prv_len, opc);
/* roll back our internal peephole */
last_op_ = second_last_op_;
second_last_op_ = OPC_NOP;
/*
* we've deleted our own opcode, so the current (most recent)
* instruction in the output stream has the length of the
* current opcode
*/
op_len = prv_len;
/* presume the previous opcode is one byte again */
prv_len = 1;
/*
* go back for another try, since we may be able to do a
* three-way combination (for example, GT/NOT/JT would
* change to GT/JF, which would in turn change to JLE)
*/
goto try_combine;
case OPC_EQ:
opc = OPC_JNE;
goto combine;
case OPC_NE:
opc = OPC_JE;
goto combine;
case OPC_LT:
opc = OPC_JGE;
goto combine;
case OPC_LE:
opc = OPC_JGT;
goto combine;
case OPC_GT:
opc = OPC_JLE;
goto combine;
case OPC_GE:
opc = OPC_JLT;
goto combine;
case OPC_GETR0:
opc = OPC_JR0F;
goto combine;
}
break;
case OPC_JE:
/*
* if we just pushed nil, convert the PUSHNIL + JE to JNIL, since
* we simply want to jump if a value is nil
*/
if (last_op_ == OPC_PUSHNIL)
{
/* convert it to a jump-if-nil */
opc = OPC_JNIL;
goto combine;
}
break;
case OPC_JNE:
/* if we just pushed nil, convert to JNOTNIL */
if (last_op_ == OPC_PUSHNIL)
{
/* convert to jump-if-not-nil */
opc = OPC_JNOTNIL;
goto combine;
}
break;
case OPC_JT:
/*
* if the last instruction was a comparison, we can use a
* compare-and-jump instruction
*/
switch(last_op_)
{
case OPC_NOT:
/* invert the sense of the test */
opc = OPC_JF;
goto combine;
case OPC_EQ:
opc = OPC_JE;
goto combine;
case OPC_NE:
opc = OPC_JNE;
goto combine;
case OPC_LT:
opc = OPC_JLT;
goto combine;
case OPC_LE:
opc = OPC_JLE;
goto combine;
case OPC_GT:
opc = OPC_JGT;
goto combine;
case OPC_GE:
opc = OPC_JGE;
goto combine;
case OPC_GETR0:
opc = OPC_JR0T;
goto combine;
}
break;
case OPC_NOT:
/*
* If the previous instruction was a comparison test of some
* kind, we can invert the sense of the test. If the previous
* instruction was a BOOLIZE op, we can eliminate it entirely,
* because the NOT will perform the same conversion before
* negating the value. If the previous was a NOT, we're
* inverting an inversion; we can simply perform a single
* BOOLIZE to get the same effect.
*/
switch(last_op_)
{
case OPC_EQ:
opc = OPC_NE;
goto combine;
case OPC_NE:
opc = OPC_EQ;
goto combine;
case OPC_GT:
opc = OPC_LE;
goto combine;
case OPC_GE:
opc = OPC_LT;
goto combine;
case OPC_LT:
opc = OPC_GE;
goto combine;
case OPC_LE:
opc = OPC_GT;
goto combine;
case OPC_BOOLIZE:
opc = OPC_NOT;
goto combine;
case OPC_NOT:
opc = OPC_BOOLIZE;
goto combine;
}
break;
case OPC_RET:
/*
* If we're writing a return instruction immediately after
* another return instruction, we can skip the additional
* instruction, since it will never be reached. This case
* typically arises only when we generate the catch-all RET
* instruction at the end of a function.
*/
switch(last_op_)
{
case OPC_RET:
case OPC_RETVAL:
case OPC_RETNIL:
case OPC_RETTRUE:
/* simply suppress this additional RET instruction */
return;
}
break;
case OPC_RETNIL:
/* we don't need to write two RETNIL's in a row */
if (last_op_ == OPC_RETNIL)
return;
break;
case OPC_RETTRUE:
/* we don't need to write two RETTRUE's in a row */
if (last_op_ == OPC_RETTRUE)
return;
break;
case OPC_RETVAL:
/* check the last opcode */
switch(last_op_)
{
case OPC_GETR0:
/*
* if we just pushed R0 onto the stack, we can compress the
* GETR0 + RETVAL sequence into a simple RET, since RET leaves
* the R0 value unchanged
*/
opc = OPC_RET;
goto combine;
case OPC_PUSHNIL:
/* PUSHNIL + RET can be converted to RETNIL */
opc = OPC_RETNIL;
goto combine;
case OPC_PUSHTRUE:
/* PUSHTRUE + RET can be converted to RETTRUE */
opc = OPC_RETTRUE;
goto combine;
}
break;
case OPC_SETLCL1:
/* we can combine this with a preceding GETR0 */
if (last_op_ == OPC_GETR0)
{
/* generate a combined SETLCL1R0 */
opc = OPC_SETLCL1R0;
goto combine;
}
break;
case OPC_GETPROP:
/* check the previous instruction for combination possibilities */
switch(last_op_)
{
case OPC_GETLCL1:
/* get property of one-byte-addressable local */
opc = OPC_GETPROPLCL1;
/* overwrite the preceding two-byte instruction */
prv_len = 2;
goto combine;
case OPC_GETR0:
/* get property of R0 */
opc = OPC_GETPROPR0;
goto combine;
}
break;
case OPC_CALLPROP:
/* check the previous instruction */
switch(last_op_)
{
case OPC_GETR0:
/* call property of R0 */
opc = OPC_CALLPROPR0;
goto combine;
}
break;
case OPC_INDEX:
/* we can combine small integer constants with INDEX */
switch(last_op_)
{
case OPC_PUSH_0:
case OPC_PUSH_1:
/*
* We can combine these into IDXINT8, but we must write an
* extra byte for the index value. Go back and plug in the
* extra index value byte, and add another byte at the end of
* the stream to compensate for the insertion. (We're just
* going to remove and overwrite everything after the inserted
* byte, so don't bother actually fixing up that part with real
* data; we merely need to make sure we have the right number
* of bytes in the stream.)
*/
G_cs->write_at(G_cs->get_ofs() - 1,
last_op_ == OPC_PUSH_0 ? 0 : 1);
G_cs->write(0);
/* combine the instructions */
opc = OPC_IDXINT8;
prv_len = 2;
goto combine;
case OPC_PUSHINT8:
/* combine the PUSHINT8 + INDEX into IDXINT8 */
opc = OPC_IDXINT8;
prv_len = 2;
goto combine;
}
break;
case OPC_IDXINT8:
/* we can replace GETLCL1 + IDXINT8 with IDXLCL1INT8 */
if (last_op_ == OPC_GETLCL1)
{
uchar idx;
/* rewrite the GETLCL1 to add the index operand */
idx = G_cs->get_byte_at(G_cs->get_ofs() - 1);
G_cs->write_at(G_cs->get_ofs() - 2, idx);
/* add another byte to compensate for the insertion */
G_cs->write(0);
/* go back and combine into what's now a three-byte opcode */
opc = OPC_IDXLCL1INT8;
prv_len = 3;
goto combine;
}
break;
case OPC_SETIND:
/* we can replace SETLCL1 + <small int> + SETIND with SETINDLCL1I8 */
if (second_last_op_ == OPC_SETLCL1)
{
uchar idx;
/* check the middle opcode */
switch(last_op_)
{
case OPC_PUSHINT8:
/*
* go back and put the index value in the right spot in the
* third instruction back
*/
idx = G_cs->get_byte_at(G_cs->get_ofs() - 2);
G_cs->write_at(G_cs->get_ofs() - 3, idx);
/*
* Go back and combine into what's now a 3-byte
* instruction: we'll remove the SETIND and the
* PUSHINT8+val, for three bytes removed, but we're adding
* one byte, so we have a net current opcode length (to
* remove) of two bytes. Since we're combining three
* instructions into one, we're losing our second-to-last
* opcode.
*/
opc = OPC_SETINDLCL1I8;
second_last_op_ = OPC_NOP;
op_len = 2;
prv_len = 3;
goto combine;
case OPC_PUSH_0:
idx = 0;
goto combine_setind;
case OPC_PUSH_1:
idx = 1;
combine_setind:
/* go back and add the index value */
G_cs->write_at(G_cs->get_ofs() - 2, idx);
/*
* go back and combine the instructions - we're removing a
* net of one byte, since we're removing two one-byte
* instructions and extending the old 2-byte instruction
* into a 3-byte instruction
*/
opc = OPC_SETINDLCL1I8;
second_last_op_ = OPC_NOP;
op_len = 1;
prv_len = 3;
goto combine;
}
}
break;
default:
/* write this instruction as-is */
break;
}
/* remember the last opcode we wrote */
second_last_op_ = last_op_;
last_op_ = opc;
}
/*
* Write a CALLPROP instruction, combining with preceding opcodes if
* possible.
*/
void CTcGenTarg::write_callprop(int argc, int varargs, vm_prop_id_t prop)
{
/*
* if the previous instruction was GETLCL1, combine it with the
* CALLPROP to form a single CALLPROPLCL1 instruction
*/
if (last_op_ == OPC_GETLCL1)
{
uchar lcl;
/* get the local variable ID from the GETLCL1 instruction */
lcl = G_cs->get_byte_at(G_cs->get_ofs() - 1);
/* back up and delete the GETLCL1 instruction */
G_cs->dec_ofs(2);
/* roll back the peephole for the instruction deletion */
last_op_ = second_last_op_;
second_last_op_ = OPC_NOP;
/* write the varargs modifier if appropriate */
if (varargs)
write_op(OPC_VARARGC);
/* write the CALLPROPLCL1 */
write_op(OPC_CALLPROPLCL1);
G_cs->write((char)argc);
G_cs->write(lcl);
G_cs->write_prop_id(prop);
}
else
{
/* generate the varargs modifier if appropriate */
if (varargs)
write_op(OPC_VARARGC);
/* we have arguments - generate a CALLPROP */
write_op(OPC_CALLPROP);
G_cs->write((char)argc);
G_cs->write_prop_id(prop);
}
/* callprop removes arguments and the object */
note_pop(argc + 1);
}
/*
* Note a string's length
*/
void CTcGenTarg::note_str(size_t len)
{
/* if it's the longest so far, remember it */
if (len > max_str_len_)
{
/*
* flag an warning the length plus overhead would exceed 32k
* (only do this the first time we cross this limit)
*/
if (len > (32*1024 - VMB_LEN)
&& max_str_len_ <= (32*1024 - VMB_LEN))
G_tok->log_warning(TCERR_CONST_POOL_OVER_32K);
/* remember the length */
max_str_len_ = len;
}
}
/*
* note a list's length
*/
void CTcGenTarg::note_list(size_t element_count)
{
/* if it's the longest list so far, remember it */
if (element_count > max_list_cnt_)
{
/* flag a warning if the stored length would be over 32k */
if (element_count > ((32*1024 - VMB_LEN) / VMB_DATAHOLDER)
&& max_list_cnt_ <= ((32*1024 - VMB_LEN) / VMB_DATAHOLDER))
G_tok->log_warning(TCERR_CONST_POOL_OVER_32K);
/* remember the length */
max_list_cnt_ = element_count;
}
}
/*
* Note a bytecode block length
*/
void CTcGenTarg::note_bytecode(ulong len)
{
/* if it's the longest bytecode block yet, remember it */
if (len > max_bytecode_len_)
{
/* flag a warning the first time we go over 32k */
if (len >= 32*1024 && max_bytecode_len_ < 32*1024)
G_tok->log_warning(TCERR_CODE_POOL_OVER_32K);
/* remember the new length */
max_bytecode_len_ = len;
}
}
/*
* Add a string to the constant pool
*/
void CTcGenTarg::add_const_str(const char *str, size_t len,
CTcDataStream *ds, ulong ofs)
{
CTcStreamAnchor *anchor;
/*
* Add an anchor for the item, and add a fixup for the reference
* from ds@ofs to the item.
*/
anchor = G_ds->add_anchor(0, 0);
CTcAbsFixup::add_abs_fixup(anchor->fixup_list_head_, ds, ofs);
/* write the length prefix */
G_ds->write2(len);
/* write the string bytes */
G_ds->write(str, len);
/* note the length of the string stored */
note_str(len);
}
/*
* Add a list to the constant pool
*/
void CTcGenTarg::add_const_list(CTPNList *lst,
CTcDataStream *ds, ulong ofs)
{
int i;
CTPNListEle *cur;
ulong dst;
CTcStreamAnchor *anchor;
/*
* Add an anchor for the item, and add a fixup for the reference
* from ds@ofs to the item.
*/
anchor = G_ds->add_anchor(0, 0);
CTcAbsFixup::add_abs_fixup(anchor->fixup_list_head_, ds, ofs);
/*
* Reserve space for the list. We need to do this first, because
* the list might contain elements which themselves must be written
* to the data stream; we must therefore reserve space for the
* entire list before we start writing its elements.
*/
dst = G_ds->reserve(2 + lst->get_count()*VMB_DATAHOLDER);
/* set the length prefix */
G_ds->write2_at(dst, lst->get_count());
dst += 2;
/* store the elements */
for (i = 0, cur = lst->get_head() ; cur != 0 ;
++i, cur = cur->get_next(), dst += VMB_DATAHOLDER)
{
CTcConstVal *ele;
/* get this element */
ele = cur->get_expr()->get_const_val();
/* write it to the element buffer */
write_const_as_dh(G_ds, dst, ele);
}
/* make sure the list wasn't corrupted */
if (i != lst->get_count())
G_tok->throw_internal_error(TCERR_CORRUPT_LIST);
/* note the number of elements in the list */
note_list(lst->get_count());
}
/*
* Generate a BigNumber object
*/
vm_obj_id_t CTcGenTarg::gen_bignum_obj(const char *txt, size_t len)
{
vm_obj_id_t id;
long size_ofs;
long end_ofs;
long num_ofs;
CTcDataStream *str = G_bignum_stream;
int exp;
int decpt;
utf8_ptr p;
size_t rem;
int dig[2];
char dig_idx;
int sig;
size_t tot_digits;
size_t prec;
int neg;
int val_zero;
uchar flags;
/* generate a new object ID for the BigNumber */
id = new_obj_id();
/*
* add the object ID to the non-symbol object list - this is
* necessary to ensure that the object ID is fixed up during linking
*/
G_prs->add_nonsym_obj(id);
/*
* generate the object data to the BigNumber data stream
*/
/*
* write the OBJS header - object ID plus byte count for
* metaclass-specific data
*/
str->write_obj_id(id);
size_ofs = str->get_ofs();
str->write2(0);
/*
* write the metaclass-specific data for the BigNumber metaclass
*/
/* remember where the number starts */
num_ofs = str->get_ofs();
/* write placeholders for the precision, exponent, and flags */
str->write2(0);
str->write2(0);
str->write(0);
/* start at the beginning of the number's text */
p.set((char *)txt);
rem = len;
/* presume the value won't be zero */
val_zero = FALSE;
/* check for leading sign indicators */
for (neg = FALSE ; rem != 0 ; p.inc(&rem))
{
/* if it's a sign, note it and keep scanning */
if (p.getch() == '-')
{
/* negative sign - note it and keep going */
neg = !neg;
}
else if (p.getch() == '+')
{
/* positive sign - ignore it and keep going */
}
else
{
/* not a sign character - stop scanning for signs */
break;
}
}
/* scan the digits of the number */
for (exp = 0, sig = FALSE, decpt = FALSE, prec = 0, tot_digits = 0,
dig_idx = 0 ; rem != 0 ; p.inc(&rem))
{
wchar_t ch;
/* get this character */
ch = p.getch();
/* see what we have */
if (is_digit(ch))
{
/*
* if it's non-zero, it's definitely significant; otherwise,
* it's significant only if we've seen a significant digit
* already
*/
if (ch != '0')
sig = TRUE;
/* count it in the total digits whether or not its significant */
++tot_digits;
/* if the digit is significant, add it to the number */
if (sig)
{
/* add another digit to our buffer */
dig[dig_idx++] = value_of_digit(ch);
/* count the precision */
++prec;
/*
* if we haven't found the decimal point yet, count the
* exponent change
*/
if (!decpt)
++exp;
/*
* if we have two digits now, write out another byte of
* the number
*/
if (dig_idx == 2)
{
/* write out this digit pair */
str->write((char)((dig[0] << 4) + dig[1]));
/* the buffer is now empty */
dig_idx = 0;
}
}
else if (decpt)
{
/*
* we have a leading insignificant zero following the
* decimal point - decrease the exponent
*/
--exp;
}
}
else if (!decpt && ch == '.')
{
/* we've found the decimal point - note it */
decpt = TRUE;
}
else if (ch == 'e' || ch == 'E')
{
int neg_exp = FALSE;
long acc;
/* we've found our exponent - check for a sign */
p.inc(&rem);
if (rem != 0)
{
if (p.getch() == '-')
{
/* note the sign and skip the '-' */
neg_exp = TRUE;
p.inc(&rem);
}
else if (p.getch() == '+')
{
/* skip the '+' */
p.inc(&rem);
}
}
/* scan the digits */
for (acc = 0 ; rem != 0 ; p.inc(&rem))
{
long new_acc;
/* if this isn't a digit, we're done */
if (!is_digit(p.getch()))
break;
/* add in this digit */
new_acc = acc*10 + value_of_digit(p.getch());
if ((!neg_exp && new_acc > 32767)
|| (neg_exp && new_acc > 32768))
break;
/* set the new accumulator */
acc = new_acc;
}
/* set the sign */
if (neg_exp)
acc = -acc;
/*
* add this exponent to the exponent we derived for the
* number itself
*/
exp = (int)(exp + acc);
/*
* since we scanned the string in our own loop, make sure we
* haven't reached the end of the buffer - if we have, we're
* done with the outer loop now
*/
if (rem == 0)
break;
}
else
{
/*
* anything else is invalid, so we've reached the end of the
* number - stop scanning
*/
break;
}
}
/* if we have a pending digit, write it out */
if (dig_idx == 1)
str->write((char)(dig[0] << 4));
/*
* if we had no significant digits, the number is all zeroes, so in
* this special case treat all of the zeroes as significant
*/
if (prec == 0 && tot_digits != 0)
{
/* note that the value is zero */
val_zero = TRUE;
/* use the zeroes as significant digits */
prec = tot_digits;
/* write out the zeroes */
for ( ; tot_digits > 1 ; tot_digits -= 2)
str->write(0);
if (tot_digits > 0)
str->write(0);
/*
* the exponent for the value zero is always 1 (this is a
* normalization rule)
*/
exp = 1;
}
/* construct the flags */
flags = 0;
if (neg)
flags |= VMBN_F_NEG;
if (val_zero)
flags |= VMBN_F_ZERO;
/* go back and fix up the precision, exponent, and flags values */
str->write2_at(num_ofs, prec);
str->write2_at(num_ofs + 2, exp);
str->write_at(num_ofs + 4, flags);
/* fix up the size */
end_ofs = str->get_ofs();
str->write2_at(size_ofs, end_ofs - size_ofs - 2);
/* return the new object ID */
return id;
}
/*
* Convert a constant value from a CTcConstVal (compiler internal
* representation) to a vm_val_t (interpreter representation).
*/
void CTcGenTarg::write_const_as_dh(CTcDataStream *ds, ulong ofs,
const CTcConstVal *src)
{
vm_val_t val;
char buf[VMB_DATAHOLDER];
/* convert according to the value's type */
switch(src->get_type())
{
case TC_CVT_NIL:
val.set_nil();
break;
case TC_CVT_TRUE:
val.set_true();
break;
case TC_CVT_INT:
val.set_int(src->get_val_int());
break;
case TC_CVT_FLOAT:
/* generate the BigNumber object */
val.set_obj(gen_bignum_obj(src->get_val_float(),
src->get_val_float_len()));
/* add a fixup for the object ID */
if (G_keep_objfixups)
CTcIdFixup::add_fixup(&G_objfixup, ds, ofs + 1, val.val.obj);
break;
case TC_CVT_SSTR:
/*
* Store the string in the constant pool. Note that our fixup
* is at the destination stream offset plus one, since the
* DATAHOLDER has the type byte followed by the offset value.
*/
add_const_str(src->get_val_str(), src->get_val_str_len(),
ds, ofs + 1);
/*
* set the offset to zero for now - the fixup that
* add_const_str() generates will take care of supplying the
* real value
*/
val.set_sstring(0);
break;
case TC_CVT_LIST:
/*
* Store the sublist in the constant pool. Our fixup is at the
* destination stream offset plus one, since the DATAHOLDER has
* the type byte followed by the offset value.
*/
add_const_list(src->get_val_list(), ds, ofs + 1);
/*
* set the offset to zero for now - the fixup that
* add_const_list() generates will take care of supplying the
* real value
*/
val.set_list(0);
break;
case TC_CVT_OBJ:
/* set the object ID value */
val.set_obj((vm_obj_id_t)src->get_val_obj());
/*
* add a fixup (at the current offset plus one, for the type
* byte) if we're keeping object ID fixups
*/
if (G_keep_objfixups)
CTcIdFixup::add_fixup(&G_objfixup, ds, ofs + 1,
src->get_val_obj());
break;
case TC_CVT_ENUM:
/* set the enum value */
val.set_enum(src->get_val_enum());
/* add a fixup */
if (G_keep_enumfixups)
CTcIdFixup::add_fixup(&G_enumfixup, ds, ofs + 1,
src->get_val_enum());
break;
case TC_CVT_PROP:
/* set the property ID value */
val.set_propid((vm_prop_id_t)src->get_val_prop());
/*
* add a fixup (at the current offset plus one, for the type
* byte) if we're keeping property ID fixups
*/
if (G_keep_propfixups)
CTcIdFixup::add_fixup(&G_propfixup, ds, ofs + 1,
src->get_val_prop());
break;
case TC_CVT_FUNCPTR:
/*
* use a placeholder value of zero for now - a function's final
* address is never known until after all code generation has
* been completed (the fixup will take care of supplying the
* correct value when the time comes)
*/
val.set_fnptr(0);
/*
* Add a fixup. The fixup is at the destination stream offset
* plus one, because the DATAHOLDER has a type byte followed by
* the function pointer value.
*/
src->get_val_funcptr_sym()->add_abs_fixup(ds, ofs + 1);
break;
case TC_CVT_ANONFUNCPTR:
/* use a placeholder of zero for now, until we fix up the pointer */
val.set_fnptr(0);
/* add a fixup for the code body */
src->get_val_anon_func_ptr()->add_abs_fixup(ds, ofs + 1);
break;
case TC_CVT_VOCAB_LIST:
/*
* it's an internal vocabulary list type - this is used as a
* placeholder only, and will be replaced during linking with an
* actual vocabulary string list
*/
val.typ = VM_VOCAB_LIST;
break;
case TC_CVT_UNK:
/* unknown - ignore it */
break;
}
/* write the vm_val_t in DATA_HOLDER format into the stream */
vmb_put_dh(buf, &val);
ds->write_at(ofs, buf, VMB_DATAHOLDER);
}
/*
* Write a DATAHOLDER at the current offset in a stream
*/
void CTcGenTarg::write_const_as_dh(CTcDataStream *ds,
const CTcConstVal *src)
{
/* write to the current stream offset */
write_const_as_dh(ds, ds->get_ofs(), src);
}
/*
* Notify that parsing is finished
*/
void CTcGenTarg::parsing_done()
{
/* nothing special to do */
}
/*
* notify the code generator that we're replacing an object
*/
void CTcGenTarg::notify_replace_object(ulong stream_ofs)
{
uint flags;
/* set the 'replaced' flag in the flags prefix */
flags = G_os->read2_at(stream_ofs);
flags |= TCT3_OBJ_REPLACED;
G_os->write2_at(stream_ofs, flags);
}
/*
* Set the starting offset of the current method
*/
void CTcGenTarg::set_method_ofs(ulong ofs)
{
/* tell the exception table object about it */
get_exc_table()->set_method_ofs(ofs);
/* remember it in the code stream */
G_cs->set_method_ofs(ofs);
}
/*
* Add a debug line table to our list
*/
void CTcGenTarg::add_debug_line_table(ulong ofs)
{
size_t idx;
uchar *p;
/* calculate the index of the next free entry on its page */
idx = (size_t)(debug_line_cnt_ % TCT3_DEBUG_LINE_PAGE_SIZE);
/*
* if we've completely filled the last page, allocate a new one - we
* know we've exhausted the page if we're at the start of a new page
* (i.e., the index is zero)
*/
if (idx == 0)
{
tct3_debug_line_page *pg;
/* allocate the new page */
pg = (tct3_debug_line_page *)t3malloc(sizeof(*pg));
/* link it in at the end of the list */
pg->nxt = 0;
if (debug_line_tail_ == 0)
debug_line_head_ = pg;
else
debug_line_tail_->nxt = pg;
debug_line_tail_ = pg;
}
/* get a pointer to the entry */
p = debug_line_tail_->line_ofs + (idx * TCT3_DEBUG_LINE_REC_SIZE);
/*
* set this entry - one byte for the code stream ID, then a UINT4
* with the offset in the stream
*/
*p = G_cs->get_stream_id();
oswp4(p + 1, ofs);
/* count it */
++debug_line_cnt_;
}
/* ------------------------------------------------------------------------ */
/*
* Method header generator
*/
/*
* Open a method
*/
void CTcGenTarg::open_method(CTcCodeStream *stream,
CTcSymbol *fixup_owner_sym,
CTcAbsFixup **fixup_list_head,
CTPNCodeBody *code_body,
CTcPrsSymtab *goto_tab,
int argc, int varargs,
int is_constructor, int is_self_available,
tct3_method_gen_ctx *ctx)
{
/* set the code stream as the current code generator output stream */
G_cs = ctx->stream = stream;
/* set the method properties in the code generator */
set_in_constructor(is_constructor);
stream->set_self_available(is_self_available);
/* we obviously can't combine any past instructions */
clear_peephole();
/* clear the old line records */
stream->clear_line_recs();
/* clear the old frame list */
stream->clear_local_frames();
/* clear the old exception table */
get_exc_table()->clear_table();
/* reset the stack depth counters */
reset_sp_depth();
/* there are no enclosing 'switch' or block statements yet */
stream->set_switch(0);
stream->set_enclosing(0);
/*
* remember where the method header starts - we'll need to come back
* and fix up some placeholder entries in "Close"
*/
ctx->method_ofs = stream->get_ofs();
/* set the method start offset in the code generator */
set_method_ofs(ctx->method_ofs);
/* set up a fixup anchor for the new method */
ctx->anchor = stream->add_anchor(fixup_owner_sym, fixup_list_head);
/* set the anchor in the associated symbol, if applicable */
if (fixup_owner_sym != 0)
fixup_owner_sym->set_anchor(ctx->anchor);
/*
* Generate the function header. At the moment, we don't know the
* stack usage, exception table offset, or debug record offset, since
* these all come after the byte code; we won't know how big the byte
* code is until after we generate it. For now, write zero bytes as
* placeholders for these slots; we'll come back and fix them up to
* their real values after we've generated the byte code.
*/
stream->write(argc | (varargs ? 0x80 : 0)); /* argument count */
stream->write(0); /* reserved zero byte */
stream->write2(0); /* number of locals - won't know until after codegen */
stream->write2(0); /* total stack - won't know until after codegen */
stream->write2(0); /* exception table offset - presume none */
stream->write2(0); /* debug record offset - presume no debug records */
/* remember the starting offset of the code */
ctx->code_start_ofs = stream->get_ofs();
/* set the current code body being generated */
ctx->old_code_body = stream->set_code_body(code_body);
/* get the 'goto' symbol table for this function */
stream->set_goto_symtab(goto_tab);
}
/*
* Close a method
*/
void CTcGenTarg::close_method(int local_cnt,
CTcTokFileDesc *end_desc, long end_linenum,
tct3_method_gen_ctx *ctx)
{
/* get the output code stream from the context */
CTcCodeStream *stream = ctx->stream;
/*
* Generate a 'return' opcode with a default 'nil' return value - this
* will ensure that code that reaches the end of the procedure returns
* normally. If this is a constructor, return the 'self' object rather
* than nil.
*
* We only need to generate this epilogue if the next instruction would
* be reachable. If it's not reachable, then the code explicitly took
* care of all types of exits.
*/
if (!can_skip_op())
{
/*
* add a line record for the implied return at the last source line
* of the code body
*/
add_line_rec(end_desc, end_linenum);
/* write the appropriate return */
if (is_in_constructor())
{
/* we're in a constructor - return 'self' */
write_op(OPC_PUSHSELF);
write_op(OPC_RETVAL);
}
else
{
/*
* Normal method/function - return without a value (explicitly
* set R0 to nil, though, so we don't return something returned
* from a called function).
*/
write_op(OPC_RETNIL);
}
}
/*
* release labels allocated for the code block; this will log an error
* if any labels are not defined
*/
stream->release_labels();
/*
* Eliminate jump-to-jump sequences in the generated code. Don't
* bother if we've found any errors, as the generated code will not
* necessarily be valid if this is the case.
*/
if (G_tcmain->get_error_count() == 0)
remove_jumps_to_jumps(stream, ctx->code_start_ofs);
/* note the code block's end point */
ctx->code_end_ofs = stream->get_ofs();
/*
* Fix up the local variable count in the function header. We might
* allocate extra locals for internal use while generating code, so we
* must wait until after generating our code before we know the final
* local count.
*/
stream->write2_at(ctx->method_ofs + 2, local_cnt);
/*
* Fix up the total stack space indicator in the function header. The
* total stack size must include the locals, as well as stack space
* needed for intermediate computations.
*/
stream->write2_at(ctx->method_ofs + 4, get_max_sp_depth() + local_cnt);
/*
* Generate the exception table, if we have one. If we have no
* exception records, leave the exception table offset set to zero to
* indicate that there is no exception table for the method.
*/
if (get_exc_table()->get_entry_count() != 0)
{
/*
* write the exception table offset - it's at the current offset in
* the code
*/
stream->write2_at(ctx->method_ofs + 6,
stream->get_ofs() - ctx->method_ofs);
/* write the table */
get_exc_table()->write_to_code_stream();
}
}
/*
* Clean up after generating a method
*/
void CTcGenTarg::close_method_cleanup(tct3_method_gen_ctx *ctx)
{
/*
* Tell the code generator our code block byte length so that it can
* keep track of the longest single byte-code block; it will use this
* to choose the code pool page size when generating the image file.
*/
note_bytecode(ctx->anchor->get_len(ctx->stream));
/* we're no longer in a constructor, if we ever were */
set_in_constructor(FALSE);
/* clear the current code body */
ctx->stream->set_code_body(ctx->old_code_body);
/* always leave the main code stream active by default */
G_cs = G_cs_main;
}
/* ------------------------------------------------------------------------ */
/*
* Run through the generated code stream starting at the given offset (and
* running up to the current offset), and eliminate jump-to-jump sequences:
*
* - whenever we find any jump instruction that points directly to an
* unconditional jump, we'll change the first jump so that it points to
* the target of the second jump, saving the unnecessary stop at the
* intermediate jump
*
* - whenever we find an unconditional jump to any return or throw
* instruction, we'll replace the jump with a copy of the target
* return/throw instruction.
*/
void CTcGenTarg::remove_jumps_to_jumps(CTcCodeStream *str, ulong start_ofs)
{
ulong ofs;
ulong end_ofs;
uchar prv_op;
static const size_t op_siz[] =
{
0, /* 0x00 - unused */
1, /* 0x01 - OPC_PUSH_0 */
1, /* 0x02 - OPC_PUSH_1 */
2, /* 0x03 - OPC_PUSHINT8 */
5, /* 0x04 - OPC_PUSHINT */
5, /* 0x05 - OPC_PUSHSTR */
5, /* 0x06 - OPC_PUSHLST */
5, /* 0x07 - OPC_PUSHOBJ */
1, /* 0x08 - OPC_PUSHNIL */
1, /* 0x09 - OPC_PUSHTRUE */
3, /* 0x0A - OPC_PUSHPROPID */
5, /* 0x0B - OPC_PUSHFNPTR */
0, /* 0x0C - OPC_PUSHSTRI - variable-size instruction */
2, /* 0x0D - OPC_PUSHPARLST */
1, /* 0x0E - OPC_MAKELSTPAR */
5, /* 0x0F - OPC_PUSHENUM */
1, /* 0x10 - unused */
1, /* 0x11 - unused */
1, /* 0x12 - unused */
1, /* 0x13 - unused */
1, /* 0x14 - unused */
1, /* 0x15 - unused */
1, /* 0x16 - unused */
1, /* 0x17 - unused */
1, /* 0x18 - unused */
1, /* 0x19 - unused */
1, /* 0x1A - unused */
1, /* 0x1B - unused */
1, /* 0x1C - unused */
1, /* 0x1D - unused */
1, /* 0x1E - unused */
1, /* 0x1F - unused */
1, /* 0x20 - OPC_NEG */
1, /* 0x21 - OPC_BNOT */
1, /* 0x22 - OPC_ADD */
1, /* 0x23 - OPC_SUB */
1, /* 0x24 - OPC_MUL */
1, /* 0x25 - OPC_BAND */
1, /* 0x26 - OPC_BOR */
1, /* 0x27 - OPC_SHL */
1, /* 0x28 - OPC_SHR */
1, /* 0x29 - OPC_XOR */
1, /* 0x2A - OPC_DIV */
1, /* 0x2B - OPC_MOD */
1, /* 0x2C - OPC_NOT */
1, /* 0x2D - OPC_BOOLIZE */
1, /* 0x2E - OPC_INC */
1, /* 0x2F - OPC_DEC */
1, /* 0x30 - unused */
1, /* 0x31 - unused */
1, /* 0x32 - unused */
1, /* 0x33 - unused */
1, /* 0x34 - unused */
1, /* 0x35 - unused */
1, /* 0x36 - unused */
1, /* 0x37 - unused */
1, /* 0x38 - unused */
1, /* 0x39 - unused */
1, /* 0x3A - unused */
1, /* 0x3B - unused */
1, /* 0x3C - unused */
1, /* 0x3D - unused */
1, /* 0x3E - unused */
1, /* 0x3F - unused */
1, /* 0x40 - OPC_EQ */
1, /* 0x41 - OPC_NE */
1, /* 0x42 - OPC_LT */
1, /* 0x43 - OPC_LE */
1, /* 0x44 - OPC_GT */
1, /* 0x45 - OPC_GE */
1, /* 0x46 - unused */
1, /* 0x47 - unused */
1, /* 0x48 - unused */
1, /* 0x49 - unused */
1, /* 0x4A - unused */
1, /* 0x4B - unused */
1, /* 0x4C - unused */
1, /* 0x4D - unused */
1, /* 0x4E - unused */
1, /* 0x4F - unused */
1, /* 0x50 - OPC_RETVAL */
1, /* 0x51 - OPC_RETNIL */
1, /* 0x52 - OPC_RETTRUE */
1, /* 0x53 - unused */
1, /* 0x54 - OPC_RET */
1, /* 0x55 - unused */
1, /* 0x56 - unused */
1, /* 0x57 - unused */
6, /* 0x58 - OPC_CALL */
2, /* 0x59 - OPC_PTRCALL */
1, /* 0x5A - unused */
1, /* 0x5B - unused */
1, /* 0x5C - unused */
1, /* 0x5D - unused */
1, /* 0x5E - unused */
1, /* 0x5F - unused */
3, /* 0x60 - OPC_GETPROP */
4, /* 0x61 - OPC_CALLPROP */
2, /* 0x62 - OPC_PTRCALLPROP */
3, /* 0x63 - OPC_GETPROPSELF */
4, /* 0x64 - OPC_CALLPROPSELF */
2, /* 0x65 - OPC_PTRCALLPROPSELF */
7, /* 0x66 - OPC_OBJGETPROP */
8, /* 0x67 - OPC_OBJCALLPROP */
3, /* 0x68 - OPC_GETPROPDATA */
1, /* 0x69 - OPC_PTRGETPROPDATA */
4, /* 0x6A - OPC_GETPROPLCL1 */
5, /* 0x6B - OPC_CALLPROPLCL1 */
3, /* 0x6C - OPC_GETPROPR0 */
4, /* 0x6D - OPC_CALLPROPR0 */
1, /* 0x6E - unused */
1, /* 0x6F - unused */
1, /* 0x70 - unused */
1, /* 0x71 - unused */
4, /* 0x72 - OPC_INHERIT */
2, /* 0x73 - OPC_PTRINHERIT */
8, /* 0x74 - OPC_EXPINHERIT */
6, /* 0x75 - OPC_PTREXPINHERIT */
1, /* 0x76 - OPC_VARARGC */
4, /* 0x77 - OPC_DELEGATE */
2, /* 0x78 - OPC_PTRDELEGATE */
1, /* 0x79 - unused */
1, /* 0x7A - unused */
1, /* 0x7B - unused */
1, /* 0x7C - unused */
1, /* 0x7D - unused */
1, /* 0x7E - unused */
1, /* 0x7F - unused */
2, /* 0x80 - OPC_GETLCL1 */
3, /* 0x81 - OPC_GETLCL2 */
2, /* 0x82 - OPC_GETARG1 */
3, /* 0x83 - OPC_GETARG2 */
1, /* 0x84 - OPC_PUSHSELF */
5, /* 0x85 - OPC_GETDBLCL */
5, /* 0x86 - OPC_GETDBARG */
1, /* 0x87 - OPC_GETARGC */
1, /* 0x88 - OPC_DUP */
1, /* 0x89 - OPC_DISC */
2, /* 0x8A - OPC_DISC1 */
1, /* 0x8B - OPC_GETR0 */
3, /* 0x8C - OPC_GETDBARGC */
1, /* 0x8D - OPC_SWAP */
2, /* 0x8E - OPC_PUSHCTXELE */
1, /* 0x8F - unused */
0, /* 0x90 - OPC_SWITCH - variable-size instruction */
3, /* 0x91 - OPC_JMP */
3, /* 0x92 - OPC_JT */
3, /* 0x93 - OPC_JF */
3, /* 0x94 - OPC_JE */
3, /* 0x95 - OPC_JNE */
3, /* 0x96 - OPC_JGT */
3, /* 0x97 - OPC_JGE */
3, /* 0x98 - OPC_JLT */
3, /* 0x99 - OPC_JLE */
3, /* 0x9A - OPC_JST */
3, /* 0x9B - OPC_JSF */
3, /* 0x9C - OPC_LJSR */
3, /* 0x9D - OPC_LRET */
3, /* 0x9E - OPC_JNIL */
3, /* 0x9F - OPC_JNOTNIL */
3, /* 0xA0 - OPC_JR0T */
3, /* 0xA1 - OPC_JR0F */
1, /* 0xA2 - unused */
1, /* 0xA3 - unused */
1, /* 0xA4 - unused */
1, /* 0xA5 - unused */
1, /* 0xA6 - unused */
1, /* 0xA7 - unused */
1, /* 0xA8 - unused */
1, /* 0xA9 - unused */
1, /* 0xAA - unused */
1, /* 0xAB - unused */
1, /* 0xAC - unused */
1, /* 0xAD - unused */
1, /* 0xAE - unused */
1, /* 0xAF - unused */
5, /* 0xB0 - OPC_SAY */
3, /* 0xB1 - OPC_BUILTIN_A */
3, /* 0xB2 - OPC_BUILTIN_B */
3, /* 0xB3 - OPC_BUILTIN_C */
3, /* 0xB4 - OPC_BUILTIN_D */
3, /* 0xB5 - OPC_BUILTIN1 */
4, /* 0xB6 - OPC_BUILTIN2 */
0, /* 0xB7 - OPC_CALLEXT (reserved; not currently implemented) */
1, /* 0xB8 - OPC_THROW */
1, /* 0xB9 - OPC_SAYVAL */
1, /* 0xBA - OPC_INDEX */
3, /* 0xBB - OPC_IDXLCL1INT8 */
2, /* 0xBC - OPC_IDXLINT8 */
1, /* 0xBD - unused */
1, /* 0xBE - unused */
1, /* 0xBF - unused */
3, /* 0xC0 - OPC_NEW1 */
5, /* 0xC1 - OPC_NEW2 */
3, /* 0xC2 - OPC_TRNEW1 */
5, /* 0xC3 - OPC_TRNEW2 */
1, /* 0xC4 - unused */
1, /* 0xC5 - unused */
1, /* 0xC6 - unused */
1, /* 0xC7 - unused */
1, /* 0xC8 - unused */
1, /* 0xC9 - unused */
1, /* 0xCA - unused */
1, /* 0xCB - unused */
1, /* 0xCC - unused */
1, /* 0xCD - unused */
1, /* 0xCE - unused */
1, /* 0xCF - unused */
3, /* 0xD0 - OPC_INCLCL */
3, /* 0xD1 - OPC_DECLCL */
3, /* 0xD2 - OPC_ADDILCL1 */
7, /* 0xD3 - OPC_ADDILCL4 */
3, /* 0xD4 - OPC_ADDTOLCL */
3, /* 0xD5 - OPC_SUBFROMLCL */
2, /* 0xD6 - OPC_ZEROLCL1 */
3, /* 0xD7 - OPC_ZEROLCL2 */
2, /* 0xD8 - OPC_NILLCL1 */
3, /* 0xD9 - OPC_NILLCL2 */
2, /* 0xDA - OPC_ONELCL1 */
3, /* 0xDB - OPC_ONELCL2 */
1, /* 0xDC - unused */
1, /* 0xDD - unused */
1, /* 0xDE - unused */
1, /* 0xDF - unused */
2, /* 0xE0 - OPC_SETLCL1 */
3, /* 0xE1 - OPC_SETLCL2 */
2, /* 0xE2 - OPC_SETARG1 */
3, /* 0xE3 - OPC_SETARG2 */
1, /* 0xE4 - OPC_SETIND */
3, /* 0xE5 - OPC_SETPROP */
1, /* 0xE6 - OPC_PTRSETPROP */
3, /* 0xE7 - OPC_SETPROPSELF */
7, /* 0xE8 - OPC_OBJSETPROP */
5, /* 0xE9 - OPC_SETDBLCL */
5, /* 0xEA - OPC_SETDBARG */
1, /* 0xEB - OPC_SETSELF */
1, /* 0xEC - OPC_LOADCTX */
1, /* 0xED - OPC_STORECTX */
2, /* 0xEE - OPC_SETLCL1R0 */
3, /* 0xEF - OPC_SETINDLCL1I8 */
1, /* 0xF0 - unused */
1, /* 0xF1 - OPC_BP */
1, /* 0xF2 - OPC_NOP */
1, /* 0xF3 - unused */
1, /* 0xF4 - unused */
1, /* 0xF5 - unused */
1, /* 0xF6 - unused */
1, /* 0xF7 - unused */
1, /* 0xF8 - unused */
1, /* 0xF9 - unused */
1, /* 0xFA - unused */
1, /* 0xFB - unused */
1, /* 0xFC - unused */
1, /* 0xFD - unused */
1, /* 0xFE - unused */
255, /* 0xFF - unused */
};
/*
* scan the code stream starting at the given offset, continuing
* through the current offset
*/
prv_op = OPC_NOP;
for (ofs = start_ofs, end_ofs = str->get_ofs() ; ofs < end_ofs ; )
{
uchar op;
ulong target_ofs;
ulong orig_target_ofs;
uchar target_op;
int done;
int chain_len;
/* check the byte code instruction at the current location */
switch(op = str->get_byte_at(ofs))
{
case OPC_RETVAL:
/*
* If our previous opcode was PUSHTRUE or PUSHNIL, we can
* replace the previous opcode with RETTRUE or RETNIL. This
* sequence can occur when we generate conditional code that
* returns a value; in such cases, we sometimes can't elide
* the PUSHx/RETVAL sequence during the original code
* generation because the RETVAL itself is the target of a
* label and thus must be retained as a separate instruction.
* Converting the PUSHTRUE or PUSHNIL here won't do any harm,
* as we'll still leave the RETVAL as a separate instruction.
* Likewise, if the previous instruction was GET_R0, we can
* change it to a simple RET.
*/
switch(prv_op)
{
case OPC_PUSHTRUE:
/* convert the PUSHTRUE to a RETTRUE */
str->write_at(ofs - 1, OPC_RETTRUE);
break;
case OPC_PUSHNIL:
/* convert the PUSHNIL to a RETNIL */
str->write_at(ofs - 1, OPC_RETNIL);
break;
case OPC_GETR0:
/* convert the GETR0 to a RET */
str->write_at(ofs - 1, OPC_RET);
break;
}
/* skip the RETVAL */
ofs += 1;
break;
case OPC_PUSHSTRI:
/*
* push in-line string: we have a UINT2 operand giving the
* length in bytes of the string, followed by the bytes of the
* string, so read the uint2 and then skip that amount plus
* three additional bytes (one for the opcode, two for the
* uint2 itself)
*/
ofs += 3 + str->readu2_at(ofs+1);
break;
case OPC_SWITCH:
/*
* Switch: we have a UINT2 giving the number of cases,
* followed by the cases, followed by an INT2; each case
* consists of a DATAHOLDER plus a UINT2, for a total of 7
* bytes. The total is thus 5 bytes (the opcode, the case
* count UINT2, the final INT2) plus 7 bytes times the number
* of cases.
*/
ofs += 5 + 7*str->readu2_at(ofs+1);
break;
case OPC_JMP:
/*
* Unconditional jump: check for a jump to a RETURN of any
* kind or a THROW. If the destination consists of either of
* those, replace the JMP with the target instruction. If the
* destination is an unconditional JMP, iteratively check its
* destination.
*/
orig_target_ofs = target_ofs = ofs + 1 + str->read2_at(ofs + 1);
/*
* Iterate through any chain of JMP's we find. Abort if we
* try following a chain longer than 20 jumps, in case we
* should encounter any circular chains.
*/
for (done = FALSE, chain_len = 0 ; !done && chain_len < 20 ;
++chain_len)
{
switch(target_op = str->get_byte_at(target_ofs))
{
case OPC_RETVAL:
/*
* Check for a special sequence that we can optimize
* even better than the usual. If we have a GETR0
* followed by a JMP to a RETVAL, then we can
* eliminate the JMP *and* the GETR0, and just convert
* the GETR0 to a RET.
*/
if (prv_op == OPC_GETR0)
{
/*
* The GETR0 is the byte before the original JMP:
* simply replace it with a RET. Note that we can
* leave the original jump intact, in case anyone
* else is pointing to it.
*/
str->write_at(ofs - 1, OPC_RET);
/* we're done iterating the chain of jumps-to-jumps */
done = TRUE;
}
else
{
/* handle the same as any return instruction */
goto any_RET;
}
break;
case OPC_RETNIL:
case OPC_RETTRUE:
case OPC_RET:
case OPC_THROW:
any_RET:
/*
* it's a THROW or RETURN of some kind - simply copy
* it to the current slot; write NOP's over our jump
* offset operand, to make sure the code continues to
* be deterministically readable
*/
str->write_at(ofs, target_op);
str->write_at(ofs + 1, (uchar)OPC_NOP);
str->write_at(ofs + 2, (uchar)OPC_NOP);
/* we're done iterating the chain of jumps-to-jumps */
done = TRUE;
break;
case OPC_JMP:
/*
* We're jumping to another jump - there's no reason
* to stop at the intermediate jump instruction, since
* we can simply jump directly to the destination
* address. Calculate the new target address, and
* continue iterating, in case this jumps to something
* we can further optimize away.
*/
target_ofs = target_ofs + 1
+ str->read2_at(target_ofs + 1);
/*
* if it's a jump to the original location, it must be
* some unreachable code that generated a circular
* jump; ignore it in this case
*/
if (target_ofs == ofs)
done = TRUE;
/* proceed */
break;
default:
/*
* For anything else, we're done with any chain of
* jumps to jumps. If we indeed found a new target
* address, rewrite the original JMP instruction so
* that it jumps directly to the end of the chain
* rather than going through the intermediate jumps.
*/
if (target_ofs != orig_target_ofs)
str->write2_at(ofs + 1, target_ofs - (ofs + 1));
/* we're done iterating */
done = TRUE;
break;
}
}
/* whatever happened, skip past the jump */
ofs += 3;
/* done */
break;
case OPC_JT:
case OPC_JF:
case OPC_JE:
case OPC_JNE:
case OPC_JGT:
case OPC_JGE:
case OPC_JLT:
case OPC_JLE:
case OPC_JST:
case OPC_JSF:
case OPC_JNIL:
case OPC_JNOTNIL:
case OPC_JR0T:
case OPC_JR0F:
/*
* We have a jump (conditional or otherwise). Check the
* target instruction to see if it's an unconditional jump; if
* so, then we can jump straight to the target of the second
* jump, since there's no reason to stop at the intermediate
* jump instruction on our way to the final destination. Make
* this check iteratively, so that we eliminate any chain of
* jumps to jumps and land at our final non-jump instruction
* in one go.
*/
orig_target_ofs = target_ofs = ofs + 1 + str->read2_at(ofs + 1);
for (done = FALSE, chain_len = 0 ; !done && chain_len < 20 ;
++chain_len)
{
uchar target_op;
/* get the target opcode */
target_op = str->get_byte_at(target_ofs);
/*
* if the target is an unconditional JMP, we can retarget
* the original instruction to jump directly to the target
* of the target JMP, bypassing the target JMP entirely and
* thus avoiding some unnecessary work at run-time
*/
if (target_op == OPC_JMP)
{
/*
* retarget the original jump to go directly to the
* target of the target JMP
*/
target_ofs = target_ofs + 1
+ str->read2_at(target_ofs + 1);
/*
* continue scanning for more opportunities, as the new
* target could also point to something we can bypass
*/
continue;
}
/*
* Certain combinations are special. If the original
* opcode was a JST or JSF, and the target is a JT or JF,
* we can recode the sequence so that the original opcode
* turns into a more efficient JT or JF and jumps directly
* past the JT or JF. If we have a JST or JSF jumping to a
* JST or JSF, we can also recode that sequence to bypass
* the second jump. In both cases, we can recode the
* sequence because the original jump will unequivocally
* determine the behavior at the target jump in such a way
* that we can compact the sequence into a single jump.
*/
switch(op)
{
case OPC_JSF:
/*
* the original is a JSF: we can recode a jump to a
* JSF, JST, JF, or JT
*/
switch(target_op)
{
case OPC_JSF:
/*
* We're jumping to another JSF. Since the
* original jump will only reach the target jump if
* the value on the top of the stack is false, and
* will then leave this same value on the stack to
* be tested again with the target JSF, we know the
* target JSF will perform its jump and leave the
* stack unchanged again. So, we can simply
* retarget the original jump to the target of the
* target JSF.
*/
target_ofs = target_ofs + 1
+ str->read2_at(target_ofs + 1);
/* keep scanning for additional opportunities */
break;
case OPC_JST:
case OPC_JT:
/*
* We're jumping to a JST or a JT. Since the JSF
* will only reach the JST/JT on a false value, we
* know the JST/JT will NOT jump - we know for a
* fact it will pop the non-true stack element and
* proceed without jumping. Therefore, we can
* avoid saving the value from the original JSF,
* which means we can recode the original as the
* simpler JF (which doesn't bother saving the
* false value), and jump on false directly to the
* instruction after the target JST/JT.
*/
str->write_at(ofs, (uchar)OPC_JF);
op = OPC_JF;
/* jump to the instruction after the target JST/JT */
target_ofs += 3;
/* keep looking for more jumps */
break;
case OPC_JF:
/*
* We're jumping to a JF: we know the JF will jump,
* because we had to have - and then save - a false
* value for the JSF to reach the JF in the first
* place. Since we know for a fact the target JF
* will remove the false value and jump to its
* target, we can bypass the target JF by recoding
* the original instruction as a simpler JF and
* jumping directly to the target of the target JF.
*/
str->write_at(ofs, (uchar)OPC_JF);
op = OPC_JF;
/* jump to the tartet of the target JF */
target_ofs = target_ofs + 1
+ str->read2_at(target_ofs + 1);
/* keep scanning for more jumps */
break;
default:
/* can't make any assumptions about other targets */
done = TRUE;
break;
}
break;
case OPC_JST:
/*
* the original is a JST: recode it if the target is a
* JSF, JST, JF, or JT
*/
switch(target_op)
{
case OPC_JST:
/* JST jumping to JST: jump to the target's target */
target_ofs = target_ofs + 1
+ str->read2_at(target_ofs + 1);
/* keep looking */
break;
case OPC_JSF:
case OPC_JF:
/*
* JST jumping to JSF/JF: the JSF/JF will
* definitely pop the stack and not jump (since the
* original JST will have left a true value on the
* stack), so we can recode the JST as a more
* efficient JT and jump to the instruction after
* the JSF/JF target
*/
str->write_at(ofs, (uchar)OPC_JT);
op = OPC_JT;
/* jump to the instruction after the target */
target_ofs += 3;
/* keep looking */
break;
case OPC_JT:
/*
* JST jumping to JT: the JT will definitely pop
* and jump, so we can recode the original as a
* simpler JT and jump to the target's target
*/
str->write_at(ofs, (uchar)OPC_JT);
op = OPC_JT;
/* jump to the target of the target */
target_ofs = target_ofs + 1
+ str->read2_at(target_ofs + 1);
/* keep scanning */
break;
default:
/* can't make any assumptions about other targets */
done = TRUE;
break;
}
break;
default:
/*
* we can't make assumptions about anything else, so
* we've come to the end of the road - stop scanning
*/
done = TRUE;
break;
}
}
/*
* if we found a chain of jumps, replace our original jump
* target with the final jump target, bypassing the
* intermediate jumps
*/
if (target_ofs != orig_target_ofs)
str->write2_at(ofs + 1, target_ofs - (ofs + 1));
/* skip past the jump */
ofs += 3;
/* done */
break;
default:
/*
* everything else is a fixed-size instruction, so simply
* consult our table of instruction lengths to determine the
* offset of the next instruction
*/
ofs += op_siz[op];
break;
}
/* remember the preceding opcode */
prv_op = op;
}
}
/* ------------------------------------------------------------------------ */
/*
* Generic T3 node
*/
/*
* generate a jump-ahead instruction, returning a new label which serves
* as the jump destination
*/
CTcCodeLabel *CTcPrsNode::gen_jump_ahead(uchar opc)
{
CTcCodeLabel *lbl;
/*
* check to see if we should suppress the jump for peephole
* optimization
*/
if (G_cg->can_skip_op())
return 0;
/* emit the opcode */
G_cg->write_op(opc);
/* allocate a new label */
lbl = G_cs->new_label_fwd();
/*
* write the forward offset to the label (this will generate a fixup
* record attached to the label, so that we'll come back and fix it
* up when the real offset is known)
*/
G_cs->write_ofs2(lbl, 0);
/* return the forward label */
return lbl;
}
/*
* Allocate a new label at the current write position
*/
CTcCodeLabel *CTcPrsNode::new_label_here()
{
/*
* suppress any peephole optimizations at this point -- someone
* could jump directly to this instruction, so we can't combine an
* instruction destined for this point with anything previous
*/
G_cg->clear_peephole();
/* create and return a label at the current position */
return G_cs->new_label_here();
}
/*
* define the position of a code label
*/
void CTcPrsNode::def_label_pos(CTcCodeLabel *lbl)
{
/* if the label is null, ignore it */
if (lbl == 0)
return;
/*
* try eliminating a jump-to-next-instruction sequence: if the last
* opcode was a JMP to this label, remove the last instruction
* entirely
*/
if (G_cg->get_last_op() == OPC_JMP
&& G_cs->has_fixup_at_ofs(lbl, G_cs->get_ofs() - 2))
{
/* remove the fixup pointing to the preceding JMP */
G_cs->remove_fixup_at_ofs(lbl, G_cs->get_ofs() - 2);
/* the JMP is unnecessary - remove it */
G_cg->remove_last_jmp();
}
/* define the label position and apply the fixup */
G_cs->def_label_pos(lbl);
/*
* whenever we define a label, we must suppress any peephole
* optimizations at this point - someone could jump directly to this
* instruction, so we can't combine an instruction destined for this
* point with anything previous
*/
G_cg->clear_peephole();
}
/*
* Generate code for an if-else conditional test. The default
* implementation is to evaluate the expression, and jump to the false
* branch if the expression is false (or jump to the true part if the
* expression is true and there's no false part).
*/
void CTcPrsNode::gen_code_cond(CTcCodeLabel *then_label,
CTcCodeLabel *else_label)
{
/* generate our expression code */
gen_code(FALSE, TRUE);
/*
* if we have a 'then' part, jump to the 'then' part if the condition
* is true; otherwise, jump to the 'else' part if the condition is
* false
*/
if (then_label != 0)
{
/* we have a 'then' part, so jump if true to the 'then' part */
G_cg->write_op(OPC_JT);
G_cs->write_ofs2(then_label, 0);
}
else
{
/* we have an 'else' part, so jump if false to the 'else' */
G_cg->write_op(OPC_JF);
G_cs->write_ofs2(else_label, 0);
}
/* the JF or JT pops an element off the stack */
G_cg->note_pop();
}
/*
* generate code for assignment to this node
*/
int CTcPrsNode::gen_code_asi(int, tc_asitype_t, CTcPrsNode *,
int ignore_error)
{
/*
* if ignoring errors, the caller is trying to assign if possible
* but doesn't require it to be possible; simply return false to
* indicate that nothing happened if this is the case
*/
if (ignore_error)
return FALSE;
/* we should never get here - throw an internal error */
G_tok->throw_internal_error(TCERR_GEN_BAD_LVALUE);
AFTER_ERR_THROW(return FALSE;)
}
/*
* generate code for taking the address of this node
*/
void CTcPrsNode::gen_code_addr()
{
/* we should never get here - throw an internal error */
G_tok->throw_internal_error(TCERR_GEN_BAD_ADDR);
}
/*
* Generate code to call the expression as a function or method.
*/
void CTcPrsNode::gen_code_call(int discard, int argc, int varargs)
{
/* function/method calls are never valid in speculative mode */
if (G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/*
* For default nodes, assume that the result of evaluating the
* expression contained in the node is a method or function pointer.
* First, generate code to evaluate the expression, which should
* yield an appropriate pointer value.
*/
gen_code(FALSE, FALSE);
/*
* if we have a varargs list, modify the call instruction that
* follows to make it a varargs call
*/
if (varargs)
{
/* swap the top of the stack to get the arg counter back on top */
G_cg->write_op(OPC_SWAP);
/* write the varargs modifier */
G_cg->write_op(OPC_VARARGC);
}
/* generate an indirect function call through the pointer */
G_cg->write_op(OPC_PTRCALL);
G_cs->write((char)argc);
/* PTRCALL pops the arguments plus the function pointer */
G_cg->note_pop(argc + 1);
/*
* if the caller isn't going to discard the return value, push the
* result, which is sitting in R0
*/
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/*
* Generate code for operator 'new' applied to this node
*/
void CTcPrsNode::gen_code_new(int, int, int, int, int)
{
/* operator 'new' cannot be applied to a default node */
G_tok->log_error(TCERR_INVAL_NEW_EXPR);
}
/*
* Generate code for a member evaluation
*/
void CTcPrsNode::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
/* evaluate my own expression to yield the object value */
gen_code(FALSE, FALSE);
/* if we have an argument counter, put it back on top */
if (varargs)
G_cg->write_op(OPC_SWAP);
/* use the generic code to generate the rest */
s_gen_member_rhs(discard, prop_expr, prop_is_expr, argc, varargs);
}
/*
* Generic code to generate the rest of a member expression after the
* left side of the '.' has been generated. This can be used for cases
* where the left of the '.' is an arbitrary expression, and hence must
* be evaluated at run-time.
*/
void CTcPrsNode::s_gen_member_rhs(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
vm_prop_id_t prop;
/* we can't call methods with argument in speculative mode */
if (argc != 0 && G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/* get or generate the property ID value */
prop = prop_expr->gen_code_propid(FALSE, prop_is_expr);
/*
* if we got a property ID, generate a simple GETPROP or CALLPROP
* instruction; otherwise, generate a PTRCALLPROP instruction
*/
if (prop != VM_INVALID_PROP)
{
/*
* we have a constant property ID - generate a GETPROP or
* CALLPROP with the property ID as constant data
*/
if (argc == 0)
{
/* no arguments - generate a GETPROP */
G_cg->write_op(G_cg->is_speculative()
? OPC_GETPROPDATA : OPC_GETPROP);
G_cs->write_prop_id(prop);
/* this pops an object element */
G_cg->note_pop();
}
else
{
/* write the CALLPROP instruction */
G_cg->write_callprop(argc, varargs, prop);
}
}
else
{
if (G_cg->is_speculative())
{
/*
* speculative - use PTRGETPROPDATA to ensure we don't cause
* any side effects
*/
G_cg->write_op(OPC_PTRGETPROPDATA);
}
else
{
/*
* if we have a varargs list, modify the call instruction
* that follows to make it a varargs call
*/
if (varargs)
{
/* swap to get the arg counter back on top */
G_cg->write_op(OPC_SWAP);
/* write the varargs modifier */
G_cg->write_op(OPC_VARARGC);
}
/* a property pointer is on the stack - write a PTRCALLPROP */
G_cg->write_op(OPC_PTRCALLPROP);
G_cs->write((int)argc);
}
/*
* ptrcallprop/ptrgetpropdata removes arguments, the object, and
* the property
*/
G_cg->note_pop(argc + 2);
}
/* if we're not discarding the result, push it from R0 */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/*
* Generate code to get the property ID of the expression.
*/
vm_prop_id_t CTcPrsNode::gen_code_propid(int check_only, int is_expr)
{
/*
* simply evaluate the expression normally, anticipating that this
* will yield a property ID value at run-time
*/
if (!check_only)
gen_code(FALSE, FALSE);
/* tell the caller that there's no constant ID available */
return VM_INVALID_PROP;
}
/* ------------------------------------------------------------------------ */
/*
* "self"
*/
/*
* generate code
*/
void CTPNSelf::gen_code(int discard, int)
{
/* it's an error if we're not in a method context */
if (!G_cs->is_self_available())
G_tok->log_error(TCERR_SELF_NOT_AVAIL);
/* if we're not discarding the result, push the "self" object */
if (!discard)
{
G_cg->write_op(OPC_PUSHSELF);
G_cg->note_push();
}
}
/*
* evaluate a property
*/
void CTPNSelf::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
vm_prop_id_t prop;
/* make sure "self" is available */
if (!G_cs->is_self_available())
G_tok->log_error(TCERR_SELF_NOT_AVAIL);
/* don't allow arguments in speculative eval mode */
if (argc != 0 && G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/* generate the property value */
prop = prop_expr->gen_code_propid(FALSE, prop_is_expr);
/*
* if we got a property ID, generate a simple GETPROPSELF or
* CALLPROPSELF; otherwise, generate a PTRCALLPROPSELF
*/
if (prop != VM_INVALID_PROP)
{
/*
* we have a constant property ID - generate a GETPROPDATA,
* GETPROPSELF, or CALLPROPSELF with the property ID as
* immediate data
*/
if (G_cg->is_speculative())
{
/* speculative - use GETPROPDATA */
G_cg->write_op(OPC_PUSHSELF);
G_cg->write_op(OPC_GETPROPDATA);
G_cs->write_prop_id(prop);
/* we pushed one element (self) and popped it back off */
G_cg->note_push();
G_cg->note_pop();
}
else if (argc == 0)
{
/* no arguments - generate a GETPROPSELF */
G_cg->write_op(OPC_GETPROPSELF);
G_cs->write_prop_id(prop);
}
else
{
/* add a varargs modifier if appropriate */
if (varargs)
G_cg->write_op(OPC_VARARGC);
/* we have arguments - generate a CALLPROPSELF */
G_cg->write_op(OPC_CALLPROPSELF);
G_cs->write((char)argc);
G_cs->write_prop_id(prop);
/* this removes arguments */
G_cg->note_pop(argc);
}
}
else
{
/*
* a property pointer is on the stack - use PTRGETPROPDATA or
* PTRCALLPROPSELF, depending on the speculative mode
*/
if (G_cg->is_speculative())
{
/* speculative - use PTRGETPROPDATA after pushing self */
G_cg->write_op(OPC_PUSHSELF);
G_cg->write_op(OPC_PTRGETPROPDATA);
/* we pushed self then removed self and the property ID */
G_cg->note_push();
G_cg->note_pop(2);
}
else
{
/*
* if we have a varargs list, modify the call instruction
* that follows to make it a varargs call
*/
if (varargs)
{
/* swap to get the arg counter back on top */
G_cg->write_op(OPC_SWAP);
/* write the varargs modifier */
G_cg->write_op(OPC_VARARGC);
}
/* a prop pointer is on the stack - write a PTRCALLPROPSELF */
G_cg->write_op(OPC_PTRCALLPROPSELF);
G_cs->write((int)argc);
/* this removes arguments and the property pointer */
G_cg->note_pop(argc + 1);
}
}
/* if the result is needed, push it */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/*
* generate code for an object before a '.'
*/
vm_obj_id_t CTPNSelf::gen_code_obj_predot(int *is_self)
{
/* make sure "self" is available */
if (!G_cs->is_self_available())
G_tok->log_error(TCERR_SELF_NOT_AVAIL);
/* tell the caller that this is "self" */
*is_self = TRUE;
return VM_INVALID_OBJ;
}
/* ------------------------------------------------------------------------ */
/*
* "replaced"
*/
/*
* evaluate 'replaced' on its own - this simply yields a function pointer
* to the modified base code
*/
void CTPNReplaced::gen_code(int discard, int for_condition)
{
/* get the modified base function symbol */
CTcSymFunc *mod_base = G_cs->get_code_body()->get_replaced_func();
/* make sure we're in a 'modify func()' context */
if (mod_base == 0)
G_tok->log_error(TCERR_REPLACED_NOT_AVAIL);
/* this expression yields a pointer to the modified base function */
G_cg->write_op(OPC_PUSHFNPTR);
/* add a fixup for the current code location */
if (mod_base != 0)
mod_base->add_abs_fixup(G_cs);
/* write a placeholder offset - arbitrarily use zero */
G_cs->write4(0);
/* note the push */
G_cg->note_push();
}
/*
* 'replaced()' call - this invokes the modified base code
*/
void CTPNReplaced::gen_code_call(int discard, int argc, int varargs)
{
/* get the modified base function symbol */
CTcSymFunc *mod_base = G_cs->get_code_body()->get_replaced_func();
/* make sure we're in a 'modify func()' context */
if (mod_base == 0)
G_tok->log_error(TCERR_REPLACED_NOT_AVAIL);
/* write the varargs modifier if appropriate */
if (varargs)
G_cg->write_op(OPC_VARARGC);
/* generate the call instruction and argument count */
G_cg->write_op(OPC_CALL);
G_cs->write((char)argc);
/* generate a fixup for the call to the modified base code */
if (mod_base != 0)
mod_base->add_abs_fixup(G_cs);
/* add a placeholder for the function address */
G_cs->write4(0);
/* call removes arguments */
G_cg->note_pop(argc);
/* make sure the argument count is correct */
if (mod_base != 0
&& (mod_base->is_varargs() ? argc < mod_base->get_argc()
: argc != mod_base->get_argc()))
G_tok->log_error(TCERR_WRONG_ARGC_FOR_FUNC,
(int)mod_base->get_sym_len(), mod_base->get_sym(),
mod_base->get_argc(), argc);
/* if we're not discarding, push the return value from R0 */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/* ------------------------------------------------------------------------ */
/*
* "targetprop"
*/
/*
* generate code
*/
void CTPNTargetprop::gen_code(int discard, int)
{
/* it's an error if we're not in a method context */
if (!G_cs->is_self_available())
G_tok->log_error(TCERR_TARGETPROP_NOT_AVAIL);
/* if we're not discarding the result, push the target property ID */
if (!discard)
{
G_cg->write_op(OPC_PUSHCTXELE);
G_cs->write(PUSHCTXELE_TARGPROP);
G_cg->note_push();
}
}
/* ------------------------------------------------------------------------ */
/*
* "targetobj"
*/
/*
* generate code
*/
void CTPNTargetobj::gen_code(int discard, int)
{
/* it's an error if we're not in a method context */
if (!G_cs->is_self_available())
G_tok->log_error(TCERR_TARGETOBJ_NOT_AVAIL);
/* if we're not discarding the result, push the target object ID */
if (!discard)
{
G_cg->write_op(OPC_PUSHCTXELE);
G_cs->write(PUSHCTXELE_TARGOBJ);
G_cg->note_push();
}
}
/* ------------------------------------------------------------------------ */
/*
* "definingobj"
*/
/*
* generate code
*/
void CTPNDefiningobj::gen_code(int discard, int)
{
/* it's an error if we're not in a method context */
if (!G_cs->is_self_available())
G_tok->log_error(TCERR_DEFININGOBJ_NOT_AVAIL);
/* if we're not discarding the result, push the defining object ID */
if (!discard)
{
G_cg->write_op(OPC_PUSHCTXELE);
G_cs->write(PUSHCTXELE_DEFOBJ);
G_cg->note_push();
}
}
/* ------------------------------------------------------------------------ */
/*
* "inherited"
*/
void CTPNInh::gen_code(int, int)
{
/*
* we should never be asked to generate an "inherited" node
* directly; these nodes should always be generated as part of
* member evaluation
*/
G_tok->throw_internal_error(TCERR_GEN_CODE_INH);
}
/*
* evaluate a property
*/
void CTPNInh::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
vm_prop_id_t prop;
/* don't allow 'inherited' in speculative evaluation mode */
if (G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/*
* If we're in a multi-method context, inherited() has a different
* meaning from the ordinary method context meaning.
*/
for (CTPNCodeBody *cb = G_cs->get_code_body() ; cb != 0 ;
cb = cb->get_enclosing())
{
/* check for a multi-method context */
CTcSymFunc *f = cb->get_func_sym();
if (f != 0 && f->is_multimethod())
{
/* generate the multi-method inherited() code */
gen_code_mminh(f, discard, prop_expr, prop_is_expr, argc, varargs);
/* we're done */
return;
}
}
/*
* make sure "self" is available - we obviously can't inherit
* anything if we're not in an object's method
*/
if (!G_cs->is_self_available())
G_tok->log_error(TCERR_SELF_NOT_AVAIL);
/* a type list ('inherited<>') is invalid for regular method inheritance */
if (typelist_ != 0)
G_tok->log_error(TCERR_MMINH_BAD_CONTEXT);
/* generate the property value */
prop = prop_expr->gen_code_propid(FALSE, prop_is_expr);
/*
* if we got a property ID, generate a simple INHERIT;
* otherwise, generate a PTRINHERIT
*/
if (prop != VM_INVALID_PROP)
{
/* generate a varargs modifier if necessary */
if (varargs)
G_cg->write_op(OPC_VARARGC);
/* we have a constant property ID - generate a regular INHERIT */
G_cg->write_op(OPC_INHERIT);
G_cs->write((char)argc);
G_cs->write_prop_id(prop);
/* this removes arguments */
G_cg->note_pop(argc);
}
else
{
/*
* if we have a varargs list, modify the call instruction that
* follows to make it a varargs call
*/
if (varargs)
{
/* swap the top of the stack to get the arg counter back on top */
G_cg->write_op(OPC_SWAP);
/* write the varargs modifier */
G_cg->write_op(OPC_VARARGC);
}
/* a property pointer is on the stack - write a PTRINHERIT */
G_cg->write_op(OPC_PTRINHERIT);
G_cs->write((int)argc);
/* this removes arguments and the property pointer */
G_cg->note_pop(argc + 1);
}
/* if the result is needed, push it */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
void CTPNInh::gen_code_mminh(CTcSymFunc *func, int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
/*
* There are two forms of inherited() for multi-methods.
*
* inherited<types>(args) - this form takes an explicit type list, to
* invoke a specific overridden version.
*
* inherited(args) - this form invokes the next inherited version,
* determined dynamically at run-time.
*/
if (typelist_ == 0)
{
/*
* It's the inherited(args) format.
*
* Call _multiMethodCallInherited(fromFunc, args). We've already
* pushed the args list, so just add the source function argument
* (which is simply our defining function).
*/
func->gen_code(FALSE);
/* look up _multiMethodCallInherited */
CTcSymFunc *mmci = (CTcSymFunc *)G_prs->get_global_symtab()->find(
"_multiMethodCallInherited", 25);
if (mmci == 0 || mmci->get_type() != TC_SYM_FUNC)
{
/* undefined or incorrectly defined - log an error */
G_tok->log_error(TCERR_MMINH_MISSING_SUPPORT_FUNC,
25, "_multiMethodCallInherited");
}
else
{
/* generate the call */
mmci->gen_code_call(discard, argc + 1, varargs);
}
}
else
{
/*
* It's the inherited<types>(args) format.
*/
/*
* Get the base name for the function. 'func' is the decorated
* name for the containing function, which is of the form
* 'Base*type1;type2...'. The base name is the part up to the
* asterisk.
*/
const char *nm = func->getstr(), *p;
size_t rem = func->getlen();
for (p = nm ; rem != 0 && *p != '*' ; ++p, --rem) ;
/* make a token for the base name */
CTcToken btok;
btok.set_text(nm, p - nm);
/* build the decorated name for the target function */
CTcToken dtok;
typelist_->decorate_name(&dtok, &btok);
/* look up the decorated name */
CTcSymFunc *ifunc = (CTcSymFunc *)G_prs->get_global_symtab()->find(
dtok.get_text(), dtok.get_text_len());
/* if we found it, call it */
if (ifunc != 0 && ifunc->get_type() == TC_SYM_FUNC)
{
/* generate the call */
ifunc->gen_code_call(discard, argc, varargs);
}
else
{
/* function not found */
G_tok->log_error(TCERR_MMINH_UNDEF_FUNC, (int)(p - nm), nm);
}
}
}
/* ------------------------------------------------------------------------ */
/*
* "inherited class"
*/
void CTPNInhClass::gen_code(int discard, int for_condition)
{
/*
* we should never be asked to generate an "inherited" node
* directly; these nodes should always be generated as part of
* member evaluation
*/
G_tok->throw_internal_error(TCERR_GEN_CODE_INH);
}
/*
* evaluate a property
*/
void CTPNInhClass::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
vm_prop_id_t prop;
CTcSymbol *objsym;
vm_obj_id_t obj;
/*
* make sure "self" is available - we obviously can't inherit
* anything if we're not in an object's method
*/
if (!G_cs->is_self_available())
G_tok->log_error(TCERR_SELF_NOT_AVAIL);
/* don't allow 'inherited' in speculative evaluation mode */
if (G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/* get the superclass name symbol */
objsym = G_cs->get_symtab()->find_or_def_undef(sym_, len_, FALSE);
/* if it's not an object, we can't inherit from it */
obj = objsym->get_val_obj();
if (obj == VM_INVALID_OBJ)
{
G_tok->log_error(TCERR_INH_NOT_OBJ, (int)len_, sym_);
return;
}
/* generate the property value */
prop = prop_expr->gen_code_propid(FALSE, prop_is_expr);
/*
* if we got a property ID, generate a simple EXPINHERIT; otherwise,
* generate a PTREXPINHERIT
*/
if (prop != VM_INVALID_PROP)
{
/* add a varargs modifier if needed */
if (varargs)
G_cg->write_op(OPC_VARARGC);
/* we have a constant property ID - generate a regular EXPINHERIT */
G_cg->write_op(OPC_EXPINHERIT);
G_cs->write((char)argc);
G_cs->write_prop_id(prop);
G_cs->write_obj_id(obj);
/* this removes argumnts */
G_cg->note_pop(argc);
}
else
{
/*
* if we have a varargs list, modify the call instruction that
* follows to make it a varargs call
*/
if (varargs)
{
/* swap the top of the stack to get the arg counter back on top */
G_cg->write_op(OPC_SWAP);
/* write the varargs modifier */
G_cg->write_op(OPC_VARARGC);
}
/* a property pointer is on the stack - write a PTREXPINHERIT */
G_cg->write_op(OPC_PTREXPINHERIT);
G_cs->write((int)argc);
G_cs->write_obj_id(obj);
/* this removes arguments and the property pointer */
G_cg->note_pop(argc + 1);
}
/* if the result is needed, push it */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/* ------------------------------------------------------------------------ */
/*
* "delegated"
*/
void CTPNDelegated::gen_code(int discard, int for_condition)
{
/*
* we should never be asked to generate a "delegated" node directly;
* these nodes should always be generated as part of member evaluation
*/
G_tok->throw_internal_error(TCERR_GEN_CODE_DELEGATED);
}
/*
* evaluate a property
*/
void CTPNDelegated::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
vm_prop_id_t prop;
/*
* make sure "self" is available - we obviously can't delegate
* anything if we're not in an object's method
*/
if (!G_cs->is_self_available())
G_tok->log_error(TCERR_SELF_NOT_AVAIL);
/* don't allow 'delegated' in speculative evaluation mode */
if (G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/* generate the delegatee expression */
delegatee_->gen_code(FALSE, FALSE);
/* if we have an argument counter, put it back on top */
if (varargs)
G_cg->write_op(OPC_SWAP);
/* generate the property value */
prop = prop_expr->gen_code_propid(FALSE, prop_is_expr);
/*
* if we got a property ID, generate a simple DELEGATE; otherwise,
* generate a PTRDELEGATE
*/
if (prop != VM_INVALID_PROP)
{
/* add a varargs modifier if needed */
if (varargs)
G_cg->write_op(OPC_VARARGC);
/* we have a constant property ID - generate a regular DELEGATE */
G_cg->write_op(OPC_DELEGATE);
G_cs->write((char)argc);
G_cs->write_prop_id(prop);
/* this removes arguments and the object value */
G_cg->note_pop(argc + 1);
}
else
{
/*
* if we have a varargs list, modify the call instruction that
* follows to make it a varargs call
*/
if (varargs)
{
/* swap the top of the stack to get the arg counter back on top */
G_cg->write_op(OPC_SWAP);
/* write the varargs modifier */
G_cg->write_op(OPC_VARARGC);
}
/* a property pointer is on the stack - write a PTRDELEGATE */
G_cg->write_op(OPC_PTRDELEGATE);
G_cs->write((int)argc);
/* this removes arguments, the object, and the property pointer */
G_cg->note_pop(argc + 2);
}
/* if the result is needed, push it */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/* ------------------------------------------------------------------------ */
/*
* "argcount"
*/
void CTPNArgc::gen_code(int discard, int)
{
/* generate the argument count, if we're not discarding */
if (!discard)
{
if (G_cg->is_eval_for_debug())
{
/* generate a debug argument count evaluation */
G_cg->write_op(OPC_GETDBARGC);
G_cs->write2(G_cg->get_debug_stack_level());
}
else
{
/* generate the normal argument count evaluation */
G_cg->write_op(OPC_GETARGC);
}
/* we push one element */
G_cg->note_push();
}
}
/* ------------------------------------------------------------------------ */
/*
* constant
*/
void CTPNConst::gen_code(int discard, int)
{
/* if we're discarding the value, do nothing */
if (discard)
return;
/* generate the appropriate type of push for the value */
switch(val_.get_type())
{
case TC_CVT_NIL:
G_cg->write_op(OPC_PUSHNIL);
break;
case TC_CVT_TRUE:
G_cg->write_op(OPC_PUSHTRUE);
break;
case TC_CVT_INT:
/* write the push-integer instruction */
s_gen_code_int(val_.get_val_int());
/* s_gen_code_int notes a push, which we'll do also, so cancel it */
G_cg->note_pop();
break;
case TC_CVT_FLOAT:
/* we'll represent it as a BigNumber object */
G_cg->write_op(OPC_PUSHOBJ);
/* generate the BigNumber object and write its ID */
G_cs->write_obj_id(G_cg->gen_bignum_obj(val_.get_val_float(),
val_.get_val_float_len()));
break;
case TC_CVT_SSTR:
/* write the instruction to push a constant pool string */
G_cg->write_op(OPC_PUSHSTR);
/*
* add the string to the constant pool, creating a fixup at the
* current code stream location
*/
G_cg->add_const_str(val_.get_val_str(), val_.get_val_str_len(),
G_cs, G_cs->get_ofs());
/*
* write a placeholder address - this will be corrected by the
* fixup that add_const_str() created for us
*/
G_cs->write4(0);
break;
case TC_CVT_LIST:
/* write the instruction */
G_cg->write_op(OPC_PUSHLST);
/*
* add the list to the constant pool, creating a fixup at the
* current code stream location
*/
G_cg->add_const_list(val_.get_val_list(), G_cs, G_cs->get_ofs());
/*
* write a placeholder address - this will be corrected by the
* fixup that add_const_list() created for us
*/
G_cs->write4(0);
break;
case TC_CVT_OBJ:
/* generate the object ID */
G_cg->write_op(OPC_PUSHOBJ);
G_cs->write_obj_id(val_.get_val_obj());
break;
case TC_CVT_PROP:
/* generate the property address */
G_cg->write_op(OPC_PUSHPROPID);
G_cs->write_prop_id(val_.get_val_prop());
break;
case TC_CVT_ENUM:
/* generate the enum value */
G_cg->write_op(OPC_PUSHENUM);
G_cs->write_enum_id(val_.get_val_enum());
break;
case TC_CVT_FUNCPTR:
/* generate the function pointer instruction */
G_cg->write_op(OPC_PUSHFNPTR);
/* add a fixup for the function address */
val_.get_val_funcptr_sym()->add_abs_fixup(G_cs);
/* write out a placeholder - arbitrarily use zero */
G_cs->write4(0);
break;
case TC_CVT_ANONFUNCPTR:
/* generate the function pointer instruction */
G_cg->write_op(OPC_PUSHFNPTR);
/* add a fixup for the code body address */
val_.get_val_anon_func_ptr()->add_abs_fixup(G_cs);
/* write our a placeholder */
G_cs->write4(0);
break;
default:
/* anything else is an internal error */
G_tok->throw_internal_error(TCERR_GEN_UNK_CONST_TYPE);
}
/* all of these push a value */
G_cg->note_push();
}
/*
* generate code to push an integer value
*/
void CTPNConst::s_gen_code_int(long intval)
{
/* push the smallest format that will fit the value */
if (intval == 0)
{
/* write the special PUSH_0 instruction */
G_cg->write_op(OPC_PUSH_0);
}
else if (intval == 1)
{
/* write the special PUSH_1 instruction */
G_cg->write_op(OPC_PUSH_1);
}
else if (intval < 127 && intval >= -128)
{
/* it fits in eight bits */
G_cg->write_op(OPC_PUSHINT8);
G_cs->write((char)intval);
}
else
{
/* it doesn't fit in 8 bits - use a full 32 bits */
G_cg->write_op(OPC_PUSHINT);
G_cs->write4(intval);
}
/* however we did it, we left one value on the stack */
G_cg->note_push();
}
/*
* Generate code to apply operator 'new' to the constant. We can apply
* 'new' only to constant object values.
*/
void CTPNConst::gen_code_new(int discard, int argc, int varargs,
int /*from_call*/, int is_transient)
{
/* check the type */
switch(val_.get_type())
{
case TC_CVT_OBJ:
/*
* Treat this the same as any other 'new' call. An object symbol
* folded into a constant is guaranteed to be of metaclass
* TadsObject - that's the only kind of symbol we'll ever fold this
* way.
*/
CTcSymObj::s_gen_code_new(discard,
val_.get_val_obj(), val_.get_val_obj_meta(),
argc, varargs, is_transient);
break;
default:
/* can't apply 'new' to other constant values */
G_tok->log_error(TCERR_INVAL_NEW_EXPR);
break;
}
}
/*
* Generate code to make a function call to this expression. If we're
* calling a function, we can generate this directly.
*/
void CTPNConst::gen_code_call(int discard, int argc, int varargs)
{
/* check our type */
switch(val_.get_type())
{
case TC_CVT_FUNCPTR:
/* generate a call to our function symbol */
val_.get_val_funcptr_sym()->gen_code_call(discard, argc, varargs);
break;
default:
/* other types cannot be called */
G_tok->log_error(TCERR_CANNOT_CALL_CONST);
break;
}
}
/*
* generate a property ID expression
*/
vm_prop_id_t CTPNConst::gen_code_propid(int check_only, int is_expr)
{
/* check the type */
switch(val_.get_type())
{
case TC_CVT_PROP:
/* return the constant property ID */
return (vm_prop_id_t)val_.get_val_prop();
default:
/* other values cannot be used as properties */
if (!check_only)
G_tok->log_error(TCERR_INVAL_PROP_EXPR);
return VM_INVALID_PROP;
}
}
/*
* Generate code for a member evaluation
*/
void CTPNConst::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
/* check our constant type */
switch(val_.get_type())
{
case TC_CVT_OBJ:
/* call the object symbol code to do the work */
CTcSymObj::s_gen_code_member(discard, prop_expr, prop_is_expr,
argc, val_.get_val_obj(), varargs);
break;
case TC_CVT_LIST:
case TC_CVT_SSTR:
case TC_CVT_FLOAT:
/*
* list/string/BigNumber constant - generate our value as
* normal, then use the standard member generation
*/
gen_code(FALSE, FALSE);
/* if we have an argument counter, put it back on top */
if (varargs)
G_cg->write_op(OPC_SWAP);
/* use standard member generation */
CTcPrsNode::s_gen_member_rhs(discard, prop_expr, prop_is_expr,
argc, varargs);
break;
default:
G_tok->log_error(TCERR_INVAL_OBJ_EXPR);
break;
}
}
/*
* generate code for an object before a '.'
*/
vm_obj_id_t CTPNConst::gen_code_obj_predot(int *is_self)
{
/* we're certainly not "self" */
*is_self = FALSE;
/* if I don't have an object value, this is illegal */
if (val_.get_type() != TC_CVT_OBJ)
{
G_tok->log_error(TCERR_INVAL_OBJ_EXPR);
return VM_INVALID_OBJ;
}
/* report our constant object value */
return val_.get_val_obj();
}
/* ------------------------------------------------------------------------ */
/*
* debugger constant
*/
void CTPNDebugConst::gen_code(int discard, int for_condition)
{
/* if we're discarding the value, do nothing */
if (discard)
return;
/* generate the appropriate type of push for the value */
switch(val_.get_type())
{
case TC_CVT_SSTR:
/* write the in-line string instruction */
G_cg->write_op(OPC_PUSHSTRI);
G_cs->write2(val_.get_val_str_len());
G_cs->write(val_.get_val_str(), val_.get_val_str_len());
/* note the value push */
G_cg->note_push();
break;
case TC_CVT_LIST:
/* we should never have a constant list when debugging */
assert(FALSE);
break;
case TC_CVT_FUNCPTR:
/* generate the function pointer instruction */
G_cg->write_op(OPC_PUSHFNPTR);
/*
* write the actual function address - no need for fixups in the
* debugger, since everything's fully resolved
*/
G_cs->write4(val_.get_val_funcptr_sym()->get_code_pool_addr());
/* note the value push */
G_cg->note_push();
break;
case TC_CVT_ANONFUNCPTR:
/*
* we should never see an anonymous function pointer in the
* debugger
*/
assert(FALSE);
break;
case TC_CVT_FLOAT:
{
CTcSymMetaclass *sym;
/*
* find the 'BigNumber' metaclass - if it's not defined, we
* can't create BigNumber values
*/
sym = (CTcSymMetaclass *)G_prs->get_global_symtab()
->find("BigNumber", 9);
if (sym == 0 || sym->get_type() != TC_SYM_METACLASS)
err_throw(VMERR_INVAL_DBG_EXPR);
/* push the floating value as an immediate string */
G_cg->write_op(OPC_PUSHSTRI);
G_cs->write2(val_.get_val_str_len());
G_cs->write(val_.get_val_str(), val_.get_val_str_len());
/* create the new BigNumber object from the string */
G_cg->write_op(OPC_NEW2);
G_cs->write2(1);
G_cs->write2(sym->get_meta_idx());
/* retrieve the value */
G_cg->write_op(OPC_GETR0);
/*
* note the net push of one value (we pushed the argument,
* popped the argument, and pushed the new object)
*/
G_cg->note_push();
}
break;
default:
/* handle normally for anything else */
CTPNConst::gen_code(discard, for_condition);
break;
}
}
/* ------------------------------------------------------------------------ */
/*
* Generic Unary Operator
*/
/*
* Generate a unary-operator opcode. We assume that the opcode has no
* side effects other than to compute the result, so we do not generate
* the opcode at all if 'discard' is true; we do, however, always
* generate code for the subexpression to ensure that its side effects
* are performed.
*
* In most cases, the caller simply should pass through its 'discard'
* status, since the result of the subexpression is generally needed
* only when the result of the enclosing expression is needed.
*
* In most cases, the caller should pass FALSE for 'for_condition',
* because applying an operator to the result generally requires that
* the result be properly converted for use as a temporary value.
* However, when the caller knows that its own opcode will perform the
* same conversions that a conditional opcode would, 'for_condition'
* should be TRUE. In most cases, the caller's own 'for_condition'
* status is not relevant and should thus not be passed through.
*/
void CTPNUnary::gen_unary(uchar opc, int discard, int for_condition)
{
/*
* Generate the operand. Pass through the 'discard' status to the
* operand - if the result of the parent operator is being
* discarded, then so is the result of this subexpression. In
* addition, pass through the caller's 'for_condition' disposition.
*/
sub_->gen_code(discard, for_condition);
/* apply the operator if we're not discarding the result */
if (!discard)
G_cg->write_op(opc);
}
/* ------------------------------------------------------------------------ */
/*
* Generic Binary Operator
*/
/*
* Generate a binary-operator opcode.
*
* In most cases, the caller's 'discard' status should be passed
* through, since the results of the operands are usually needed if and
* only if the results of the enclosing expression are needed.
*
* In most cases, the caller should pass FALSE for 'for_condition'.
* Only when the caller knows that the opcode will perform the same
* conversions as a BOOLIZE instruction should it pass TRUE for
* 'for_condition'.
*/
void CTPNBin::gen_binary(uchar opc, int discard, int for_condition)
{
/*
* generate the operands, passing through the discard and
* conditional status
*/
left_->gen_code(discard, for_condition);
right_->gen_code(discard, for_condition);
/* generate our operand if we're not discarding the result */
if (!discard)
{
/* apply the operator */
G_cg->write_op(opc);
/*
* boolean operators all remove two values and push one, so
* there's a net pop
*/
G_cg->note_pop();
}
}
/* ------------------------------------------------------------------------ */
/*
* logical NOT
*/
void CTPNNot::gen_code(int discard, int)
{
/*
* Generate the subexpression and apply the NOT opcode. Note that
* we can compute the subexpression as though we were applying a
* condition, because the NOT opcode takes exactly the same kind of
* input as any condition opcode; we can thus avoid an extra
* conversion in some cases.
*/
gen_unary(OPC_NOT, discard, TRUE);
}
/* ------------------------------------------------------------------------ */
/*
* Boolean-ize operator
*/
void CTPNBoolize::gen_code(int discard, int for_condition)
{
/*
* If the result will be used for a conditional, there's no need to
* generate an instruction to convert the value to boolean. The opcode
* that will be used for the condition will perform exactly the same
* conversions that this opcode would apply; avoid the redundant work
* in this case, and simply generate the underlying expression
* directly.
*/
if (for_condition)
{
/* generate the underlying expression without modification */
sub_->gen_code(discard, for_condition);
/* done */
return;
}
/*
* Generate the subexpression and apply the BOOLIZE operator. Since
* we're explicitly boolean-izing the value, there's no need for the
* subexpression to do the same thing, so the subexpression can
* pretend it's generating for a conditional.
*/
gen_unary(OPC_BOOLIZE, discard, TRUE);
}
/* ------------------------------------------------------------------------ */
/*
* bitwise NOT
*/
void CTPNBNot::gen_code(int discard, int)
{
gen_unary(OPC_BNOT, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* arithmetic positive
*/
void CTPNPos::gen_code(int discard, int)
{
/*
* simply generate our operand, since the operator itself has no
* effect
*/
sub_->gen_code(discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* unary arithmetic negative
*/
void CTPNNeg::gen_code(int discard, int)
{
gen_unary(OPC_NEG, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* pre-increment
*/
void CTPNPreInc::gen_code(int discard, int)
{
/* ask the subnode to generate it */
if (!sub_->gen_code_asi(discard, TC_ASI_PREINC, 0, FALSE))
{
/*
* the subnode didn't handle it - generate code to evaluate the
* subnode, increment that value, then assign the result back to
* the subnode with a simple assignment
*/
sub_->gen_code(FALSE, FALSE);
/* increment the value at top of stack */
G_cg->write_op(OPC_INC);
/*
* generate a simple assignment back to the subexpression; if
* we're using the value, let the simple assignment leave its
* value on the stack, since the result is the value *after* the
* increment
*/
sub_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* pre-decrement
*/
void CTPNPreDec::gen_code(int discard, int)
{
/* ask the subnode to generate it */
if (!sub_->gen_code_asi(discard, TC_ASI_PREDEC, 0, FALSE))
{
/*
* the subnode didn't handle it - generate code to evaluate the
* subnode, decrement that value, then assign the result back to
* the subnode with a simple assignment
*/
sub_->gen_code(FALSE, FALSE);
/* decrement the value at top of stack */
G_cg->write_op(OPC_DEC);
/*
* generate a simple assignment back to the subexpression; if
* we're using the value, let the simple assignment leave its
* value on the stack, since the result is the value *after* the
* decrement
*/
sub_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* post-increment
*/
void CTPNPostInc::gen_code(int discard, int)
{
/* ask the subnode to generate it */
if (!sub_->gen_code_asi(discard, TC_ASI_POSTINC, 0, FALSE))
{
/*
* the subnode didn't handle it - generate code to evaluate the
* subnode, increment that value, then assign the result back to
* the subnode with a simple assignment
*/
sub_->gen_code(FALSE, FALSE);
/*
* if we're keeping the result, duplicate the value at top of
* stack prior to the increment - since this is a
* post-increment, the result is the value *before* the
* increment
*/
if (!discard)
{
G_cg->write_op(OPC_DUP);
G_cg->note_push();
}
/* increment the value at top of stack */
G_cg->write_op(OPC_INC);
/*
* Generate a simple assignment back to the subexpression.
* Discard the result of this assignment, regardless of whether
* the caller wants the result of the overall expression,
* because we've already pushed the actual result, which is the
* original value before the increment operation.
*/
sub_->gen_code_asi(TRUE, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* post-decrement
*/
void CTPNPostDec::gen_code(int discard, int)
{
/* ask the subnode to generate it */
if (!sub_->gen_code_asi(discard, TC_ASI_POSTDEC, 0, FALSE))
{
/*
* the subnode didn't handle it - generate code to evaluate the
* subnode, decrement that value, then assign the result back to
* the subnode with a simple assignment
*/
sub_->gen_code(FALSE, FALSE);
/*
* if we're keeping the result, duplicate the value at top of
* stack prior to the decrement - since this is a
* post-decrement, the result is the value *before* the
* decrement
*/
if (!discard)
{
G_cg->write_op(OPC_DUP);
G_cg->note_push();
}
/* decrement the value at top of stack */
G_cg->write_op(OPC_DEC);
/*
* Generate a simple assignment back to the subexpression.
* Discard the result of this assignment, regardless of whether
* the caller wants the result of the overall expression,
* because we've already pushed the actual result, which is the
* original value before the decrement operation.
*/
sub_->gen_code_asi(TRUE, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* operator 'new'
*/
void CTPNNew::gen_code(int discard, int /*for condition*/)
{
/*
* ask my subexpression to generate the code - at this point we
* don't know the number of arguments, so pass in zero for now
*/
sub_->gen_code_new(discard, 0, FALSE, FALSE, transient_);
}
/* ------------------------------------------------------------------------ */
/*
* operator 'delete'
*/
void CTPNDelete::gen_code(int, int)
{
/* 'delete' generates no code for T3 VM */
}
/* ------------------------------------------------------------------------ */
/*
* comma operator
*/
void CTPNComma::gen_code(int discard, int for_condition)
{
/*
* Generate each side's code. Note that the left side is *always*
* discarded, regardless of whether the result of the comma operator
* will be discarded. After we generate our subexpressions, there's
* nothing left to do, since the comma operator itself doesn't
* change anything - we simply use the right operand result as our
* result.
*
* Pass through the 'for_condition' status to the right operand,
* since we pass through its result to the caller. For the left
* operand, treat it as a condition - we don't care about the result
* value, so don't bother performing any extra conversions on it.
*/
left_->gen_code(TRUE, TRUE);
right_->gen_code(discard, for_condition);
}
/* ------------------------------------------------------------------------ */
/*
* logical OR (short-circuit logic)
*/
void CTPNOr::gen_code(int discard, int for_condition)
{
CTcCodeLabel *lbl;
/*
* First, evaluate the left-hand side; we need the result even if
* we're discarding the overall expression, since we will check the
* result to see if we should even evaluate the right-hand side.
* We're using the value for a condition, so don't bother
* boolean-izing it.
*/
left_->gen_code(FALSE, TRUE);
/*
* If the left-hand side is true, there's no need to evaluate the
* right-hand side (and, in fact, we're not even allowed to evaluate
* the right-hand side because of the short-circuit logic rule).
* So, if the lhs is true, we want to jump around the code to
* evaluate the rhs, saving the 'true' result if we're not
* discarding the overall result.
*/
lbl = gen_jump_ahead(discard ? OPC_JT : OPC_JST);
/*
* Evaluate the right-hand side. We don't need to save the result
* unless we need the result of the overall expression. Generate
* the value as though we were going to booleanize it ourselves,
* since we'll do just that (hence pass for_condition = TRUE).
*/
right_->gen_code(discard, TRUE);
/*
* If we discarded the result, we generated a JT which explicitly
* popped a value. If we didn't discard the result, we generated a
* JST; this may or may not pop the value. However, if it doesn't
* pop the value (save on true), it will bypass the right side
* evaluation, and will thus "pop" that value in the sense that it
* will never be pushed. So, note a pop either way.
*/
G_cg->note_pop();
/* define the label for the jump over the rhs */
def_label_pos(lbl);
/*
* if the result is not going to be used directly for a condition,
* we must boolean-ize the value
*/
if (!for_condition)
G_cg->write_op(OPC_BOOLIZE);
}
/*
* Generate code for the short-circuit OR when used in a condition. We can
* use the fact that we're being used conditionally to avoid actually
* pushing the result value onto the stack, instead simply branching to the
* appropriate point in the enclosing control structure instead.
*/
void CTPNOr::gen_code_cond(CTcCodeLabel *then_label,
CTcCodeLabel *else_label)
{
CTcCodeLabel *internal_then;
/*
* First, generate the conditional code for our left operand. If the
* condition is true, we can short-circuit the rest of the expression
* by jumping directly to the 'then' label. If the caller provided a
* 'then' label, we can jump directly to the caller's 'then' label;
* otherwise, we must synthesize our own internal label, which we'll
* define at the end of our generated code so that we'll fall through
* on true to the enclosing code. In any case, we want to fall through
* if the condition is false, so that control will flow to the code for
* our right operand if the left operand is false.
*/
internal_then = (then_label == 0 ? G_cs->new_label_fwd() : then_label);
left_->gen_code_cond(internal_then, 0);
/*
* Now, generate code for our right operand. We can generate this code
* using the caller's destination labels directly: if we reach this
* code at all, it's because the left operand was false, in which case
* the result is simply the value of the right operand.
*/
right_->gen_code_cond(then_label, else_label);
/*
* If we created an internal 'then' label, it goes at the end of our
* generated code: this ensures that we fall off the end of our code
* if the left subexpression is true, which is what the caller told us
* they wanted when they gave us a null 'then' label. If the caller
* gave us an explicit 'then' label, we'll have jumped there directly
* if the first subexpression was true.
*/
if (then_label == 0)
def_label_pos(internal_then);
}
/* ------------------------------------------------------------------------ */
/*
* logical AND (short-circuit logic)
*/
void CTPNAnd::gen_code(int discard, int for_condition)
{
CTcCodeLabel *lbl;
/*
* first, evaluate the left-hand side; we need the result even if
* we're discarding the overall expression, since we will check the
* result to see if we should even evaluate the right-hand side
*/
left_->gen_code(FALSE, TRUE);
/*
* If the left-hand side is false, there's no need to evaluate the
* right-hand side (and, in fact, we're not even allowed to evaluate
* the right-hand side because of the short-circuit logic rule).
* So, if the lhs is false, we want to jump around the code to
* evaluate the rhs, saving the false result if we're not discarding
* the overall result.
*/
lbl = gen_jump_ahead(discard ? OPC_JF : OPC_JSF);
/*
* Evaluate the right-hand side. We don't need to save the result
* unless we need the result of the overall expression.
*/
right_->gen_code(discard, TRUE);
/* define the label for the jump over the rhs */
def_label_pos(lbl);
/*
* If we discarded the result, we generated a JF which explicitly
* popped a value. If we didn't discard the result, we generated a
* JSF; this may or may not pop the value. However, if it doesn't
* pop the value (save on false), it will bypass the right side
* evaluation, and will thus "pop" that value in the sense that it
* will never be pushed. So, note a pop either way.
*/
G_cg->note_pop();
/*
* if the result is not going to be used directly for a condition,
* we must boolean-ize the value
*/
if (!for_condition)
G_cg->write_op(OPC_BOOLIZE);
}
/*
* Generate code for the short-circuit AND when used in a condition. We
* can use the fact that we're being used conditionally to avoid actually
* pushing the result value onto the stack, instead simply branching to the
* appropriate point in the enclosing control structure instead.
*/
void CTPNAnd::gen_code_cond(CTcCodeLabel *then_label,
CTcCodeLabel *else_label)
{
CTcCodeLabel *internal_else;
/*
* First, generate the conditional code for our left operand. If the
* condition is false, we can short-circuit the rest of the expression
* by jumping directly to the 'else' label. If the caller provided an
* 'else' label, we can jump directly to the caller's 'else' label;
* otherwise, we must synthesize our own internal label, which we'll
* define at the end of our generated code so that we'll fall through
* on false to the enclosing code. In any case, we want to fall
* through if the condition is true, so that control will flow to the
* code for our right operand if the left operand is true.
*/
internal_else = (else_label == 0 ? G_cs->new_label_fwd() : else_label);
left_->gen_code_cond(0, internal_else);
/*
* Now, generate code for our right operand. We can generate this code
* using the caller's destination labels directly: if we reach this
* code at all, it's because the left operand was true, in which case
* the result is simply the value of the right operand.
*/
right_->gen_code_cond(then_label, else_label);
/*
* If we created an internal 'else' label, it goes at the end of our
* generated code: this ensures that we fall off the end of our code
* if the left subexpression is false, which is what the caller told
* us they wanted when they gave us a null 'else' label. If the
* caller gave us an explicit 'else' label, we'll have jumped there
* directly if the first subexpression was false.
*/
if (else_label == 0)
def_label_pos(internal_else);
}
/* ------------------------------------------------------------------------ */
/*
* bitwise OR
*/
void CTPNBOr::gen_code(int discard, int)
{
gen_binary(OPC_BOR, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* bitwise AND
*/
void CTPNBAnd::gen_code(int discard, int)
{
gen_binary(OPC_BAND, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* bitwise XOR
*/
void CTPNBXor::gen_code(int discard, int)
{
gen_binary(OPC_XOR, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* greater-than
*/
void CTPNGt::gen_code(int discard, int)
{
gen_binary(OPC_GT, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* greater-or-equal
*/
void CTPNGe::gen_code(int discard, int)
{
gen_binary(OPC_GE, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* less-than
*/
void CTPNLt::gen_code(int discard, int)
{
gen_binary(OPC_LT, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* less-or-equal
*/
void CTPNLe::gen_code(int discard, int)
{
gen_binary(OPC_LE, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* compare for equality
*/
void CTPNEq::gen_code(int discard, int)
{
gen_binary(OPC_EQ, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* compare for inequality
*/
void CTPNNe::gen_code(int discard, int)
{
gen_binary(OPC_NE, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* 'is in'
*/
void CTPNIsIn::gen_code(int discard, int)
{
CTPNArglist *lst;
CTPNArg *arg;
CTcCodeLabel *lbl_found;
CTcCodeLabel *lbl_done;
/* allocate our 'found' label */
lbl_found = G_cs->new_label_fwd();
/*
* allocate our 'done' label - we only need to do this if we don't
* have a constant true value and we're not discarding the result
*/
if (!const_true_ && !discard)
lbl_done = G_cs->new_label_fwd();
/* generate my left-side expression */
left_->gen_code(FALSE, FALSE);
/* the right side is always an argument list */
lst = (CTPNArglist *)right_;
/* compare to each element in the list on the right */
for (arg = lst->get_arg_list_head() ; arg != 0 ;
arg = arg->get_next_arg())
{
/*
* duplicate the left-side value, so we don't have to generate
* it again for this comparison
*/
G_cg->write_op(OPC_DUP);
/* generate this list element */
arg->gen_code(FALSE, FALSE);
/* if they're equal, jump to the 'found' label */
G_cg->write_op(OPC_JE);
G_cs->write_ofs2(lbl_found, 0);
/* we pushed one more (DUP) and popped two (JE) */
G_cg->note_push(1);
G_cg->note_pop(2);
}
/*
* Generate the code that comes at the end of all of tests when we
* fail to find any matches - we simply discard the left-side value
* from the stack, push our 'nil' value, and jump to the end label.
*
* If we have a constant 'true' value, there's no need to do any of
* this, because we know that, even after testing all of our
* non-constant values, there's a constant value that makes the
* entire expression true, and we can thus just fall through to the
* 'found' code.
*
* If we're discarding the result, there's no need to push a
* separate value for the result, so we can just fall through to the
* common ending code in this case.
*/
if (!const_true_ && !discard)
{
G_cg->write_op(OPC_DISC);
G_cg->write_op(OPC_PUSHNIL);
G_cg->write_op(OPC_JMP);
G_cs->write_ofs2(lbl_done, 0);
}
/*
* Generate the 'found' code - this discards the left-side value and
* pushes our 'true' result. Note that there's no reason to push
* our result if we're discarding it.
*/
def_label_pos(lbl_found);
G_cg->write_op(OPC_DISC);
/*
* if we're discarding the result, just note the pop of the left
* value; otherwise, push our result
*/
if (discard)
G_cg->note_pop();
else
G_cg->write_op(OPC_PUSHTRUE);
/* our 'done' label is here, if we needed one */
if (!const_true_ && !discard)
def_label_pos(lbl_done);
}
/* ------------------------------------------------------------------------ */
/*
* 'not in'
*/
void CTPNNotIn::gen_code(int discard, int)
{
CTPNArglist *lst;
CTPNArg *arg;
CTcCodeLabel *lbl_found;
CTcCodeLabel *lbl_done;
/* allocate our 'found' label */
lbl_found = G_cs->new_label_fwd();
/*
* allocate our 'done' label - we only need to do this if we don't
* have a constant false value
*/
if (!const_false_ && !discard)
lbl_done = G_cs->new_label_fwd();
/* generate my left-side expression */
left_->gen_code(FALSE, FALSE);
/* the right side is always an argument list */
lst = (CTPNArglist *)right_;
/* compare to each element in the list on the right */
for (arg = lst->get_arg_list_head() ; arg != 0 ;
arg = arg->get_next_arg())
{
/*
* duplicate the left-side value, so we don't have to generate
* it again for this comparison
*/
G_cg->write_op(OPC_DUP);
/* generate this list element */
arg->gen_code(FALSE, FALSE);
/* if they're equal, jump to the 'found' label */
G_cg->write_op(OPC_JE);
G_cs->write_ofs2(lbl_found, 0);
/* we pushed one more (DUP) and popped two (JE) */
G_cg->note_push(1);
G_cg->note_pop(2);
}
/*
* Generate the code that comes at the end of all of tests when we
* fail to find any matches - we simply discard the left-side value
* from the stack, push our 'true' value, and jump to the end label.
*
* If we have a constant 'nil' value, however, there's no need to do
* any of this, because we know that, even after testing all of our
* non-constant values, there's a matching constant value that makes
* the entire expression false (because 'not in' is false if we find
* a match), and we can thus just fall through to the 'found' code.
*/
if (!const_false_ && !discard)
{
G_cg->write_op(OPC_DISC);
G_cg->write_op(OPC_PUSHTRUE);
G_cg->write_op(OPC_JMP);
G_cs->write_ofs2(lbl_done, 0);
}
/*
* generate the 'found' code - this discards the left-side value and
* pushes our 'nil' result (because the result of 'not in' is false
* if we found the value)
*/
def_label_pos(lbl_found);
G_cg->write_op(OPC_DISC);
/* push the result, or note the pop if we're just discarding it */
if (discard)
G_cg->note_pop();
else
G_cg->write_op(OPC_PUSHNIL);
/* our 'done' label is here, if we needed one */
if (!const_false_ && !discard)
def_label_pos(lbl_done);
}
/* ------------------------------------------------------------------------ */
/*
* bit-shift left
*/
void CTPNShl::gen_code(int discard, int)
{
gen_binary(OPC_SHL, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* bit-shift right
*/
void CTPNShr::gen_code(int discard, int)
{
gen_binary(OPC_SHR, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* multiply
*/
void CTPNMul::gen_code(int discard, int)
{
/* if either side is zero or one, we can apply special handling */
if (left_->is_const_int(0))
{
/* evaluate the right for side effects and discard the result */
right_->gen_code(TRUE, TRUE);
/* the result is zero */
G_cg->write_op(OPC_PUSH_0);
G_cg->note_push();
/* done */
return;
}
else if (right_->is_const_int(0))
{
/* evaluate the left for side effects and discard the result */
left_->gen_code(TRUE, TRUE);
/* the result is zero */
G_cg->write_op(OPC_PUSH_0);
G_cg->note_push();
/* done */
return;
}
else if (left_->is_const_int(1))
{
/*
* evaluate the right side - it's the result; note that, because
* of the explicit multiplication, we must compute logical
* results using assignment (not 'for condition') rules
*/
right_->gen_code(discard, FALSE);
/* done */
return;
}
else if (right_->is_const_int(1))
{
/* evaluate the right side - it's the result */
left_->gen_code(discard, FALSE);
/* done */
return;
}
/* apply generic handling */
gen_binary(OPC_MUL, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* divide
*/
void CTPNDiv::gen_code(int discard, int for_cond)
{
/* if dividing by 1, we can skip the whole thing (except side effects) */
if (right_->is_const_int(1))
{
/*
* simply generate the left side for side effects; actually
* doing the arithmetic has no effect
*/
left_->gen_code(discard, for_cond);
return;
}
/* if the left side is zero, the result is always zero */
if (left_->is_const_int(0))
{
/* evaluate the right for side effects, but discard the result */
right_->gen_code(TRUE, TRUE);
/* the result is zero */
G_cg->write_op(OPC_PUSH_0);
G_cg->note_push();
return;
}
/* use generic code generation */
gen_binary(OPC_DIV, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* modulo
*/
void CTPNMod::gen_code(int discard, int for_condition)
{
/* if dividing by 1, we can skip the whole thing (except side effects) */
if (right_->is_const_int(1))
{
/*
* simply generate the left side for side effects; actually
* doing the arithmetic has no effect
*/
left_->gen_code(discard, for_condition);
/* the result is zero */
G_cg->write_op(OPC_PUSH_0);
G_cg->note_push();
return;
}
/* if the left side is zero, the result is always zero */
if (left_->is_const_int(0))
{
/* evaluate the right for side effects, but discard the result */
right_->gen_code(TRUE, TRUE);
/* the result is zero */
G_cg->write_op(OPC_PUSH_0);
G_cg->note_push();
return;
}
/* use generic processing */
gen_binary(OPC_MOD, discard, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* subtract
*/
void CTPNSub::gen_code(int discard, int for_cond)
{
/* check for subtracting 1, which we can accomplish more efficiently */
if (right_->is_const_int(1))
{
/*
* We're subtracting one - use decrement. The decrement
* operator itself has no side effects, so we can pass through
* the 'discard' status to the subnode.
*/
left_->gen_code(discard, FALSE);
/* apply decrement if we're not discarding the result */
if (!discard)
G_cg->write_op(OPC_DEC);
}
else
{
/* we can't do anything special - use the general-purpose code */
gen_binary(OPC_SUB, discard, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* add
*/
void CTPNAdd::gen_code(int discard, int)
{
/* check for adding 1, which we can accomplish more efficiently */
if (right_->is_const_int(1))
{
/*
* We're adding one - use increment. The increment operator
* itself has no side effects, so we can pass through the
* 'discard' status to the subnode.
*/
left_->gen_code(discard, FALSE);
/* apply increment if we're not discarding the result */
if (!discard)
G_cg->write_op(OPC_INC);
}
else
{
/* we can't do anything special - use the general-purpose code */
gen_binary(OPC_ADD, discard, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* simple assignment
*/
void CTPNAsi::gen_code(int discard, int)
{
/*
* Ask the left subnode to generate a simple assignment to the value
* on the right. Simple assignments cannot be refused, so we don't
* need to try to do any assignment work ourselves.
*/
left_->gen_code_asi(discard, TC_ASI_SIMPLE, right_, FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* add and assign
*/
void CTPNAddAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate an add-and-assign; if it can't,
* handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_ADD, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_ADD, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* subtract and assign
*/
void CTPNSubAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate a subtract-and-assign; if it
* can't, handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_SUB, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_SUB, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* multiply and assign
*/
void CTPNMulAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate a multiply-and-assign; if it
* can't, handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_MUL, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_MUL, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* divide and assign
*/
void CTPNDivAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate a divide-and-assign; if it
* can't, handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_DIV, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_DIV, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* modulo and assign
*/
void CTPNModAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate a mod-and-assign; if it can't,
* handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_MOD, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_MOD, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* bitwise-AND and assign
*/
void CTPNBAndAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate an AND-and-assign; if it can't,
* handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_BAND, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_BAND, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* bitwise-OR and assign
*/
void CTPNBOrAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate an OR-and-assign; if it can't,
* handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_BOR, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_BOR, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* bitwise-XOR and assign
*/
void CTPNBXorAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate an XOR-and-assign; if it can't,
* handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_BXOR, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_XOR, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* bit-shift left and assign
*/
void CTPNShlAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate an shift-left-and-assign; if it
* can't, handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_SHL, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_SHL, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* bit-shift right and assign
*/
void CTPNShrAsi::gen_code(int discard, int)
{
/*
* ask the left subnode to generate a shift-right-and-assign; if it
* can't, handle it generically
*/
if (!left_->gen_code_asi(discard, TC_ASI_SHR, right_, FALSE))
{
/*
* there's no special coding for this assignment type -- compute
* the result generically, then assign the result as a simple
* assignment, which cannot be refused
*/
gen_binary(OPC_SHR, FALSE, FALSE);
left_->gen_code_asi(discard, TC_ASI_SIMPLE, 0, FALSE);
}
}
/* ------------------------------------------------------------------------ */
/*
* subscript a list/array value
*/
void CTPNSubscript::gen_code(int discard, int)
{
gen_binary(OPC_INDEX, discard, FALSE);
}
/*
* assign to a subscripted value
*/
int CTPNSubscript::gen_code_asi(int discard, tc_asitype_t typ,
CTcPrsNode *rhs, int)
{
/*
* If this isn't a simple assignment, tell the caller to emit the
* generic code to compute the composite result, then call us again
* for a simple assignment. We can't add any value with specialized
* instructions for composite assignments, so there's no point in
* dealing with those here.
*/
if (typ != TC_ASI_SIMPLE)
return FALSE;
/*
* Generate the value to assign to the element - that's the right
* side of the assignment operator. If rhs is null, it means the
* caller has already done this.
*/
if (rhs != 0)
rhs->gen_code(FALSE, FALSE);
/*
* if we're not discarding the result, duplicate the value to be
* assigned, so that it's left on the stack after we're finished
* (this is necessary because we'll consume one copy with the SETIND
* instruction)
*/
if (!discard)
{
G_cg->write_op(OPC_DUP);
G_cg->note_push();
}
/* generate the value to be subscripted - that's my left-hand side */
left_->gen_code(FALSE, FALSE);
/* generate the index value - that's my right-hand side */
right_->gen_code(FALSE, FALSE);
/* generate the assign-to-indexed-value opcode */
G_cg->write_op(OPC_SETIND);
/* setind pops three and pushes one - net of pop 2 */
G_cg->note_pop(2);
/*
* The top value now on the stack is the new container value. The new
* container will be different from the old container in some cases
* (with lists, for example, because we must create a new list object
* to contain the modified list value). Therefore, if my left-hand
* side is an lvalue, we must assign the new container to the left-hand
* side - this makes something like "x[1] = 5" actually change the
* value in "x" if "x" is a local variable. If my left-hand side isn't
* an lvalue, don't bother with this step, and simply discard the new
* container value.
*
* Regardless of whether we're keeping the result of the overall
* expression, we're definitely not keeping the result of assigning the
* new container - the result of the assignment is the value assigned,
* not the container. Thus, discard = true in this call.
*
* There's a special case that's handled through the peep-hole
* optimizer: if we are assigning to a local variable and indexing with
* a constant integer value, we will have converted the whole operation
* to a SETINDLCL1I8. That instruction takes care of assigning the
* value back to the rvalue, so we don't need to generate a separate
* rvalue assignment.
*/
if (G_cg->get_last_op() == OPC_SETINDLCL1I8)
{
/*
* no assignment is necessary - we just need to account for the
* difference in the stack arrangement with this form of the
* assignment, which is that we don't leave the value on the stack
*/
G_cg->note_pop();
}
else if (!left_->gen_code_asi(TRUE, TC_ASI_SIMPLE, 0, TRUE))
{
/* no assignment is possible; discard the new container value */
G_cg->write_op(OPC_DISC);
G_cg->note_pop();
}
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* conditional operator
*/
void CTPNIf::gen_code(int discard, int for_condition)
{
CTcCodeLabel *lbl_else;
CTcCodeLabel *lbl_end;
/*
* Generate the condition value - we need the value regardless of
* whether the overall result is going to be used, because we need
* it to determine which branch to take. Generate the subexpression
* for a condition, so that we don't perform any extra unnecessary
* conversions on it.
*/
first_->gen_code(FALSE, TRUE);
/* if the condition is false, jump to the 'else' expression part */
lbl_else = gen_jump_ahead(OPC_JF);
/* JF pops a value */
G_cg->note_pop();
/*
* Generate the 'then' expression part. Only request a return value if
* it has one AND we're not discarding it. If it doesn't return a
* value, and we actually need one, we'll supply a default 'nil' value
* next. This value will be our yielded value (in this branch,
* anyway), so pass through the for-condition flag.
*/
second_->gen_code(discard || !second_->has_return_value(), for_condition);
/*
* If this expression has no return value, and we need the return
* value, supply nil as the result.
*/
if (!discard && !second_->has_return_value())
{
G_cg->write_op(OPC_PUSHNIL);
G_cg->note_push();
}
/* unconditionally jump over the 'else' part */
lbl_end = gen_jump_ahead(OPC_JMP);
/* set the label for the 'else' part */
def_label_pos(lbl_else);
/*
* Generate the 'else' part. Only request a return value if it has one
* AND we're not discarding it. Pass through 'discard' and
* 'for_condition', since this result is our result.
*/
third_->gen_code(discard || !third_->has_return_value(), for_condition);
/*
* If this expression has no return value, and we need the return
* value, supply nil as the result.
*/
if (!discard && !third_->has_return_value())
{
G_cg->write_op(OPC_PUSHNIL);
G_cg->note_push();
}
/*
* Because of the jump, we only evaluate one of the two expressions
* we generated, so note an extra pop for the branch we didn't take.
* Note that if either one pushes a value, both will, since we'll
* explicitly have pushed nil for the one that doesn't generate a
* value to keep the stack balanced on both branches.
*
* If neither of our expressions yields a value, don't pop anything
* extra, since we won't think we've pushed two values in the course
* of generating the two expressions.
*/
if (second_->has_return_value() || third_->has_return_value())
G_cg->note_pop();
/* set the label for the end of the expression */
def_label_pos(lbl_end);
}
/* ------------------------------------------------------------------------ */
/*
* symbol
*/
void CTPNSym::gen_code(int discard, int)
{
/*
* Look up the symbol; if it's undefined, add a default property
* symbol entry if possible. Then ask the symbol to generate the
* code.
*/
G_cs->get_symtab()
->find_or_def_prop_implied(get_sym_text(), get_sym_text_len(),
FALSE, G_cs->is_self_available())
->gen_code(discard);
}
/*
* assign to a symbol
*/
int CTPNSym::gen_code_asi(int discard, tc_asitype_t typ, CTcPrsNode *rhs,
int ignore_errors)
{
/*
* Look up the symbol; if it's undefined and there's a "self" object
* available, define it as a property by default, since a property
* is the only kind of symbol that we could possibly assign to
* without having defined anywhere in the program. Once we have the
* symbol, tell it to generate the code for assigning to it.
*/
return G_cs->get_symtab()
->find_or_def_prop_implied(get_sym_text(), get_sym_text_len(),
FALSE, G_cs->is_self_available())
->gen_code_asi(discard, typ, rhs, ignore_errors);
}
/*
* take the address of the symbol
*/
void CTPNSym::gen_code_addr()
{
/*
* Look up our symbol in the symbol table, then ask the resulting
* symbol to generate the appropriate code. If the symbol isn't
* defined, and we have a "self" object available (i.e., we're in
* method code), define the symbol by default as a property.
*
* Note that we look only in the global symbol table, because local
* symbols have no address value. So, even if the symbol is defined
* in the local table, ignore the local definition and look at the
* global definition.
*/
G_prs->get_global_symtab()
->find_or_def_prop_explicit(get_sym_text(), get_sym_text_len(),
FALSE)
->gen_code_addr();
}
/*
* call the symbol
*/
void CTPNSym::gen_code_call(int discard, int argc, int varargs)
{
/*
* Look up our symbol in the symbol table, then ask the resulting
* symbol to generate the appropriate call. The symbol is
* implicitly a property (if in a method context), since that's the
* only kind of undefined symbol that we could be calling.
*/
G_cs->get_symtab()
->find_or_def_prop_implied(get_sym_text(), get_sym_text_len(),
FALSE, G_cs->is_self_available())
->gen_code_call(discard, argc, varargs);
}
/*
* generate code for 'new'
*/
void CTPNSym::gen_code_new(int discard, int argc, int varargs,
int /*from_call*/, int is_transient)
{
/*
* Look up our symbol, then ask the resulting symbol to generate the
* 'new' code. If the symbol is undefined, add an 'undefined' entry
* to the table; we can't implicitly create an object symbol.
*/
G_cs->get_symtab()
->find_or_def_undef(get_sym_text(), get_sym_text_len(), FALSE)
->gen_code_new(discard, argc, varargs, is_transient);
}
/*
* generate a property ID expression
*/
vm_prop_id_t CTPNSym::gen_code_propid(int check_only, int is_expr)
{
CTcSymbol *sym;
CTcPrsSymtab *symtab;
/*
* Figure out where to look for the symbol. If the symbol was given
* as an expression (in other words, it was explicitly enclosed in
* parentheses), look it up in the local symbol table, since it
* could refer to a local. Otherwise, it must refer to a property,
* so look only in the global table.
*
* If the symbol isn't defined already, define it as a property now.
* Because the symbol is explicitly on the right side of a member
* evaluation, we can define it as a property whether or not there's
* a valid "self" in this context.
*/
if (is_expr)
{
/* it's an expression - look it up in the local symbol table */
symtab = G_cs->get_symtab();
}
else
{
/* it's a simple symbol - look only in the global symbol table */
symtab = G_prs->get_global_symtab();
}
/*
* look it up (note that this will always return a valid symbol,
* since it will create one if we can't find an existing entry)
*/
sym = symtab->find_or_def_prop(get_sym_text(), get_sym_text_len(), FALSE);
/* ask the symbol to generate the property reference */
return sym->gen_code_propid(check_only, is_expr);
}
/*
* generate code for a member expression
*/
void CTPNSym::gen_code_member(int discard, CTcPrsNode *prop_expr,
int prop_is_expr, int argc, int varargs)
{
/*
* Look up the symbol, and let it do the work. There's no
* appropriate default for the symbol, so leave it undefined if we
* can't find it.
*/
G_cs->get_symtab()
->find_or_def_undef(get_sym_text(), get_sym_text_len(), FALSE)
->gen_code_member(discard, prop_expr, prop_is_expr, argc, varargs);
}
/*
* generate code for an object before a '.'
*/
vm_obj_id_t CTPNSym::gen_code_obj_predot(int *is_self)
{
/*
* Look up the symbol, and let it do the work. There's no default
* type for the symbol, so leave it undefined if we don't find it.
*/
return G_cs->get_symtab()
->find_or_def_undef(get_sym_text(), get_sym_text_len(), FALSE)
->gen_code_obj_predot(is_self);
}
/* ------------------------------------------------------------------------ */
/*
* resolved symbol
*/
void CTPNSymResolved::gen_code(int discard, int)
{
/* let the symbol handle it */
sym_->gen_code(discard);
}
/*
* assign to a symbol
*/
int CTPNSymResolved::gen_code_asi(int discard, tc_asitype_t typ,
CTcPrsNode *rhs,
int ignore_errors)
{
/* let the symbol handle it */
return sym_->gen_code_asi(discard, typ, rhs, ignore_errors);
}
/*
* take the address of the symbol
*/
void CTPNSymResolved::gen_code_addr()
{
/* let the symbol handle it */
sym_->gen_code_addr();
}
/*
* call the symbol
*/
void CTPNSymResolved::gen_code_call(int discard, int argc, int varargs)
{
/* let the symbol handle it */
sym_->gen_code_call(discard, argc, varargs);
}
/*
* generate code for 'new'
*/
void CTPNSymResolved::gen_code_new(int discard, int argc, int varargs,
int /*from_call*/, int is_transient)
{
/* let the symbol handle it */
sym_->gen_code_new(discard, argc, varargs, is_transient);
}
/*
* generate a property ID expression
*/
vm_prop_id_t CTPNSymResolved::gen_code_propid(int check_only, int is_expr)
{
/* let the symbol handle it */
return sym_->gen_code_propid(check_only, is_expr);
}
/*
* generate code for a member expression
*/
void CTPNSymResolved::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
/* let the symbol handle it */
sym_->gen_code_member(discard, prop_expr, prop_is_expr, argc, varargs);
}
/*
* generate code for an object before a '.'
*/
vm_obj_id_t CTPNSymResolved::gen_code_obj_predot(int *is_self)
{
/* let the symbol handle it */
return sym_->gen_code_obj_predot(is_self);
}
/* ------------------------------------------------------------------------ */
/*
* Debugger local variable symbol
*/
/*
* generate code to evaluate the variable
*/
void CTPNSymDebugLocal::gen_code(int discard, int for_condition)
{
/* if we're not discarding the value, push the local */
if (!discard)
{
/* generate the debugger local/parameter variable instruction */
G_cg->write_op(is_param_ ? OPC_GETDBARG : OPC_GETDBLCL);
G_cs->write2(var_id_);
G_cs->write2(frame_idx_);
/* note that we pushed the value */
G_cg->note_push();
/* if it's a context local, get the value from the context array */
if (ctx_arr_idx_ != 0)
{
CTPNConst::s_gen_code_int(ctx_arr_idx_);
G_cg->write_op(OPC_INDEX);
/*
* the 'index' operation pops two values and pushes one, for a
* net of one pop
*/
G_cg->note_pop();
}
}
}
/*
* generate code for assigning to this variable
*/
int CTPNSymDebugLocal::gen_code_asi(int discard, tc_asitype_t typ,
CTcPrsNode *rhs, int ignore_error)
{
/*
* if this isn't a simple assignment, use the generic combination
* assignment computation
*/
if (typ != TC_ASI_SIMPLE)
return FALSE;
/* generate the value to be assigned */
if (rhs != 0)
rhs->gen_code(FALSE, FALSE);
/*
* if we're not discarding the result, duplicate the value so we'll
* have a copy after the assignment
*/
if (!discard)
{
G_cg->write_op(OPC_DUP);
G_cg->note_push();
}
/* check for a context property */
if (ctx_arr_idx_ == 0)
{
/*
* generate the debug-local-set instruction - the operands are
* the variable number and the stack frame index
*/
G_cg->write_op(is_param_ ? OPC_SETDBARG : OPC_SETDBLCL);
G_cs->write2(var_id_);
G_cs->write2(frame_idx_);
}
else
{
/* get the local containing our context object */
G_cg->write_op(OPC_GETDBLCL);
G_cs->write2(var_id_);
G_cs->write2(frame_idx_);
/* set the actual variable value in the context object */
CTPNConst::s_gen_code_int(ctx_arr_idx_);
G_cg->write_op(OPC_SETIND);
G_cg->write_op(OPC_DISC);
/*
* we did three pops (SETIND), then a push (SETIND), then a pop
* (DISC) - this is a net of three extra pops
*/
G_cg->note_pop(3);
}
/* the debug-local-set removes the rvalue from the stack */
G_cg->note_pop();
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Double-quoted string. The 'discard' status is irrelevant, because we
* evaluate double-quoted strings for their side effects.
*/
void CTPNDstr::gen_code(int discard, int)
{
/* if we're not discarding the value, it's an error */
if (!discard)
G_tok->log_error(TCERR_DQUOTE_IN_EXPR, (int)len_, str_);
/* generate the instruction to display it */
G_cg->write_op(OPC_SAY);
/* add the string to the constant pool, creating a fixup here */
G_cg->add_const_str(str_, len_, G_cs, G_cs->get_ofs());
/* write a placeholder value, which will be corrected by the fixup */
G_cs->write4(0);
}
/* ------------------------------------------------------------------------ */
/*
* Double-quoted debug string
*/
void CTPNDebugDstr::gen_code(int, int)
{
/* generate code to push the in-line string */
G_cg->write_op(OPC_PUSHSTRI);
G_cs->write2(len_);
G_cs->write(str_, len_);
/* write code to display the value */
G_cg->write_op(OPC_SAYVAL);
/* note that we pushed the string and then popped it */
G_cg->note_push();
G_cg->note_pop();
}
/* ------------------------------------------------------------------------ */
/*
* Double-quoted string embedding
*/
/*
* create an embedding
*/
CTPNDstrEmbed::CTPNDstrEmbed(CTcPrsNode *sub)
: CTPNDstrEmbedBase(sub)
{
}
/*
* Generate code for a double-quoted string embedding
*/
void CTPNDstrEmbed::gen_code(int, int)
{
int orig_depth;
/* note the stack depth before generating the expression */
orig_depth = G_cg->get_sp_depth();
/*
* Generate code for the embedded expression. If the expression has a
* return value, generate the value so that it can be displayed in the
* string; but don't request a value if it doesn't have one, as a
* return value is optional in this context. This is a normal value
* invocation, not a conditional, so we need any applicable normal
* value conversions.
*/
sub_->gen_code(!sub_->has_return_value(), FALSE);
/*
* If the code generation left anything on the stack, generate code
* to display the value via the default display function.
*/
if (G_cg->get_sp_depth() > orig_depth)
{
/* add a SAYVAL instruction */
G_cg->write_op(OPC_SAYVAL);
/* SAYVAL pops the argument value */
G_cg->note_pop();
}
}
/* ------------------------------------------------------------------------ */
/*
* Argument list
*/
void CTPNArglist::gen_code_arglist(int *varargs)
{
CTPNArg *arg;
int i;
int fixed_cnt;
int pushed_varargs_counter;
/*
* scan the argument list for varargs - if we have any, we must
* treat all of them as varargs
*/
for (*varargs = FALSE, fixed_cnt = 0, arg = get_arg_list_head() ;
arg != 0 ; arg = arg->get_next_arg())
{
/* if this is a varargs argument, we have varargs */
if (arg->is_varargs())
{
/* note it */
*varargs = TRUE;
}
else
{
/* count another fixed argument */
++fixed_cnt;
}
}
/*
* Push each argument in the list - start with the last element and
* work backwards through the list to the first element. The parser
* builds the list in reverse order, so we must merely follow the
* list from head to tail.
*
* We need each argument value to be pushed (hence discard = false),
* and we need the assignable value of each argument expression
* (hence for_condition = false).
*/
for (pushed_varargs_counter = FALSE, i = argc_,
arg = get_arg_list_head() ; arg != 0 ;
arg = arg->get_next_arg(), --i)
{
int depth;
/* note the stack depth before generating the value */
depth = G_cg->get_sp_depth();
/*
* check for varargs - if this is first varargs argument, push
* the counter placeholder
*/
if (arg->is_varargs() && !pushed_varargs_counter)
{
/*
* write code to push the fixed argument count - we can use
* this as a starting point, since we always know we have
* this many argument to start with; we'll dynamically add
* in the variable count at run-time
*/
CTPNConst::s_gen_code_int(fixed_cnt);
/* note that we've pushed the counter */
pushed_varargs_counter = TRUE;
/*
* we will take the extra value off when we evaluate the
* varargs counter, so simply count it as removed now
*/
G_cg->note_pop();
}
/* generate the argument's code */
arg->gen_code(FALSE, FALSE);
/*
* if we've pushed the variable argument counter value onto the
* stack, and this a fixed argument, swap the top two stack
* elements to get the argument counter back to the top of the
* stack; if this is a varargs argument there's no need, since
* it will have taken care of this
*/
if (pushed_varargs_counter && !arg->is_varargs())
G_cg->write_op(OPC_SWAP);
/* ensure that it generated something */
if (G_cg->get_sp_depth() <= depth)
G_tok->log_error(TCERR_ARG_EXPR_HAS_NO_VAL, i);
}
}
/* ------------------------------------------------------------------------ */
/*
* argument list entry
*/
void CTPNArg::gen_code(int, int)
{
/*
* Generate the argument expression. We need the value (hence
* discard = false), and we need the assignable value (hence
* for_condition = false).
*/
get_arg_expr()->gen_code(FALSE, FALSE);
/*
* if this is a list-to-varargs conversion, generate the conversion
* instruction
*/
if (is_varargs_)
{
/* write the opcode */
G_cg->write_op(OPC_MAKELSTPAR);
/* note the extra push and pop for the argument count */
G_cg->note_push();
G_cg->note_pop();
}
}
/* ------------------------------------------------------------------------ */
/*
* function/method call
*/
/*
* create
*/
CTPNCall::CTPNCall(CTcPrsNode *func, class CTPNArglist *arglist)
: CTPNCallBase(func, arglist)
{
/* the T3 instruction set limits calls to 127 arguments */
if (arglist->get_argc() > 127)
G_tok->log_error(TCERR_TOO_MANY_CALL_ARGS);
}
/*
* generate code
*/
void CTPNCall::gen_code(int discard, int)
{
int varargs;
/* push the argument list */
get_arg_list()->gen_code_arglist(&varargs);
/* generate an appropriate call instruction */
get_func()->gen_code_call(discard, get_arg_list()->get_argc(),
varargs);
}
/*
* Generate code for operator 'new'. A 'new' with an argument list
* looks like a function call: NEW(CALL(object-contents, ARGLIST(...))).
*/
void CTPNCall::gen_code_new(int discard, int argc, int varargs,
int from_call, int is_transient)
{
/*
* if this is a recursive call from another 'call' node, it's not
* allowed - we'd be trying to use the result of a call as the base
* class of the 'new', which is illegal
*/
if (from_call)
{
G_tok->log_error(TCERR_INVAL_NEW_EXPR);
return;
}
/* generate the argument list */
get_arg_list()->gen_code_arglist(&varargs);
/* generate the code for the 'new' call */
get_func()->gen_code_new(discard, get_arg_list()->get_argc(), varargs,
TRUE, is_transient);
}
/* ------------------------------------------------------------------------ */
/*
* member property evaluation
*/
void CTPNMember::gen_code(int discard, int)
{
/* ask the object expression to generate the code */
get_obj_expr()->gen_code_member(discard, get_prop_expr(), prop_is_expr_,
0, FALSE);
}
/*
* assign to member expression
*/
int CTPNMember::gen_code_asi(int discard, tc_asitype_t typ, CTcPrsNode *rhs,
int ignore_errors)
{
int is_self;
vm_obj_id_t obj;
vm_prop_id_t prop;
/*
* if it's not a simple assignment, tell the caller to generate the
* generic code to compute the composite value, and then call us
* again for a simple assignment
*/
if (typ != TC_ASI_SIMPLE)
return FALSE;
/* generate the right-hand side, unless the caller has already done so */
if (rhs != 0)
rhs->gen_code(FALSE, FALSE);
/*
* if the caller wants to use the assigned value, push a copy --
* we'll consume one copy in the SETPROP or related instruction, so
* we'll need another copy for the caller
*/
if (!discard)
{
G_cg->write_op(OPC_DUP);
G_cg->note_push();
}
/*
* Determine what we have on the left: we could have self, a
* constant object value, or any other expression.
*/
obj = get_obj_expr()->gen_code_obj_predot(&is_self);
/*
* determine what kind of property expression we have - don't
* generate any code for now, since we may need to generate some
* more code ahead of the property generation
*/
prop = get_prop_expr()->gen_code_propid(TRUE, prop_is_expr_);
/* determine what we need to do based on the operands */
if (prop == VM_INVALID_PROP)
{
/*
* We're assigning through a property pointer -- we must
* generate a PTRSETPROP instruction.
*
* Before we generate the property expression, we must generate
* the object expression. If we got a constant object, we must
* generate code to push that object value; otherwise, the code
* to generate the object value is already generated.
*/
if (is_self)
{
/* self - generate code to push the "self" value */
G_cg->write_op(OPC_PUSHSELF);
G_cg->note_push();
}
else if (obj != VM_INVALID_OBJ)
{
/* constant object - generate code to push the value */
G_cg->write_op(OPC_PUSHOBJ);
G_cs->write_obj_id(obj);
G_cg->note_push();
}
/* generate the property value expression */
get_prop_expr()->gen_code_propid(FALSE, prop_is_expr_);
/* generate the PTRSETPROP instruction */
G_cg->write_op(OPC_PTRSETPROP);
/* ptrsetprop removes three elements */
G_cg->note_pop(3);
}
else
{
/*
* We have a constant property value, so we have several
* instructions to choose from. If we're assigning to a
* property of "self", use SETPROPSELF. If we're assigning to a
* constant object, use OBJSETPROP. Otherwise, use the plain
* SETPROP.
*/
if (is_self)
{
/* write the SETPROPSELF */
G_cg->write_op(OPC_SETPROPSELF);
G_cs->write_prop_id(prop);
/* setpropself removes the value */
G_cg->note_pop();
}
else if (obj != VM_INVALID_OBJ)
{
/* write the OBJSETPROP */
G_cg->write_op(OPC_OBJSETPROP);
G_cs->write_obj_id(obj);
G_cs->write_prop_id(prop);
/* objsetprop removes the value */
G_cg->note_pop();
}
else
{
/*
* write the normal SETPROP; we already generated the code
* to push the object value, so it's where it should be
*/
G_cg->write_op(OPC_SETPROP);
G_cs->write_prop_id(prop);
/* setprop removes the value and the object */
G_cg->note_pop(2);
}
}
/* handled */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* member with argument list
*/
void CTPNMemArg::gen_code(int discard, int)
{
int varargs;
/* push the argument list */
get_arg_list()->gen_code_arglist(&varargs);
/* ask the object expression to generate the code */
get_obj_expr()->gen_code_member(discard, get_prop_expr(), prop_is_expr_,
get_arg_list()->get_argc(),
varargs);
}
/* ------------------------------------------------------------------------ */
/*
* construct a list
*/
void CTPNList::gen_code(int discard, int for_condition)
{
CTPNListEle *ele;
/*
* Before we construct the list dynamically, check to see if the
* list is constant. If it is, we need only built the list in the
* constant pool, and push its offset.
*/
if (is_const())
{
/* push the value only if we're not discarding it */
if (!discard)
{
/* write the instruction */
G_cg->write_op(OPC_PUSHLST);
/* add the list to the constant pool */
G_cg->add_const_list(this, G_cs, G_cs->get_ofs());
/*
* write a placeholder address, which will be corrected by
* the fixup that add_const_list() created
*/
G_cs->write4(0);
/* note the push */
G_cg->note_push();
}
/* done */
return;
}
/*
* It's not a constant list, so we must generate code to construct a
* list dynamically. Push each element of the list. We need each
* value (hence discard = false), and we require the assignable
* value of each expression (hence for_condition = false). Push the
* argument list in reverse order, since the run-time metaclass
* requires this ordering.
*/
for (ele = get_tail() ; ele != 0 ; ele = ele->get_prev())
ele->gen_code(FALSE, FALSE);
/* generate a NEW instruction for an object of metaclass LIST */
if (get_count() <= 255)
{
/* the count will fit in one byte - use the short form */
G_cg->write_op(OPC_NEW1);
G_cs->write((char)get_count());
G_cs->write((char)G_cg->get_predef_meta_idx(TCT3_METAID_LIST));
}
else
{
/* count doesn't fit in one byte - use the long form */
G_cg->write_op(OPC_NEW2);
G_cs->write2(get_count());
G_cs->write2(G_cg->get_predef_meta_idx(TCT3_METAID_LIST));
}
/* new1/new2 remove arguments */
G_cg->note_pop(get_count());
/* if we're not discarding the value, push it */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/* ------------------------------------------------------------------------ */
/*
* list element
*/
void CTPNListEle::gen_code(int discard, int for_condition)
{
/* generate the subexpression */
expr_->gen_code(discard, for_condition);
}
/* ------------------------------------------------------------------------ */
/*
* Basic T3-specific symbol class
*/
/*
* generate code to take the address of a symbol - in general, we cannot
* take the address of a symbol, so we'll just log an error
*/
void CTcSymbol::gen_code_addr()
{
G_tok->log_error(TCERR_NO_ADDR_SYM, (int)get_sym_len(), get_sym());
}
/*
* generate code to assign to the symbol - in general, we cannot assign
* to a symbol, so we'll just log an error
*/
int CTcSymbol::gen_code_asi(int, tc_asitype_t, class CTcPrsNode *,
int ignore_error)
{
/*
* if we're ignoring errors, simply return false to indicate that
* nothing happened
*/
if (ignore_error)
return FALSE;
/* log the error */
G_tok->log_error(TCERR_CANNOT_ASSIGN_SYM, (int)get_sym_len(), get_sym());
/*
* even though we didn't generate anything, this has been fully
* handled - the caller shouldn't attempt to generate any additional
* code for this
*/
return TRUE;
}
/*
* Generate code for calling the symbol. By default, we can't call a
* symbol.
*/
void CTcSymbol::gen_code_call(int, int, int)
{
/* log an error */
G_tok->log_error(TCERR_CANNOT_CALL_SYM, (int)get_sym_len(), get_sym());
}
/*
* Generate code for operator 'new'
*/
void CTcSymbol::gen_code_new(int, int, int, int)
{
G_tok->log_error(TCERR_INVAL_NEW_EXPR);
}
/*
* evaluate a property ID
*/
vm_prop_id_t CTcSymbol::gen_code_propid(int check_only, int is_expr)
{
/* by default, a symbol cannot be used as a property ID */
if (!check_only)
G_tok->log_error(TCERR_SYM_NOT_PROP, (int)get_sym_len(), get_sym());
/* we can't return a valid property ID */
return VM_INVALID_PROP;
}
/*
* evaluate a member expression
*/
void CTcSymbol::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
/* by default, a symbol cannot be used as an object expression */
G_tok->log_error(TCERR_SYM_NOT_OBJ, (int)get_sym_len(), get_sym());
}
/*
* generate code for an object expression before a '.'
*/
vm_obj_id_t CTcSymbol::gen_code_obj_predot(int *is_self)
{
/* by default, a symbol cannot be used as an object expression */
G_tok->log_error(TCERR_SYM_NOT_OBJ, (int)get_sym_len(), get_sym());
/* indicate that we don't have a constant object */
*is_self = FALSE;
return VM_INVALID_OBJ;
}
/* ------------------------------------------------------------------------ */
/*
* T3-specific function symbol class
*/
/*
* evaluate the symbol
*/
void CTcSymFunc::gen_code(int discard)
{
/*
* function address are always unknown during code generation;
* generate a placeholder instruction and add a fixup record for it
*/
G_cg->write_op(OPC_PUSHFNPTR);
/* add a fixup for the current code location */
add_abs_fixup(G_cs);
/* write a placeholder offset - arbitrarily use zero */
G_cs->write4(0);
/* note the push */
G_cg->note_push();
}
/*
* take the address of the function
*/
void CTcSymFunc::gen_code_addr()
{
/*
* the address of a function cannot be taken - using the name alone
* yields the address
*/
G_tok->log_error(TCERR_INVAL_FUNC_ADDR, (int)get_sym_len(), get_sym());
}
/*
* call the symbol
*/
void CTcSymFunc::gen_code_call(int discard, int argc, int varargs)
{
/*
* If this is a multi-method base function, a call to the function is
* actually a call to _multiMethodCall('name', args).
*/
if (is_multimethod_base_)
{
/* make a list out of the arguments */
if (varargs)
{
G_cg->write_op(OPC_VARARGC);
G_cs->write((char)argc);
}
else if (argc <= 255)
{
G_cg->write_op(OPC_NEW1);
G_cs->write((char)argc);
}
else
{
G_cg->write_op(OPC_NEW2);
G_cs->write2(argc);
}
G_cs->write((char)G_cg->get_predef_meta_idx(TCT3_METAID_LIST));
G_cg->note_pop(argc);
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
/* add the base function pointer argument */
CTcConstVal funcval;
funcval.set_funcptr(this);
CTPNConst func(&funcval);
func.gen_code(FALSE, FALSE);
/* look up _multiMethodCall */
static const char mmc_name[] = "_multiMethodCall";
static const size_t mmc_len = sizeof(mmc_name) - 1;
CTcSymFunc *mmc = (CTcSymFunc *)G_prs->get_global_symtab()->find(
mmc_name, mmc_len);
if (mmc == 0)
{
/* it's not defined - add an implied declaration for it */
mmc = new CTcSymFunc(mmc_name, mmc_len, FALSE, 1, TRUE, TRUE,
FALSE, FALSE, TRUE);
G_prs->get_global_symtab()->add_entry(mmc);
}
else if(mmc->get_type() != TC_SYM_FUNC)
{
/* it's defined, but not as a function - this is an error */
G_tok->log_error(TCERR_REDEF_AS_FUNC, (int)mmc_len, mmc_name);
return;
}
/*
* Generate the call. Note that there are always two arguments at
* this point: the base function pointer, and the argument list.
* The argument list is just one argument because we've already
* constructed a list out of it.
*/
mmc->gen_code_call(discard, 2, FALSE);
}
else
{
/* write the varargs modifier if appropriate */
if (varargs)
G_cg->write_op(OPC_VARARGC);
/* generate the call instruction and argument count */
G_cg->write_op(OPC_CALL);
G_cs->write((char)argc);
/* check the mode */
if (G_cg->is_eval_for_debug())
{
/*
* debugger expression compilation - we know the absolute
* address already, since all symbols are pre-resolved in the
* debugger
*/
G_cs->write4(get_code_pool_addr());
}
else
{
/*
* Normal compilation - we won't know the function's address
* until after generation is completed, so add a fixup for the
* current location, then write a placeholder for the offset
* field.
*/
add_abs_fixup(G_cs);
G_cs->write4(0);
}
/* call removes arguments */
G_cg->note_pop(argc);
/* make sure the argument count is correct */
if (varargs_ ? argc < argc_ : argc != argc_)
G_tok->log_error(TCERR_WRONG_ARGC_FOR_FUNC,
(int)get_sym_len(), get_sym(), argc_, argc);
/* if we're not discarding, push the return value from R0 */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
}
/*
* Get my code pool address. Valid only after linking.
*/
ulong CTcSymFunc::get_code_pool_addr() const
{
/* check for an absolute address */
if (abs_addr_valid_)
{
/*
* we have an absolute address - this means the symbol was
* loaded from a fully-linked image file (specifically, from the
* debug records)
*/
return abs_addr_;
}
else
{
/*
* we don't have an absolute address, so our address must have
* been determined through a linking step - get the final
* address from the anchor
*/
return anchor_->get_addr();
}
}
/*
* add a runtime symbol table entry
*/
void CTcSymFunc::add_runtime_symbol(CVmRuntimeSymbols *symtab)
{
vm_val_t val;
/* add an entry for our absolute address */
val.set_fnptr(get_code_pool_addr());
symtab->add_sym(get_sym(), get_sym_len(), &val);
}
/* ------------------------------------------------------------------------ */
/*
* T3-specific object symbol class
*/
/*
* evaluate the symbol
*/
void CTcSymObj::gen_code(int discard)
{
/* write code to push the object ID */
if (!discard)
{
/* push the object */
G_cg->write_op(OPC_PUSHOBJ);
G_cs->write_obj_id(obj_id_);
/* note the push */
G_cg->note_push();
}
}
/*
* take the address of the object
*/
void CTcSymObj::gen_code_addr()
{
/* act as though we were pushing the object ID directly */
gen_code(FALSE);
}
/*
* Generate a 'new' expression
*/
void CTcSymObj::gen_code_new(int discard, int argc, int varargs,
int is_transient)
{
/* use our static generator */
s_gen_code_new(discard, obj_id_, metaclass_, argc, varargs, is_transient);
}
/*
* Generate a 'new' expression. (This is a static method so that this
* code can be used by all of the possible expression types to which
* 'new' can be applied.)
*
* This type of generation applies only to objects of metaclass TADS
* Object.
*/
void CTcSymObj::s_gen_code_new(int discard, vm_obj_id_t obj_id,
tc_metaclass_t meta,
int argc, int varargs, int is_transient)
{
/*
* push the base class object - this is always the first argument
* (hence last pushed) to the metaclass constructor
*/
G_cg->write_op(OPC_PUSHOBJ);
G_cs->write_obj_id(obj_id);
/* note the push */
G_cg->note_push();
/*
* note that we can only allow 126 arguments to a constructor,
* because we must add the implicit superclass argument
*/
if (argc > 126)
G_tok->log_error(TCERR_TOO_MANY_CTOR_ARGS);
/*
* if we have varargs, swap the top stack elements to get the
* argument count back on top, and then generate the varargs
* modifier opcode
*/
if (varargs)
{
/* swap the top stack elements to get argc back to the top */
G_cg->write_op(OPC_SWAP);
/*
* increment the argument count to account for the superclass
* object argument
*/
G_cg->write_op(OPC_INC);
/* write the varargs modifier opcode */
G_cg->write_op(OPC_VARARGC);
}
/* figure the metaclass index - the compiler can only generate known
/*
* write the NEW instruction - since we always add TADS Object to
* our metaclass table before we start compiling any code, we know
* it always has a small metaclass number and will always fit in the
* short form of the instruction
*
* Note that the actual argument count we generate is one higher
* than the source code argument list, because we add the implicit
* first argument to the metaclass constructor
*/
G_cg->write_op(is_transient ? OPC_TRNEW1 : OPC_NEW1);
G_cs->write((char)(argc + 1));
/* write out the dependency table index for the metaclass */
switch (meta)
{
case TC_META_TADSOBJ:
G_cs->write((char)G_cg->get_predef_meta_idx(TCT3_METAID_TADSOBJ));
break;
default:
/* we can't use 'new' on symbols of other metaclasses */
G_tok->log_error(TCERR_BAD_META_FOR_NEW);
G_cs->write(0);
break;
}
/* new1 removes the arguments */
G_cg->note_pop(argc + 1);
/*
* if they're not discarding the value, push the new object
* reference, which will be in R0 when the constructor returns
*/
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/*
* Generate code for a member expression
*/
void CTcSymObj::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
s_gen_code_member(discard, prop_expr, prop_is_expr,
argc, obj_id_, varargs);
}
/*
* Static method to generate code for a member expression. This is
* static so that constant object nodes can share it.
*/
void CTcSymObj::s_gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, vm_obj_id_t obj_id, int varargs)
{
vm_prop_id_t prop;
/*
* generate the property expression - don't generate the code right
* now even if code generation is necessary, because this isn't the
* right place for it; for now, simply check to determine if we're
* going to need to generate any code for the property expression
*/
prop = prop_expr->gen_code_propid(TRUE, prop_is_expr);
/* don't allow method calls with arguments in speculative mode */
if (argc != 0 && G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/* check for a constant property value */
if (prop != VM_INVALID_PROP)
{
/* generate an OBJGETPROP or OBJCALLPROP as appropriate */
if (G_cg->is_speculative())
{
/* speculative evaluation - use GETPROPDATA */
G_cg->write_op(OPC_PUSHOBJ);
G_cs->write_obj_id(obj_id);
G_cg->write_op(OPC_GETPROPDATA);
G_cs->write_prop_id(prop);
/* we pushed the object, then popped it */
G_cg->note_push();
G_cg->note_pop();
}
else if (argc == 0)
{
/* no arguments - use OBJGETPROP */
G_cg->write_op(OPC_OBJGETPROP);
G_cs->write_obj_id(obj_id);
G_cs->write_prop_id(prop);
}
else
{
/* generate a varargs modifier if needed */
if (varargs)
G_cg->write_op(OPC_VARARGC);
/* arguments - use OBJCALLPROP */
G_cg->write_op(OPC_OBJCALLPROP);
G_cs->write((char)argc);
G_cs->write_obj_id(obj_id);
G_cs->write_prop_id(prop);
/* objcallprop removes arguments */
G_cg->note_pop(argc);
}
}
else
{
/*
* non-constant property value - we must first push the object
* value, then push the property value, then write a PTRCALLPROP
* instruction
*/
/* generate the object push */
G_cg->write_op(OPC_PUSHOBJ);
G_cs->write_obj_id(obj_id);
/* note the pushes */
G_cg->note_push();
/* keep the argument counter on top if necessary */
if (varargs)
G_cg->write_op(OPC_SWAP);
/* generate the property push */
prop_expr->gen_code_propid(FALSE, prop_is_expr);
/* generate the PTRCALLPROP or PTRGETPROPDATA */
if (G_cg->is_speculative())
{
/* speculative - use the data-only property evaluation */
G_cg->write_op(OPC_PTRGETPROPDATA);
}
else
{
/*
* if we have a varargs list, modify the call instruction
* that follows to make it a varargs call
*/
if (varargs)
{
/* swap to get the arg counter back on top */
G_cg->write_op(OPC_SWAP);
/* write the varargs modifier */
G_cg->write_op(OPC_VARARGC);
}
/* normal - call the property */
G_cg->write_op(OPC_PTRCALLPROP);
G_cs->write((int)argc);
}
/* ptrcallprop removes the arguments, the object, and the property */
G_cg->note_pop(argc + 2);
}
/* if they want the result, push it onto the stack */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/*
* generate code for an object before a '.'
*/
vm_obj_id_t CTcSymObj::gen_code_obj_predot(int *is_self)
{
/* return our constant object reference */
*is_self = FALSE;
return obj_id_;
}
/*
* add a runtime symbol table entry
*/
void CTcSymObj::add_runtime_symbol(CVmRuntimeSymbols *symtab)
{
vm_val_t val;
/* add our entry */
val.set_obj(obj_id_);
symtab->add_sym(get_sym(), get_sym_len(), &val);
}
/* ------------------------------------------------------------------------ */
/*
* T3-specific property symbol class
*/
/*
* evaluate the symbol
*/
void CTcSymProp::gen_code(int discard)
{
/*
* Evaluating a property is equivalent to calling the property on
* the "self" object with no arguments. If there's no "self"
* object, an unqualified property evaluation is not possible, so
* log an error if this is the case.
*/
if (!G_cs->is_self_available())
{
G_tok->log_error(TCERR_PROP_NEEDS_OBJ, (int)get_sym_len(), get_sym());
return;
}
if (G_cg->is_speculative())
{
/* push 'self', then evaluate the property in data-only mode */
G_cg->write_op(OPC_PUSHSELF);
G_cg->write_op(OPC_GETPROPDATA);
G_cs->write_prop_id(prop_);
/* we pushed the 'self' value then popped it again */
G_cg->note_push();
G_cg->note_pop();
}
else
{
/* generate the call to 'self' */
G_cg->write_op(OPC_GETPROPSELF);
G_cs->write_prop_id(prop_);
}
/* if they're not discarding the value, push the result */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/*
* evaluate a member expression
*/
void CTcSymProp::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
/* generate code to evaluate the property */
gen_code(FALSE);
/* if we have an argument counter, put it back on top */
if (varargs)
G_cg->write_op(OPC_SWAP);
/* use the standard member generation */
CTcPrsNode::s_gen_member_rhs(discard, prop_expr, prop_is_expr,
argc, varargs);
}
/*
* take the address of the property
*/
void CTcSymProp::gen_code_addr()
{
/* write code to push the property ID */
G_cg->write_op(OPC_PUSHPROPID);
G_cs->write_prop_id(prop_);
/* note the push */
G_cg->note_push();
}
/*
* assign to a property, implicitly of the "self" object
*/
int CTcSymProp::gen_code_asi(int discard, tc_asitype_t typ,
class CTcPrsNode *rhs, int /*ignore_errors*/)
{
/* if there's no "self" object, we can't make this assignment */
if (!G_cs->is_self_available())
{
/* log an error */
G_tok->log_error(TCERR_SETPROP_NEEDS_OBJ,
(int)get_sym_len(), get_sym());
/*
* indicate that we're finished, since there's nothing more we
* can do here
*/
return TRUE;
}
/*
* if it's not a simple assignment, tell the caller to do the
* composite work and get back to us with the value to store
*/
if (typ != TC_ASI_SIMPLE)
return FALSE;
/*
* generate the right-hand side's expression for assignment, unless
* the caller has already done so
*/
if (rhs != 0)
rhs->gen_code(FALSE, FALSE);
/*
* if we're not discarding the value, make a copy - we'll consume a
* copy in the SETPROP instruction, so we need one more copy to
* return to the enclosing expression
*/
if (!discard)
{
G_cg->write_op(OPC_DUP);
G_cg->note_push();
}
/*
* write the SETPROP instruction - use the special form to assign to
* "self"
*/
G_cg->write_op(OPC_SETPROPSELF);
G_cs->write_prop_id(prop_);
/* setpropself removes the value */
G_cg->note_pop();
/* handled */
return TRUE;
}
/*
* call the symbol
*/
void CTcSymProp::gen_code_call(int discard, int argc, int varargs)
{
/*
* if there's no "self", we can't invoke a property without an
* explicit object reference
*/
if (!G_cs->is_self_available())
{
G_tok->log_error(TCERR_PROP_NEEDS_OBJ, (int)get_sym_len(), get_sym());
return;
}
/* don't allow calling with arguments in speculative mode */
if (argc != 0 && G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/* generate code to invoke the property of "self" */
if (G_cg->is_speculative())
{
/* push 'self', then get the property in data-only mode */
G_cg->write_op(OPC_PUSHSELF);
G_cg->write_op(OPC_GETPROPDATA);
G_cs->write_prop_id(get_prop());
/* we pushed 'self' then popped it again */
G_cg->note_push();
G_cg->note_pop();
}
else if (argc == 0)
{
/* use the instruction with no arguments */
G_cg->write_op(OPC_GETPROPSELF);
G_cs->write_prop_id(get_prop());
}
else
{
/* write the varargs modifier if appropriate */
if (varargs)
G_cg->write_op(OPC_VARARGC);
/* use the instruction with arguments */
G_cg->write_op(OPC_CALLPROPSELF);
G_cs->write((char)argc);
G_cs->write_prop_id(get_prop());
/* callpropself removes arguments */
G_cg->note_pop(argc);
}
/* if we're not discarding, push the return value from R0 */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/*
* generate a property ID expression
*/
vm_prop_id_t CTcSymProp::gen_code_propid(int check_only, int is_expr)
{
/*
* If I'm to be treated as an expression (which indicates that the
* property symbol is explicitly enclosed in parentheses in the
* original source code expression), then I must evaluate this
* property of self. Otherwise, I yield literally the property ID.
*/
if (is_expr)
{
/* generate code unless we're only checking */
if (!check_only)
{
/* evaluate this property of self */
G_cg->write_op(OPC_GETPROPSELF);
G_cs->write_prop_id(get_prop());
/* leave the result on the stack */
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
/* tell the caller to use the stack value */
return VM_INVALID_PROP;
}
else
{
/* simple '.prop' - return my property ID as a constant value */
return get_prop();
}
}
/*
* add a runtime symbol table entry
*/
void CTcSymProp::add_runtime_symbol(CVmRuntimeSymbols *symtab)
{
vm_val_t val;
/* add our entry */
val.set_propid(get_prop());
symtab->add_sym(get_sym(), get_sym_len(), &val);
}
/* ------------------------------------------------------------------------ */
/*
* Enumerator symbol
*/
/*
* evaluate the symbol
*/
void CTcSymEnum::gen_code(int discard)
{
if (!discard)
{
/* generate code to push the enum value */
G_cg->write_op(OPC_PUSHENUM);
G_cs->write_enum_id(get_enum_id());
/* note the push */
G_cg->note_push();
}
}
/*
* add a runtime symbol table entry
*/
void CTcSymEnum::add_runtime_symbol(CVmRuntimeSymbols *symtab)
{
vm_val_t val;
/* add our entry */
val.set_enum(get_enum_id());
symtab->add_sym(get_sym(), get_sym_len(), &val);
}
/* ------------------------------------------------------------------------ */
/*
* T3-specific local variable/parameter symbol class
*/
/*
* generate code to evaluate the symbol
*/
void CTcSymLocal::gen_code(int discard)
{
/* generate code to push the local, if we're not discarding it */
if (!discard)
{
/*
* generate as a context local if required, otherwise as an
* ordinary local variable
*/
if (is_ctx_local_)
{
/* generate the context array lookup */
if (ctx_var_num_ <= 255 && get_ctx_arr_idx() <= 255)
{
/* we can do this whole operation with one instruction */
G_cg->write_op(OPC_IDXLCL1INT8);
G_cs->write((uchar)ctx_var_num_);
G_cs->write((uchar)get_ctx_arr_idx());
/* this pushes one value */
G_cg->note_push();
}
else
{
/* get our context array */
s_gen_code_getlcl(ctx_var_num_, FALSE);
/* get our value from the context array */
CTPNConst::s_gen_code_int(get_ctx_arr_idx());
G_cg->write_op(OPC_INDEX);
/* the INDEX operation removes two values and pushes one */
G_cg->note_pop();
}
}
else
{
/* generate as an ordinary local */
s_gen_code_getlcl(get_var_num(), is_param());
}
}
/*
* Mark the value as referenced, whether or not we're generating the
* code - the value has been logically referenced in the program
* even if the result of evaluating it isn't needed.
*/
set_val_used(TRUE);
}
/*
* generate code to push a local onto the stack
*/
void CTcSymLocal::s_gen_code_getlcl(int var_num, int is_param)
{
/* use the shortest form of the instruction that we can */
if (var_num <= 255)
{
/* 8-bit local number - use the one-byte form */
G_cg->write_op(is_param ? OPC_GETARG1 : OPC_GETLCL1);
G_cs->write((char)var_num);
}
else
{
/* local number won't fit in 8 bits - use the two-byte form */
G_cg->write_op(is_param ? OPC_GETARG2 : OPC_GETLCL2);
G_cs->write2(var_num);
}
/* note the push */
G_cg->note_push();
}
/*
* assign a value
*/
int CTcSymLocal::gen_code_asi(int discard, tc_asitype_t typ,
class CTcPrsNode *rhs, int ignore_errors)
{
int adding;
/* mark the variable as having had a value assigned to it */
set_val_assigned(TRUE);
/*
* if the assignment is anything but simple, this references the
* value as well
*/
if (typ != TC_ASI_SIMPLE)
set_val_used(TRUE);
/*
* If this is a context variable, use standard assignment (i.e.,
* generate the result first, then generate a simple assignment to the
* variable). Otherwise, we might be able to generate a fancy
* combined calculate-and-assign sequence, depending on the type of
* assignment calculation we're performing.
*/
if (is_ctx_local_ && typ != TC_ASI_SIMPLE)
{
/*
* it's a context local and it's not a simple assignment, so we
* can't perform any special calculate-and-assign sequence - tell
* the caller to calculate the full result first and then try
* again using simple assignment
*/
return FALSE;
}
/*
* check the type of assignment - we can optimize the code
* generation to use more compact instruction sequences for certain
* types of assignments
*/
switch(typ)
{
case TC_ASI_SIMPLE:
/*
* Simple assignment to local/parameter. Check for some special
* cases: when assigning a constant value of 0, 1, or nil to a
* local, we can generate a short instruction
*/
if (!is_param() && !is_ctx_local_ && rhs != 0 && rhs->is_const())
{
CTcConstVal *cval;
/* get the constant value */
cval = rhs->get_const_val();
/* check for nil and 0 or 1 values */
if (cval->get_type() == TC_CVT_NIL)
{
/* it's nil - generate NILLCL1 or NILLCL2 */
if (get_var_num() <= 255)
{
G_cg->write_op(OPC_NILLCL1);
G_cs->write((char)get_var_num());
}
else
{
G_cg->write_op(OPC_NILLCL2);
G_cs->write2(get_var_num());
}
/* if not discarding, leave nil on the stack */
if (!discard)
{
G_cg->write_op(OPC_PUSHNIL);
G_cg->note_push();
}
/* handled */
return TRUE;
}
else if (cval->get_type() == TC_CVT_INT
&& (cval->get_val_int() == 0
|| cval->get_val_int() == 1))
{
int ival;
/* get the integer value */
ival = cval->get_val_int();
/* 0 or 1 - generate ZEROLCLn or ONELCLn */
if (get_var_num() <= 255)
{
G_cg->write_op(ival == 0 ? OPC_ZEROLCL1 : OPC_ONELCL1);
G_cs->write((char)get_var_num());
}
else
{
G_cg->write_op(ival == 0 ? OPC_ZEROLCL2 : OPC_ONELCL2);
G_cs->write2(get_var_num());
}
/* if not discarding, leave the value on the stack */
if (!discard)
{
G_cg->write_op(ival == 0 ? OPC_PUSH_0 : OPC_PUSH_1);
G_cg->note_push();
}
/* handled */
return TRUE;
}
}
/*
* If we got here, we can't generate a specialized constant
* assignment - so, first, generate the right-hand side's value
* normally. (If no 'rhs' is specified, the value is already on
* the stack.)
*/
if (rhs != 0)
rhs->gen_code(FALSE, FALSE);
/* leave an extra copy of the value on the stack if not discarding */
if (!discard)
{
G_cg->write_op(OPC_DUP);
G_cg->note_push();
}
/* now assign the value at top of stack to the variable */
gen_code_setlcl();
/* handled */
return TRUE;
case TC_ASI_ADD:
adding = TRUE;
goto add_or_sub;
case TC_ASI_SUB:
adding = FALSE;
add_or_sub:
/* if this is a parameter, there's nothing special we can do */
if (is_param())
return FALSE;
/*
* Add/subtract to a local/parameter. If the right-hand side is a
* constant integer value, we might be able to generate a special
* instruction to add/subtract it.
*/
if (rhs != 0
&& adding
&& rhs->is_const()
&& rhs->get_const_val()->get_type() == TC_CVT_INT)
{
long ival;
/* get the integer value to assign */
ival = rhs->get_const_val()->get_val_int();
/*
* if the right-hand side's integer value fits in one byte,
* generate the short (8-bit) instruction; otherwise,
* generate the long (32-bit) format
*/
if (ival == 1)
{
/* adding one - increment the local */
G_cg->write_op(OPC_INCLCL);
G_cs->write2(get_var_num());
}
else if (ival == -1)
{
/* subtracting one - decrement the local */
G_cg->write_op(OPC_DECLCL);
G_cs->write2(get_var_num());
}
else if (ival <= 127 && ival >= -128
&& get_var_num() <= 255)
{
/* fits in 8 bits - use the 8-bit format */
G_cg->write_op(OPC_ADDILCL1);
G_cs->write((char)get_var_num());
G_cs->write((char)ival);
}
else
{
/*
* either the value or the variable number doesn't fit
* in 8 bits - use the 32-bit format
*/
G_cg->write_op(OPC_ADDILCL4);
G_cs->write2(get_var_num());
G_cs->write4(ival);
}
}
else
{
/*
* We don't have a special instruction for the right side,
* so generate it normally and add/subtract the value. (If
* there's no 'rhs' value specified, it means that the value
* is already on the stack, so there's nothing extra for us
* to generate.)
*/
if (rhs != 0)
rhs->gen_code(FALSE, FALSE);
/* write the ADDTOLCL instruction */
G_cg->write_op(adding ? OPC_ADDTOLCL : OPC_SUBFROMLCL);
G_cs->write2(get_var_num());
/* addtolcl/subfromlcl remove the rvalue */
G_cg->note_pop();
}
/*
* if not discarding, push the result onto the stack; do this by
* simply evaluating the local, which is the simplest and most
* efficient way to obtain the result of the computation
*/
if (!discard)
gen_code(FALSE);
/* handled */
return TRUE;
case TC_ASI_PREINC:
/* if this is a parameter, there's nothing special we can do */
if (is_param())
return FALSE;
/* generate code to increment the local */
G_cg->write_op(OPC_INCLCL);
G_cs->write2(get_var_num());
/* if we're not discarding, push the local's new value */
if (!discard)
gen_code(FALSE);
/* handled */
return TRUE;
case TC_ASI_POSTINC:
/* if this is a parameter, there's nothing special we can do */
if (is_param())
return FALSE;
/*
* if we're not discarding, push the local's value prior to
* incrementing it - this will be the result we'll leave on the
* stack
*/
if (!discard)
gen_code(FALSE);
/* generate code to increment the local */
G_cg->write_op(OPC_INCLCL);
G_cs->write2(get_var_num());
/* handled */
return TRUE;
case TC_ASI_PREDEC:
/* if this is a parameter, there's nothing special we can do */
if (is_param())
return FALSE;
/* generate code to decrement the local */
G_cg->write_op(OPC_DECLCL);
G_cs->write2(get_var_num());
/* if we're not discarding, push the local's new value */
if (!discard)
gen_code(FALSE);
/* handled */
return TRUE;
case TC_ASI_POSTDEC:
/* if this is a parameter, there's nothing special we can do */
if (is_param())
return FALSE;
/*
* if we're not discarding, push the local's value prior to
* decrementing it - this will be the result we'll leave on the
* stack
*/
if (!discard)
gen_code(FALSE);
/* generate code to decrement the local */
G_cg->write_op(OPC_DECLCL);
G_cs->write2(get_var_num());
/* handled */
return TRUE;
default:
/* we can't do anything special with other assignment types */
return FALSE;
}
}
/*
* generate code to assigin the value at top of stack to the local
* variable
*/
void CTcSymLocal::gen_code_setlcl()
{
/* check to see if we're a context local (as opposed to a stack local) */
if (is_ctx_local_)
{
/* generate the assignment using the appropriate sequence */
if (ctx_var_num_ <= 255 && get_ctx_arr_idx() <= 255)
{
/* we can fit this in a single instruction */
G_cg->write_op(OPC_SETINDLCL1I8);
G_cs->write((uchar)ctx_var_num_);
G_cs->write((uchar)get_ctx_arr_idx());
/* this pops the value being assigned */
G_cg->note_pop();
}
else
{
/* get our context array */
s_gen_code_getlcl(ctx_var_num_, FALSE);
/* set our value in the context array */
CTPNConst::s_gen_code_int(get_ctx_arr_idx());
G_cg->write_op(OPC_SETIND);
G_cg->write_op(OPC_DISC);
/*
* the SETIND pops three values and pushes one (for a net two
* pops), and the DISC pops one more value, so our total is
* three pops
*/
G_cg->note_pop(3);
}
}
else
{
/* we're just a plain stack variable */
gen_code_setlcl_stk();
}
}
/*
* Generate code to store the value at the top of the stack into the given
* local stack slot. Note that this routine will not work with a context
* local - it only works if the variable is known to be a stack variable.
*/
void CTcSymLocal::s_gen_code_setlcl_stk(int var_num, int is_param)
{
/* use the shortest form that will fit our variable index */
if (var_num <= 255)
{
/* use the one-byte instruction */
G_cg->write_op(is_param ? OPC_SETARG1 : OPC_SETLCL1);
G_cs->write((char)var_num);
}
else
{
/* big number - use the two-byte instruction */
G_cg->write_op(is_param ? OPC_SETARG2 : OPC_SETLCL2);
G_cs->write2(var_num);
}
/* the setarg/setlcl ops remove the rvalue */
G_cg->note_pop();
}
/*
* call the symbol
*/
void CTcSymLocal::gen_code_call(int discard, int argc, int varargs)
{
/*
* to call a local, we'll simply evaluate the local normally, then
* call through the resulting (presumed) property or function
* pointer value
*/
gen_code(FALSE);
/*
* if we have a varargs list, modify the call instruction that
* follows to make it a varargs call
*/
if (varargs)
{
/* swap the top of the stack to get the arg counter back on top */
G_cg->write_op(OPC_SWAP);
/* write the varargs modifier */
G_cg->write_op(OPC_VARARGC);
}
/* don't allow this at all in speculative mode */
if (G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/* call the result as a function or method pointer */
G_cg->write_op(OPC_PTRCALL);
G_cs->write((char)argc);
/* ptrcall removes the arguments and the function pointer */
G_cg->note_pop(argc + 1);
/* if we're not discarding the value, push the result */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/*
* generate a property ID expression
*/
vm_prop_id_t CTcSymLocal::gen_code_propid(int check_only, int /*is_expr*/)
{
/*
* treat the local as a property-valued expression; generate the
* code for the local, then tell the caller that no constant value
* is available, since the local's property ID value should be on
* the stack
*/
if (!check_only)
gen_code(FALSE);
/* tell the caller to use the stack value */
return VM_INVALID_PROP;
}
/*
* evaluate a member expression
*/
void CTcSymLocal::gen_code_member(int discard,
CTcPrsNode *prop_expr, int prop_is_expr,
int argc, int varargs)
{
/* generate code to evaluate the local */
gen_code(FALSE);
/* if we have an argument counter, put it back on top */
if (varargs)
G_cg->write_op(OPC_SWAP);
/* use the standard member generation */
CTcPrsNode::s_gen_member_rhs(discard, prop_expr, prop_is_expr,
argc, varargs);
}
/*
* write to a debug record
*/
int CTcSymLocal::write_to_debug_frame()
{
int flags;
/*
* write my ID - if we're a context variable, we want to write the
* context variable ID; otherwise write our stack location as normal
*/
if (is_ctx_local_)
G_cs->write2(ctx_var_num_);
else
G_cs->write2(var_num_);
/* compute my flags */
flags = 0;
if (is_param_)
flags |= 1;
if (is_ctx_local_)
flags |= 2;
/* write my flags */
G_cs->write2(flags);
/* write my local context array index */
G_cs->write2(get_ctx_arr_idx());
/* write the length of my symbol name */
G_cs->write2(len_);
G_cs->write(str_, len_);
/* we did write this symbol */
return TRUE;
}
/* ------------------------------------------------------------------------ */
/*
* Built-in function symbol
*/
/*
* Evaluate the symbol. Invoking a built-in function without an
* argument list is simply a call to the built-in function with no
* arguments.
*/
void CTcSymBif::gen_code(int discard)
{
/* generate a call */
gen_code_call(discard, 0, FALSE);
}
/*
* Generate code to call the built-in function
*/
void CTcSymBif::gen_code_call(int discard, int argc, int varargs)
{
/* don't allow calling built-in functions in speculative mode */
if (G_cg->is_speculative())
err_throw(VMERR_BAD_SPEC_EVAL);
/* check for minimum and maximum arguments */
if (argc < min_argc_)
{
G_tok->log_error(TCERR_TOO_FEW_FUNC_ARGS,
(int)get_sym_len(), get_sym());
}
else if (!varargs_ && argc > max_argc_)
{
G_tok->log_error(TCERR_TOO_MANY_FUNC_ARGS,
(int)get_sym_len(), get_sym());
}
/* write the varargs modifier if appropriate */
if (varargs)
G_cg->write_op(OPC_VARARGC);
/* generate the call */
if (get_func_set_id() < 4 && get_func_idx() < 256)
{
uchar short_ops[] =
{ OPC_BUILTIN_A, OPC_BUILTIN_B, OPC_BUILTIN_C, OPC_BUILTIN_D };
/*
* it's one of the first 256 functions in one of the first four
* function sets - we can generate a short instruction
*/
G_cg->write_op(short_ops[get_func_set_id()]);
G_cs->write((char)argc);
G_cs->write((char)get_func_idx());
}
else
{
/* it's not in the default set - use the longer instruction */
if (get_func_idx() < 256)
{
/* low function index - write the short form */
G_cg->write_op(OPC_BUILTIN1);
G_cs->write((char)argc);
G_cs->write((char)get_func_idx());
}
else
{
/* big function index - write the long form */
G_cg->write_op(OPC_BUILTIN2);
G_cs->write((char)argc);
G_cs->write2(get_func_idx());
}
/* write the function set ID */
G_cs->write((char)get_func_set_id());
}
/* the built-in functions always remove arguments */
G_cg->note_pop(argc);
/*
* if they're not discarding the value, push it - the value is
* sitting in R0 after the call returns
*/
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/* ------------------------------------------------------------------------ */
/*
* External function symbol
*/
/*
* evaluate the symbol
*/
void CTcSymExtfn::gen_code(int /*discard*/)
{
//$$$ to be implemented
assert(FALSE);
}
/*
* generate a call to the symbol
*/
void CTcSymExtfn::gen_code_call(int /*discard*/, int /*argc*/, int /*varargs*/)
{
//$$$ to be implemented
assert(FALSE);
}
/* ------------------------------------------------------------------------ */
/*
* Code Label symbol
*/
/*
* evaluate the symbol
*/
void CTcSymLabel::gen_code(int discard)
{
/* it's not legal to evaluate a code label; log an error */
G_tok->log_error(TCERR_CANNOT_EVAL_LABEL,
(int)get_sym_len(), get_sym());
}
/* ------------------------------------------------------------------------ */
/*
* Metaclass symbol
*/
/*
* generate code for evaluating the symbol
*/
void CTcSymMetaclass::gen_code(int discard)
{
/*
* the metaclass name refers to the IntrinsicClass instance
* associated with the metaclass
*/
G_cg->write_op(OPC_PUSHOBJ);
G_cs->write_obj_id(class_obj_);
/* note the push */
G_cg->note_push();
}
/*
* generate code for operator 'new' applied to the metaclass
*/
void CTcSymMetaclass::gen_code_new(int discard, int argc, int varargs,
int is_transient)
{
/* if we have varargs, write the modifier */
if (varargs)
G_cg->write_op(OPC_VARARGC);
if (meta_idx_ <= 255 && argc <= 255)
{
G_cg->write_op(is_transient ? OPC_TRNEW1 : OPC_NEW1);
G_cs->write((char)argc);
G_cs->write((char)meta_idx_);
}
else
{
G_cg->write_op(is_transient ? OPC_TRNEW1 : OPC_NEW2);
G_cs->write2(argc);
G_cs->write2(meta_idx_);
}
/* new1/new2 remove arguments */
G_cg->note_pop(argc);
/* if we're not discarding the value, push it */
if (!discard)
{
G_cg->write_op(OPC_GETR0);
G_cg->note_push();
}
}
/*
* generate a member expression
*/
void CTcSymMetaclass::gen_code_member(int discard, CTcPrsNode *prop_expr,
int prop_is_expr,
int argc, int varargs)
{
/* generate code to push our class object onto the stack */
gen_code(FALSE);
/* if we have an argument counter, put it back on top */
if (varargs)
G_cg->write_op(OPC_SWAP);
/* use the standard member generation */
CTcPrsNode::s_gen_member_rhs(discard, prop_expr, prop_is_expr,
argc, varargs);
}
/*
* add a runtime symbol table entry
*/
void CTcSymMetaclass::add_runtime_symbol(CVmRuntimeSymbols *symtab)
{
vm_val_t val;
/* add our entry */
val.set_obj(get_class_obj());
symtab->add_sym(get_sym(), get_sym_len(), &val);
}
/* ------------------------------------------------------------------------ */
/*
* Exception Table
*/
/*
* create
*/
CTcT3ExcTable::CTcT3ExcTable()
{
/* allocate an initial table */
exc_alloced_ = 1024;
table_ = (CTcT3ExcEntry *)t3malloc(exc_alloced_ * sizeof(table_[0]));
/* no entries are in use yet */
exc_used_ = 0;
/* method offset is not yet known */
method_ofs_ = 0;
}
/*
* add an entry to our table
*/
void CTcT3ExcTable::add_catch(ulong protected_start_ofs,
ulong protected_end_ofs,
ulong exc_obj_id, ulong catch_block_ofs)
{
CTcT3ExcEntry *entry;
/* if necessary, expand our table */
if (exc_used_ == exc_alloced_)
{
/* expand the table a bit */
exc_alloced_ += 1024;
/* reallocate the table at the larger size */
table_ = (CTcT3ExcEntry *)
t3realloc(table_, exc_alloced_ * sizeof(table_[0]));
}
/*
* set up the new entry - store the offsets relative to the method
* header start address
*/
entry = table_ + exc_used_;
entry->start_ofs = protected_start_ofs - method_ofs_;
entry->end_ofs = protected_end_ofs - method_ofs_;
entry->exc_obj_id = exc_obj_id;
entry->catch_ofs = catch_block_ofs - method_ofs_;
/* consume the new entry */
++exc_used_;
}
/*
* write our exception table to the code stream
*/
void CTcT3ExcTable::write_to_code_stream()
{
CTcT3ExcEntry *entry;
size_t i;
/* write the number of entries as a UINT2 */
G_cs->write2(exc_used_);
/* write the entries */
for (i = 0, entry = table_ ; i < exc_used_ ; ++i, ++entry)
{
/* write this entry */
G_cs->write2(entry->start_ofs);
G_cs->write2(entry->end_ofs);
G_cs->write_obj_id(entry->exc_obj_id);
G_cs->write2(entry->catch_ofs);
}
}
/* ------------------------------------------------------------------------ */
/*
* Code body
*/
/*
* generate code
*/
void CTPNCodeBody::gen_code(int, int)
{
CTcCodeBodyCtx *cur_ctx;
int ctx_idx;
tct3_method_gen_ctx gen_ctx;
/* if I've been replaced, don't bother generating any code */
if (replaced_)
return;
/*
* Open the method header.
*
* Generate to the static stream if this is a static initializer
* method, otherwise to the main stream.
*
* Anchor the fixups in the associated symbol table entry, if any. We
* maintain our own fixup list if we don't have a symbol, otherwise we
* use the one from our symbol table entry - in either case, we have to
* keep track of it ourselves, because a code body might be reachable
* through multiple references (a function, for example, has a global
* symbol table entry - fixups referencing us might already have been
* created by the time we generate our code).
*/
G_cg->open_method(is_static_ ? G_cs_static : G_cs_main,
fixup_owner_sym_, fixup_list_anchor_,
this, gototab_,
argc_, varargs_, is_constructor_, self_valid_,
&gen_ctx);
/*
* Add each local symbol table enclosing the code body's primary
* local symbol table to the frame list. The outermost code body
* table can be outside the primary code body table for situations
* such as anonymous functions. Since these tables are outside of
* any statements, we must explicitly add them to ensure that they
* are assigned debugging frame ID's and are written to the debug
* data.
*/
if (lcltab_ != 0)
{
CTcPrsSymtab *tab;
/* add each frame outside the primary frame to the code gen list */
for (tab = lcltab_->get_parent() ; tab != 0 ; tab = tab->get_parent())
G_cs->set_local_frame(tab);
}
/* the method's local symbol table is now the active symbol table */
G_cs->set_local_frame(lcltab_);
/* if we have a local context, initialize it */
if (has_local_ctx_)
{
/* write code to create the new Vector to store the context locals */
CTPNConst::s_gen_code_int(local_ctx_arr_size_);
G_cg->write_op(OPC_DUP);
G_cg->write_op(OPC_NEW1);
G_cs->write(2);
G_cs->write((char)G_cg->get_predef_meta_idx(TCT3_METAID_VECTOR));
/* retrieve the object value */
G_cg->write_op(OPC_GETR0);
/*
* we duplicated the vector size argument, then we popped it and
* pushed the object; so we have a maximum of one extra push and a
* net of zero
*/
G_cg->note_push();
G_cg->note_pop();
/* store the new object in the context local variable */
CTcSymLocal::s_gen_code_setlcl_stk(local_ctx_var_, FALSE);
/*
* go through our symbol table, and copy each parameter that's
* also a context local into its context local slot
*/
if (lcltab_ != 0)
lcltab_->enum_entries(&enum_for_param_ctx, this);
}
/*
* If we have a varargs-list parameter, generate the code to set up
* the list value from the actual parameters. Note that we must do
* this after we set up the local context, in case the varargs list
* parameter variable is a context local, in which case it will need
* to be stored in the context, in which case we need the context to
* be initialized first.
*/
if (varargs_list_)
{
/* generate the PUSHPARLST instruction to create the list */
G_cg->write_op(OPC_PUSHPARLST);
G_cs->write((uchar)argc_);
/*
* we pushed at least one value (the list); we don't know how many
* others we might have pushed, but it doesn't matter because the
* interpreter is responsible for checking for stack space
*/
G_cg->note_push();
/* store the list in our varargs parameter list local */
varargs_list_local_->gen_code_setlcl();
}
/*
* Generate code to initialize each enclosing-context-pointer local -
* these variables allow us to find the context objects while we're
* running inside this function.
*
* We *have to* generate context level 1 last. Context level 1 does a
* set-self to re-establish the method context (if there is one), and
* once we've changed to the method context 'self', we can no longer
* access the anonymous function pointer context, which is in 'self'
* until we change it. So, we have to wait and do level 1 last, so
* that we're completely done with the anonymous function context
* before we lose it.
*
* To ensure we generate level 1 last, make two passes: in the first
* pass, generate everything except level 1; on the second pass,
* generate only level 1. This two-pass approach guarantees that level
* 1 will be the last one generated, regardless of where it appears in
* the list. (We can't just rearrange the list - not easily, at least
* - because the list is in order of function object ('self') context
* slot index.)
*/
for (int ctx_pass = 1 ; ctx_pass <= 2 ; ++ctx_pass)
{
/* loop over each context entry */
for (ctx_idx = 0, cur_ctx = ctx_head_ ; cur_ctx != 0 ;
cur_ctx = cur_ctx->nxt_, ++ctx_idx)
{
/*
* Context level 1 *must* be generated last. If we're on pass
* 1 and this is level 1, skip it for now; if we're on pass 2,
* skip everything *except* level 1.
*/
if ((ctx_pass == 1 && cur_ctx->level_ == 1)
|| (ctx_pass == 2 && cur_ctx->level_ != 1))
continue;
/*
* Get this context value, stored in the function object
* ('self') at index value 2+n (n=0,1,...). Note that the
* context object indices start at 2 because the code pointer
* for the function is at index 1.
*/
G_cg->write_op(OPC_PUSHSELF);
CTPNConst::s_gen_code_int(ctx_idx + 2);
G_cg->write_op(OPC_INDEX);
/*
* we pushed the object, then popped the object and index and
* pushed the indexed value - this is a net of no change with
* one maximum push
*/
G_cg->note_push();
G_cg->note_pop();
/*
* If this is context level 1, and this context has a 'self',
* and we need either 'self' or the full method context from
* the lexically enclosing scope, generate code to load the
* self or the full method context (as appropriate) from our
* local context.
*
* The enclosing method context is always stored in the context
* at level 1, because this is inherently shared context for
* all enclosed lexical scopes. We thus only have to worry
* about this for context level 1.
*/
if (cur_ctx->level_ == 1
&& self_valid_
&& (self_referenced_ || full_method_ctx_referenced_))
{
CTPNCodeBody *outer;
/*
* we just put our context object on the stack in
* preparation for storing it - make a duplicate copy of it
* for our own purposes
*/
G_cg->write_op(OPC_DUP);
G_cg->note_push();
/* get the saved method context from the context object */
CTPNConst::s_gen_code_int(TCPRS_LOCAL_CTX_METHODCTX);
G_cg->write_op(OPC_INDEX);
/*
* Load the context. We must check the outermost context
* to determine what it stored, because we must load
* whatever it stored.
*/
if ((outer = get_outermost_enclosing()) != 0
&& outer->local_ctx_needs_full_method_ctx())
{
/* load the full method context */
G_cg->write_op(OPC_LOADCTX);
}
else
{
/* load the 'self' object */
G_cg->write_op(OPC_SETSELF);
}
/*
* we popped two values and pushed one in the INDEX, then
* popped a value in the LOADCTX or SETSELF: the net is
* removal of two elements and no additional maximum depth
*/
G_cg->note_pop(2);
}
/* store the context value in the appropriate local variable */
CTcSymLocal::s_gen_code_setlcl_stk(cur_ctx->var_num_, FALSE);
/*
* if we just did context level 1, and this is pass 2, we're
* done - pass 2's only function is to do level 1, so once we
* reach it, there's nothing left to do
*/
if (ctx_pass == 2 && cur_ctx->level_ == 1)
break;
}
}
/*
* if we created our own local context, and we have a 'self' object,
* and we need access to the 'self' object or the full method context
* from anonymous functions that refer to the local context, generate
* code to store the appropriate data in the local context
*/
if (has_local_ctx_ && self_valid_
&& (local_ctx_needs_self_ || local_ctx_needs_full_method_ctx_))
{
/* check to see what we need */
if (local_ctx_needs_full_method_ctx_)
{
/*
* we need the full method context - generate code to store it
* and push a reference to it onto the stack
*/
G_cg->write_op(OPC_STORECTX);
}
else
{
/* we only need 'self' - push it */
G_cg->write_op(OPC_PUSHSELF);
}
/* we just pushed one value */
G_cg->note_push();
/* assign the value to the context variable */
if (local_ctx_var_ <= 255 && TCPRS_LOCAL_CTX_METHODCTX <= 255)
{
/* we can make the assignment with a single instruction */
G_cg->write_op(OPC_SETINDLCL1I8);
G_cs->write((uchar)local_ctx_var_);
G_cs->write(TCPRS_LOCAL_CTX_METHODCTX);
/* that pops one value */
G_cg->note_pop();
}
else
{
/* get the context object */
CTcSymLocal::s_gen_code_getlcl(local_ctx_var_, FALSE);
/* store the data in the local context object */
CTPNConst::s_gen_code_int(TCPRS_LOCAL_CTX_METHODCTX);
G_cg->write_op(OPC_SETIND);
/* discard the indexed result */
G_cg->write_op(OPC_DISC);
/*
* the SETIND pops three values and pushes one, then we pop one
* more with the DISC - this is a net three pops with no extra
* maximum depth
*/
G_cg->note_pop(3);
}
}
/* generate the compound statement, if we have one */
if (stm_ != 0)
stm_->gen_code(TRUE, TRUE);
#ifdef T3_DEBUG
if (G_cg->get_sp_depth() != 0)
{
printf("---> stack depth is %d after block codegen!\n",
G_cg->get_sp_depth());
if (fixup_owner_sym_ != 0)
printf("---> code block for %.*s\n",
(int)fixup_owner_sym_->get_sym_len(),
fixup_owner_sym_->get_sym());
}
#endif
/* close the method */
G_cg->close_method(local_cnt_, end_desc_, end_linenum_, &gen_ctx);
/* remember the head of the nested symbol table list */
first_nested_symtab_ = G_cs->get_first_frame();
/* generate debug records if appropriate */
if (G_debug)
build_debug_table(gen_ctx.method_ofs);
/* check for unreferenced labels and issue warnings */
check_unreferenced_labels();
/* show the disassembly of the code block if desired */
if (G_disasm_out != 0)
show_disassembly(gen_ctx.method_ofs,
gen_ctx.code_start_ofs, gen_ctx.code_end_ofs);
/* clean up globals for the end of the method */
G_cg->close_method_cleanup(&gen_ctx);
}
/*
* disassembly stream source implementation
*/
class CTcUnasSrcCodeBody: public CTcUnasSrc
{
public:
CTcUnasSrcCodeBody(CTcCodeStream *str,
unsigned long code_start_ofs,
unsigned long code_end_ofs)
{
/* remember the stream */
str_ = str;
/* start at the starting offset */
cur_ofs_ = code_start_ofs;
/* remember the ending offset */
end_ofs_ = code_end_ofs;
}
/* read the next byte */
int next_byte(char *ch)
{
/* if there's anything left, return it */
if (cur_ofs_ < end_ofs_)
{
/* return the next byte */
*ch = str_->get_byte_at(cur_ofs_);
++cur_ofs_;
return 0;
}
else
{
/* indicate end of file */
return 1;
}
}
/* get the current offset */
ulong get_ofs() const { return cur_ofs_; }
protected:
/* code stream */
CTcCodeStream *str_;
/* current offset */
unsigned long cur_ofs_;
/* offset of end of code stream */
unsigned long end_ofs_;
};
/*
* Show the disassembly of this code block
*/
void CTPNCodeBody::show_disassembly(unsigned long start_ofs,
unsigned long code_start_ofs,
unsigned long code_end_ofs)
{
int argc;
int locals;
int total_stk;
unsigned exc_rel;
unsigned dbg_rel;
/* first, dump the header */
argc = (unsigned char)G_cs->get_byte_at(start_ofs);
locals = G_cs->readu2_at(start_ofs + 2);
total_stk = G_cs->readu2_at(start_ofs + 4);
exc_rel = G_cs->readu2_at(start_ofs + 6);
dbg_rel = G_cs->readu2_at(start_ofs + 8);
G_disasm_out->print("%8lx .code\n", start_ofs);
G_disasm_out->print(" .argcount %d%s\n",
(argc & 0x7f),
(argc & 0x80) != 0 ? "+" : "");
G_disasm_out->print(" .locals %d\n", locals);
G_disasm_out->print(" .maxstack %d\n", total_stk);
/* set up a code stream reader and dump the code stream */
CTcUnasSrcCodeBody src(G_cs, code_start_ofs, code_end_ofs);
CTcT3Unasm::disasm(&src, G_disasm_out);
/* show the exception table, if there is one */
if (exc_rel != 0)
{
unsigned long exc_ofs;
unsigned long exc_end_ofs;
/* get the starting address */
exc_ofs = start_ofs + exc_rel;
/*
* get the length - it's the part up to the debug records, or the
* part up to the current code offset if there are no debug records
*/
exc_end_ofs = (dbg_rel != 0 ? start_ofs + dbg_rel : G_cs->get_ofs());
/* show the table */
G_disasm_out->print(".exceptions\n");
CTcUnasSrcCodeBody exc_src(G_cs, exc_ofs, exc_end_ofs);
CTcT3Unasm::show_exc_table(&exc_src, G_disasm_out, start_ofs);
}
/* add a blank line at the end */
G_disasm_out->print("\n");
}
/*
* Check for unreferenced local variables
*/
void CTPNCodeBody::check_locals()
{
CTcPrsSymtab *tab;
/* check for unreferenced locals in each nested scope */
for (tab = first_nested_symtab_ ; tab != 0 ; tab = tab->get_list_next())
{
/* check this table */
tab->check_unreferenced_locals();
}
}
/*
* local symbol table enumerator for checking for parameter symbols that
* belong in the local context
*/
void CTPNCodeBody::enum_for_param_ctx(void *, class CTcSymbol *sym)
{
/* if this is a local, check it further */
if (sym->get_type() == TC_SYM_LOCAL || sym->get_type() == TC_SYM_PARAM)
{
CTcSymLocal *lcl = (CTcSymLocal *)sym;
/*
* if it's a parameter, and it's also a context variable, its
* value needs to be moved into the context
*/
if (lcl->is_param() && lcl->is_ctx_local())
{
/* get the actual parameter value from the stack */
CTcSymLocal::s_gen_code_getlcl(lcl->get_var_num(), TRUE);
/* store the value in the context variable */
lcl->gen_code_asi(TRUE, TC_ASI_SIMPLE, 0, TRUE);
}
}
}
/* ------------------------------------------------------------------------ */
/*
* 'return' statement
*/
/*
* generate code
*/
void CTPNStmReturn::gen_code(int, int)
{
int val_on_stack;
int need_gen;
/* add a line record */
add_debug_line_rec();
/* presume we'll generate a value */
need_gen = TRUE;
val_on_stack = FALSE;
/* generate the return value expression, if appropriate */
if (expr_ != 0)
{
/*
* it's an error if we're in a constructor, because a
* constructor implicitly always returns 'self'
*/
if (G_cg->is_in_constructor())
log_error(TCERR_CONSTRUCT_CANNOT_RET_VAL);
/* check for a constant expression */
if (expr_->is_const())
{
switch(expr_->get_const_val()->get_type())
{
case TC_CVT_NIL:
case TC_CVT_TRUE:
/*
* we can use special constant return instructions for
* these, so there's no need to generate the value
*/
need_gen = FALSE;
break;
default:
/*
* other types don't have constant-return opcodes, so we
* must generate the expression code
*/
need_gen = TRUE;
break;
}
}
/* if necessary, generate the value */
if (need_gen)
{
int depth;
/* note the initial stack depth */
depth = G_cg->get_sp_depth();
/*
* Generate the value. We are obviously not discarding the
* value, and since returning a value is equivalent to
* assigning the value, we must use the stricter assignment
* (not 'for condition') rules for logical expressions
*/
expr_->gen_code(FALSE, FALSE);
/* note whether we actually left a value on the stack */
val_on_stack = (G_cg->get_sp_depth() > depth);
}
else
{
/*
* we obviously aren't leaving a value on the stack if we
* don't generate anything
*/
val_on_stack = FALSE;
}
}
/*
* Before we return, let any enclosing statements generate any code
* necessary to leave their scope (in particular, we must invoke
* 'finally' handlers in any enclosing 'try' blocks).
*
* Note that we generated the expression BEFORE we call any
* 'finally' handlers. This is necessary because something we call
* in the course of evaluating the return value could have thrown an
* exception; if we were to call the 'finally' clauses before
* generating the return value, we could invoke the 'finally' clause
* twice (once explicitly, once in the handling of the thrown
* exception), which would be incorrect. By generating the
* 'finally' calls after the return expression, we're sure that the
* 'finally' blocks are invoked only once - either through the
* throw, or else now, after there's no more possibility of a
* 'throw' before the return.
*/
if (G_cs->get_enclosing() != 0)
{
int did_save_retval;
uint fin_ret_lcl;
/*
* if we're going to generate any subroutine calls, and we have
* a return value on the stack, we need to save the return value
* in a local to make sure the calculated value isn't affected
* by the subroutine call
*/
if (val_on_stack
&& G_cs->get_enclosing()->will_gen_code_unwind_for_return()
&& G_cs->get_code_body() != 0)
{
/* allocate a local variable to save the return value */
fin_ret_lcl = G_cs->get_code_body()->alloc_fin_ret_lcl();
/* save the return value in a stack temporary for a moment */
CTcSymLocal::s_gen_code_setlcl_stk(fin_ret_lcl, FALSE);
/*
* note that we saved the return value, so we can retrieve
* it later
*/
did_save_retval = TRUE;
}
else
{
/* note that we didn't save the return value */
did_save_retval = FALSE;
}
/* generate the unwind */
G_cs->get_enclosing()->gen_code_unwind_for_return();
/* if we saved the return value, retrieve it */
if (did_save_retval)
CTcSymLocal::s_gen_code_getlcl(fin_ret_lcl, FALSE);
}
/* check for an expression to return */
if (G_cg->is_in_constructor())
{
/* we're in a constructor - return 'self' */
G_cg->write_op(OPC_PUSHSELF);
G_cg->write_op(OPC_RETVAL);
}
else if (expr_ == 0)
{
/*
* there's no expression - generate a simple void return (but
* explicitly return nil, so we don't return something left in
* R0 from a previous function call we made)
*/
G_cg->write_op(OPC_RETNIL);
}
else
{
/* check for a constant expression */
if (expr_->is_const())
{
switch(expr_->get_const_val()->get_type())
{
case TC_CVT_NIL:
/* generate a RETNIL instruction */
G_cg->write_op(OPC_RETNIL);
break;
case TC_CVT_TRUE:
/* generate a RETTRUE instruction */
G_cg->write_op(OPC_RETTRUE);
break;
default:
break;
}
}
/*
* if we needed code generation to evaluate the return value, we
* now need to return the value
*/
if (need_gen)
{
/*
* Other types don't have constant-return opcodes. We
* already generated the expression value (before invoking
* the enclosing 'finally' handlers, if any), so the value
* is on the stack, and all we need to do is return it.
*
* If we didn't actually leave a value on the stack, we'll
* just return nil.
*/
if (val_on_stack)
{
/* generate the return-value opcode */
G_cg->write_op(OPC_RETVAL);
/* RETVAL removes an element from the stack */
G_cg->note_pop();
}
else
{
/*
* The depth didn't change - they must have evaluated an
* expression involving a dstring or void function.
* Return nil instead of the non-existent value.
*/
G_cg->write_op(OPC_RETNIL);
}
}
}
}
/* ------------------------------------------------------------------------ */
/*
* Static property initializer statement
*/
void CTPNStmStaticPropInit::gen_code(int, int)
{
int depth;
/* add a line record */
add_debug_line_rec();
/* note the initial stack depth */
depth = G_cg->get_sp_depth();
/* generate the expression, keeping the generated value */
expr_->gen_code(FALSE, FALSE);
/* ensure that we generated a value; if we didn't, push nil by default */
if (G_cg->get_sp_depth() <= depth)
{
/* push a default nil value */
G_cg->write_op(OPC_PUSHNIL);
G_cg->note_push();
}
/*
* duplicate the value on the stack, so we can assign it to
* initialize the property and also return it
*/
G_cg->write_op(OPC_DUP);
G_cg->note_push();
/* write the SETPROPSELF to initialize the property */
G_cg->write_op(OPC_SETPROPSELF);
G_cs->write_prop_id(prop_);
/* SETPROPSELF removes the value */
G_cg->note_pop();
/* return the value (which we duplicated on the stack) */
G_cg->write_op(OPC_RETVAL);
/* RETVAL removes the value */
G_cg->note_pop();
}
/* ------------------------------------------------------------------------ */
/*
* Object Definition Statement
*/
/*
* generate code
*/
void CTPNStmObject::gen_code(int, int)
{
CTPNSuperclass *sc;
CTPNObjProp *prop;
int sc_cnt;
ulong start_ofs;
uint internal_flags;
uint obj_flags;
CTcDataStream *str;
int bad_sc;
/* if this object has been replaced, don't generate any code for it */
if (replaced_)
return;
/* add an implicit constructor if necessary */
add_implicit_constructor();
/* get the appropriate stream for generating the data */
str = obj_sym_->get_stream();
/* clear the internal flags */
internal_flags = 0;
/*
* if we're a modified object, set the 'modified' flag in the object
* header
*/
if (modified_)
internal_flags |= TCT3_OBJ_MODIFIED;
/* set the 'transient' flag if appropriate */
if (transient_)
internal_flags |= TCT3_OBJ_TRANSIENT;
/* clear the object flags */
obj_flags = 0;
/*
* If we're specifically marked as a 'class' object, or we're a
* modified object, set the 'class' flag in the object flags.
*/
if (is_class_ || modified_)
obj_flags |= TCT3_OBJFLG_CLASS;
/* remember our starting offset in the object stream */
start_ofs = str->get_ofs();
/*
* store our stream offset in our defining symbol, for storage in
* the object file
*/
obj_sym_->set_stream_ofs(start_ofs);
/* write our internal flags */
str->write2(internal_flags);
/*
* First, write the per-object image file "OBJS" header - each
* object starts with its object ID and the number of bytes in the
* object's metaclass-specific data. For now, write zero as a
* placeholder for our data size. Note that this is a
* self-reference: it must be modified if the object is renumbered.
*/
str->write_obj_id_selfref(obj_sym_);
str->write2(0);
/* write a placeholder for the superclass count */
str->write2(0);
/* write the fixed property count */
str->write2(prop_cnt_);
/* write the object flags */
str->write2(obj_flags);
/*
* First, go through the superclass list and verify that each
* superclass is actually an object.
*/
for (bad_sc = FALSE, sc_cnt = 0, sc = first_sc_ ; sc != 0 ; sc = sc->nxt_)
{
CTcSymObj *sc_sym;
/* look up the superclass in the global symbol table */
sc_sym = (CTcSymObj *)sc->get_sym();
/* make sure it's defined, and that it's really an object */
if (sc_sym == 0)
{
/* not defined */
log_error(TCERR_UNDEF_SYM_SC,
(int)sc->get_sym_len(), sc->get_sym_txt(),
(int)obj_sym_->get_sym_len(), obj_sym_->get_sym());
/* note that we have an invalid superclass */
bad_sc = TRUE;
}
else if (sc_sym->get_type() != TC_SYM_OBJ)
{
/* log an error */
log_error(TCERR_SC_NOT_OBJECT,
(int)sc_sym->get_sym_len(), sc_sym->get_sym());
/* note that we have an invalid superclass */
bad_sc = TRUE;
}
else
{
/* count the superclass */
++sc_cnt;
/* write the superclass to the object header */
str->write_obj_id(sc_sym->get_obj_id());
}
}
/*
* If we detected a 'bad template' error when we were parsing the
* object definition, and all of our superclasses are valid, report the
* template error.
*
* Do not report this error if we have any undefined or invalid
* superclasses, because (1) we've already reported one error for this
* object definition (the bad superclass error), and (2) the missing
* template is likely just a consequence of the bad superclass, since
* we can't have scanned the proper superclass's list of templates if
* they didn't tell us the correct superclass to start with. When they
* fix the superclass list and re-compile the code, it's likely that
* this will fix the template problem as well, since we'll probably be
* able to find the template give the corrected superclass list.
*
* If we found an undescribed class anywhere in our hierarchy, a
* template simply cannot be used with this object; otherwise, the
* error is that we failed to find a suitable template
*/
if (has_bad_template() && !bad_sc)
log_error(has_undesc_sc()
? TCERR_OBJ_DEF_CANNOT_USE_TEMPLATE
: TCERR_OBJ_DEF_NO_TEMPLATE);
/* go back and write the superclass count to the header */
str->write2_at(start_ofs + TCT3_TADSOBJ_HEADER_OFS, sc_cnt);
/*
* Write the properties. We're required to write the properties in
* sorted order of property ID, but we can't do that yet, because
* the property ID's aren't finalized until after linking. For now,
* just write them out in the order in which they were defined.
*/
for (prop = first_prop_ ; prop != 0 ; prop = prop->nxt_)
{
/* make sure we have a valid property symbol */
if (prop->get_prop_sym() != 0)
{
/* write the property ID */
str->write_prop_id(prop->get_prop_sym()->get_prop());
/* generate code for the property */
prop->gen_code(FALSE, FALSE);
}
}
/*
* go back and write the size of our metaclass-specific data - this
* goes at offset 4 in the T3 generic metaclass header
*/
str->write2_at(start_ofs + TCT3_META_HEADER_OFS + 4,
str->get_ofs() - (start_ofs + TCT3_META_HEADER_OFS + 6));
}
/*
* Check for unreferenced local variables
*/
void CTPNStmObject::check_locals()
{
CTPNObjProp *prop;
/* check for unreferenced locals for each property */
for (prop = first_prop_ ; prop != 0 ; prop = prop->nxt_)
prop->check_locals();
} |