1
0
mirror of https://github.com/cc65/cc65.git synced 2024-06-07 23:29:39 +00:00
cc65/src/cc65/stdfunc.c
Oliver Schmidt 02daf9f8b5 So far the built-in inlining of several known standard function was always (!) enabled and the option -Os enabled additional, potentially unsafe inlining of some of those functions.
There were two aspects of this behavior that were considered undesirable:
- Although the safe inlining is in general desirable it should only be enabled if asked for it - like any other optimization.
- The option name -Os implies that it is a safe option, the potentially unsafe inlining should have a more explicit name.

So now:
- The option -Os enables the safe inlining.
- The new option --eagerly-inline-funcs enables the potentially unsafe inlining (including the safe inlining).

Additionally was added:
- The option --inline-stdfuncs that does like -Os enable the safe inlining but doesn't enable optimizations.
- The pragma inline-stdfuncs that works identical to --inline-stdfuncs.
- The pragma allow-eager-inline that enables the potentially unsafe inlining but doesn't include the safe inlining. That means that by itself it only marks code as safe for potentially unsafe inlining but doesn't actually enable any inlining.
2017-04-03 23:20:26 +02:00

1399 lines
51 KiB
C

/*****************************************************************************/
/* */
/* stdfunc.c */
/* */
/* Handle inlining of known functions for the cc65 compiler */
/* */
/* */
/* */
/* (C) 1998-2010 Ullrich von Bassewitz */
/* Roemerstrasse 52 */
/* D-70794 Filderstadt */
/* EMail: uz@cc65.org */
/* */
/* */
/* This software is provided 'as-is', without any expressed or implied */
/* warranty. In no event will the authors be held liable for any damages */
/* arising from the use of this software. */
/* */
/* Permission is granted to anyone to use this software for any purpose, */
/* including commercial applications, and to alter it and redistribute it */
/* freely, subject to the following restrictions: */
/* */
/* 1. The origin of this software must not be misrepresented; you must not */
/* claim that you wrote the original software. If you use this software */
/* in a product, an acknowledgment in the product documentation would be */
/* appreciated but is not required. */
/* 2. Altered source versions must be plainly marked as such, and must not */
/* be misrepresented as being the original software. */
/* 3. This notice may not be removed or altered from any source */
/* distribution. */
/* */
/*****************************************************************************/
#include <stdlib.h>
#include <string.h>
/* common */
#include "attrib.h"
#include "check.h"
/* cc65 */
#include "asmcode.h"
#include "asmlabel.h"
#include "codegen.h"
#include "error.h"
#include "funcdesc.h"
#include "global.h"
#include "litpool.h"
#include "loadexpr.h"
#include "scanner.h"
#include "stackptr.h"
#include "stdfunc.h"
#include "stdnames.h"
#include "typeconv.h"
/*****************************************************************************/
/* Function forwards */
/*****************************************************************************/
static void StdFunc_memcpy (FuncDesc*, ExprDesc*);
static void StdFunc_memset (FuncDesc*, ExprDesc*);
static void StdFunc_strcmp (FuncDesc*, ExprDesc*);
static void StdFunc_strcpy (FuncDesc*, ExprDesc*);
static void StdFunc_strlen (FuncDesc*, ExprDesc*);
/*****************************************************************************/
/* Data */
/*****************************************************************************/
/* Table with all known functions and their handlers. Must be sorted
** alphabetically!
*/
static struct StdFuncDesc {
const char* Name;
void (*Handler) (FuncDesc*, ExprDesc*);
} StdFuncs[] = {
{ "memcpy", StdFunc_memcpy },
{ "memset", StdFunc_memset },
{ "strcmp", StdFunc_strcmp },
{ "strcpy", StdFunc_strcpy },
{ "strlen", StdFunc_strlen },
};
#define FUNC_COUNT (sizeof (StdFuncs) / sizeof (StdFuncs[0]))
typedef struct ArgDesc ArgDesc;
struct ArgDesc {
const Type* ArgType; /* Required argument type */
ExprDesc Expr; /* Argument expression */
const Type* Type; /* The original type before conversion */
CodeMark Load; /* Start of argument load code */
CodeMark Push; /* Start of argument push code */
CodeMark End; /* End of the code for calculation+push */
unsigned Flags; /* Code generation flags */
};
/*****************************************************************************/
/* Helper functions */
/*****************************************************************************/
static int CmpFunc (const void* Key, const void* Elem)
/* Compare function for bsearch */
{
return strcmp ((const char*) Key, ((const struct StdFuncDesc*) Elem)->Name);
}
static long ArrayElementCount (const ArgDesc* Arg)
/* Check if the type of the given argument is an array. If so, and if the
** element count is known, return it. In all other cases, return UNSPECIFIED.
*/
{
long Count;
if (IsTypeArray (Arg->Type)) {
Count = GetElementCount (Arg->Type);
if (Count == FLEXIBLE) {
/* Treat as unknown */
Count = UNSPECIFIED;
}
} else {
Count = UNSPECIFIED;
}
return Count;
}
static void ParseArg (ArgDesc* Arg, Type* Type)
/* Parse one argument but do not push it onto the stack. Make all fields in
** Arg valid.
*/
{
/* We have a prototype, so chars may be pushed as chars */
Arg->Flags = CF_FORCECHAR;
/* Remember the required argument type */
Arg->ArgType = Type;
/* Read the expression we're going to pass to the function */
MarkedExprWithCheck (hie1, &Arg->Expr);
/* Remember the actual argument type */
Arg->Type = Arg->Expr.Type;
/* Convert this expression to the expected type */
TypeConversion (&Arg->Expr, Type);
/* Remember the following code position */
GetCodePos (&Arg->Load);
/* If the value is a constant, set the flag, otherwise load it into the
** primary register.
*/
if (ED_IsConstAbsInt (&Arg->Expr) && ED_CodeRangeIsEmpty (&Arg->Expr)) {
/* Remember that we have a constant value */
Arg->Flags |= CF_CONST;
} else {
/* Load into the primary */
LoadExpr (CF_NONE, &Arg->Expr);
}
/* Remember the following code position */
GetCodePos (&Arg->Push);
GetCodePos (&Arg->End);
/* Use the type of the argument for the push */
Arg->Flags |= TypeOf (Arg->Expr.Type);
}
void AddCmpCodeIfSizeNot256 (const char* Code, long Size)
/* Add a line of Assembly code that compares an index register
** only if it isn't comparing to #<256. (If the next line
** is "bne", then this will avoid a redundant line.)
*/
{
if (Size != 256) {
AddCodeLine (Code, (unsigned int)Size);
}
}
/*****************************************************************************/
/* memcpy */
/*****************************************************************************/
static void StdFunc_memcpy (FuncDesc* F attribute ((unused)), ExprDesc* Expr)
/* Handle the memcpy function */
{
/* Argument types: (void*, const void*, size_t) */
static Type Arg1Type[] = { TYPE(T_PTR), TYPE(T_VOID), TYPE(T_END) };
static Type Arg2Type[] = { TYPE(T_PTR), TYPE(T_VOID|T_QUAL_CONST), TYPE(T_END) };
static Type Arg3Type[] = { TYPE(T_SIZE_T), TYPE(T_END) };
ArgDesc Arg1, Arg2, Arg3;
unsigned ParamSize = 0;
unsigned Label;
int Offs;
/* Argument #1 */
ParseArg (&Arg1, Arg1Type);
g_push (Arg1.Flags, Arg1.Expr.IVal);
GetCodePos (&Arg1.End);
ParamSize += SizeOf (Arg1Type);
ConsumeComma ();
/* Argument #2 */
ParseArg (&Arg2, Arg2Type);
g_push (Arg2.Flags, Arg2.Expr.IVal);
GetCodePos (&Arg2.End);
ParamSize += SizeOf (Arg2Type);
ConsumeComma ();
/* Argument #3. Since memcpy is a fastcall function, we must load the
** arg into the primary if it is not already there. This parameter is
** also ignored for the calculation of the parameter size, since it is
** not passed via the stack.
*/
ParseArg (&Arg3, Arg3Type);
if (Arg3.Flags & CF_CONST) {
LoadExpr (CF_NONE, &Arg3.Expr);
}
/* Emit the actual function call. This will also cleanup the stack. */
g_call (CF_FIXARGC, Func_memcpy, ParamSize);
if (ED_IsConstAbsInt (&Arg3.Expr) && Arg3.Expr.IVal == 0) {
/* memcpy has been called with a count argument of zero */
Warning ("Call to memcpy has no effect");
/* Remove all of the generated code but the load of the first
** argument, which is what memcpy returns.
*/
RemoveCode (&Arg1.Push);
/* Set the function result to the first argument */
*Expr = Arg1.Expr;
/* Bail out, no need for further improvements */
goto ExitPoint;
}
if (IS_Get (&InlineStdFuncs)) {
/* We've generated the complete code for the function now and know the
** types of all parameters. Check for situations where better code can
** be generated. If such a situation is detected, throw away the
** generated, and emit better code.
*/
if (ED_IsConstAbsInt (&Arg3.Expr) && Arg3.Expr.IVal <= 256 &&
((ED_IsRVal (&Arg2.Expr) && ED_IsLocConst (&Arg2.Expr)) ||
(ED_IsLVal (&Arg2.Expr) && ED_IsLocRegister (&Arg2.Expr))) &&
((ED_IsRVal (&Arg1.Expr) && ED_IsLocConst (&Arg1.Expr)) ||
(ED_IsLVal (&Arg1.Expr) && ED_IsLocRegister (&Arg1.Expr)))) {
int Reg1 = ED_IsLVal (&Arg1.Expr) && ED_IsLocRegister (&Arg1.Expr);
int Reg2 = ED_IsLVal (&Arg2.Expr) && ED_IsLocRegister (&Arg2.Expr);
/* Drop the generated code */
RemoveCode (&Arg1.Expr.Start);
/* We need a label */
Label = GetLocalLabel ();
/* Generate memcpy code */
if (Arg3.Expr.IVal <= 127) {
AddCodeLine ("ldy #$%02X", (unsigned char) (Arg3.Expr.IVal-1));
g_defcodelabel (Label);
if (Reg2) {
AddCodeLine ("lda (%s),y", ED_GetLabelName (&Arg2.Expr, 0));
} else {
AddCodeLine ("lda %s,y", ED_GetLabelName (&Arg2.Expr, 0));
}
if (Reg1) {
AddCodeLine ("sta (%s),y", ED_GetLabelName (&Arg1.Expr, 0));
} else {
AddCodeLine ("sta %s,y", ED_GetLabelName (&Arg1.Expr, 0));
}
AddCodeLine ("dey");
AddCodeLine ("bpl %s", LocalLabelName (Label));
} else {
AddCodeLine ("ldy #$00");
g_defcodelabel (Label);
if (Reg2) {
AddCodeLine ("lda (%s),y", ED_GetLabelName (&Arg2.Expr, 0));
} else {
AddCodeLine ("lda %s,y", ED_GetLabelName (&Arg2.Expr, 0));
}
if (Reg1) {
AddCodeLine ("sta (%s),y", ED_GetLabelName (&Arg1.Expr, 0));
} else {
AddCodeLine ("sta %s,y", ED_GetLabelName (&Arg1.Expr, 0));
}
AddCodeLine ("iny");
AddCmpCodeIfSizeNot256 ("cpy #$%02X", Arg3.Expr.IVal);
AddCodeLine ("bne %s", LocalLabelName (Label));
}
/* memcpy returns the address, so the result is actually identical
** to the first argument.
*/
*Expr = Arg1.Expr;
/* Bail out, no need for further processing */
goto ExitPoint;
}
if (ED_IsConstAbsInt (&Arg3.Expr) && Arg3.Expr.IVal <= 256 &&
ED_IsRVal (&Arg2.Expr) && ED_IsLocConst (&Arg2.Expr) &&
ED_IsRVal (&Arg1.Expr) && ED_IsLocStack (&Arg1.Expr) &&
(Arg1.Expr.IVal - StackPtr) + Arg3.Expr.IVal < 256) {
/* It is possible to just use one index register even if the stack
** offset is not zero, by adjusting the offset to the constant
** address accordingly. But we cannot do this if the data in
** question is in the register space or at an absolute address less
** than 256. Register space is zero page, which means that the
** address calculation could overflow in the linker.
*/
int AllowOneIndex = !ED_IsLocRegister (&Arg2.Expr) &&
!(ED_IsLocAbs (&Arg2.Expr) && Arg2.Expr.IVal < 256);
/* Calculate the real stack offset */
Offs = ED_GetStackOffs (&Arg1.Expr, 0);
/* Drop the generated code */
RemoveCode (&Arg1.Expr.Start);
/* We need a label */
Label = GetLocalLabel ();
/* Generate memcpy code */
if (Arg3.Expr.IVal <= 127 && !AllowOneIndex) {
if (Offs == 0) {
AddCodeLine ("ldy #$%02X", (unsigned char) (Offs + Arg3.Expr.IVal - 1));
g_defcodelabel (Label);
AddCodeLine ("lda %s,y", ED_GetLabelName (&Arg2.Expr, -Offs));
AddCodeLine ("sta (sp),y");
AddCodeLine ("dey");
AddCodeLine ("bpl %s", LocalLabelName (Label));
} else {
AddCodeLine ("ldx #$%02X", (unsigned char) (Arg3.Expr.IVal-1));
AddCodeLine ("ldy #$%02X", (unsigned char) (Offs + Arg3.Expr.IVal - 1));
g_defcodelabel (Label);
AddCodeLine ("lda %s,x", ED_GetLabelName (&Arg2.Expr, 0));
AddCodeLine ("sta (sp),y");
AddCodeLine ("dey");
AddCodeLine ("dex");
AddCodeLine ("bpl %s", LocalLabelName (Label));
}
} else {
if (Offs == 0 || AllowOneIndex) {
AddCodeLine ("ldy #$%02X", (unsigned char) Offs);
g_defcodelabel (Label);
AddCodeLine ("lda %s,y", ED_GetLabelName (&Arg2.Expr, -Offs));
AddCodeLine ("sta (sp),y");
AddCodeLine ("iny");
AddCmpCodeIfSizeNot256 ("cpy #$%02X", Offs + Arg3.Expr.IVal);
AddCodeLine ("bne %s", LocalLabelName (Label));
} else {
AddCodeLine ("ldx #$00");
AddCodeLine ("ldy #$%02X", (unsigned char) Offs);
g_defcodelabel (Label);
AddCodeLine ("lda %s,x", ED_GetLabelName (&Arg2.Expr, 0));
AddCodeLine ("sta (sp),y");
AddCodeLine ("iny");
AddCodeLine ("inx");
AddCmpCodeIfSizeNot256 ("cpx #$%02X", Arg3.Expr.IVal);
AddCodeLine ("bne %s", LocalLabelName (Label));
}
}
/* memcpy returns the address, so the result is actually identical
** to the first argument.
*/
*Expr = Arg1.Expr;
/* Bail out, no need for further processing */
goto ExitPoint;
}
if (ED_IsConstAbsInt (&Arg3.Expr) && Arg3.Expr.IVal <= 256 &&
ED_IsRVal (&Arg2.Expr) && ED_IsLocStack (&Arg2.Expr) &&
(Arg2.Expr.IVal - StackPtr) + Arg3.Expr.IVal < 256 &&
ED_IsRVal (&Arg1.Expr) && ED_IsLocConst (&Arg1.Expr)) {
/* It is possible to just use one index register even if the stack
** offset is not zero, by adjusting the offset to the constant
** address accordingly. But we cannot do this if the data in
** question is in the register space or at an absolute address less
** than 256. Register space is zero page, which means that the
** address calculation could overflow in the linker.
*/
int AllowOneIndex = !ED_IsLocRegister (&Arg1.Expr) &&
!(ED_IsLocAbs (&Arg1.Expr) && Arg1.Expr.IVal < 256);
/* Calculate the real stack offset */
Offs = ED_GetStackOffs (&Arg2.Expr, 0);
/* Drop the generated code */
RemoveCode (&Arg1.Expr.Start);
/* We need a label */
Label = GetLocalLabel ();
/* Generate memcpy code */
if (Arg3.Expr.IVal <= 127 && !AllowOneIndex) {
if (Offs == 0) {
AddCodeLine ("ldy #$%02X", (unsigned char) (Arg3.Expr.IVal - 1));
g_defcodelabel (Label);
AddCodeLine ("lda (sp),y");
AddCodeLine ("sta %s,y", ED_GetLabelName (&Arg1.Expr, 0));
AddCodeLine ("dey");
AddCodeLine ("bpl %s", LocalLabelName (Label));
} else {
AddCodeLine ("ldx #$%02X", (unsigned char) (Arg3.Expr.IVal-1));
AddCodeLine ("ldy #$%02X", (unsigned char) (Offs + Arg3.Expr.IVal - 1));
g_defcodelabel (Label);
AddCodeLine ("lda (sp),y");
AddCodeLine ("sta %s,x", ED_GetLabelName (&Arg1.Expr, 0));
AddCodeLine ("dey");
AddCodeLine ("dex");
AddCodeLine ("bpl %s", LocalLabelName (Label));
}
} else {
if (Offs == 0 || AllowOneIndex) {
AddCodeLine ("ldy #$%02X", (unsigned char) Offs);
g_defcodelabel (Label);
AddCodeLine ("lda (sp),y");
AddCodeLine ("sta %s,y", ED_GetLabelName (&Arg1.Expr, -Offs));
AddCodeLine ("iny");
AddCmpCodeIfSizeNot256 ("cpy #$%02X", Offs + Arg3.Expr.IVal);
AddCodeLine ("bne %s", LocalLabelName (Label));
} else {
AddCodeLine ("ldx #$00");
AddCodeLine ("ldy #$%02X", (unsigned char) Offs);
g_defcodelabel (Label);
AddCodeLine ("lda (sp),y");
AddCodeLine ("sta %s,x", ED_GetLabelName (&Arg1.Expr, 0));
AddCodeLine ("iny");
AddCodeLine ("inx");
AddCmpCodeIfSizeNot256 ("cpx #$%02X", Arg3.Expr.IVal);
AddCodeLine ("bne %s", LocalLabelName (Label));
}
}
/* memcpy returns the address, so the result is actually identical
** to the first argument.
*/
*Expr = Arg1.Expr;
/* Bail out, no need for further processing */
goto ExitPoint;
}
if (ED_IsConstAbsInt (&Arg3.Expr) && Arg3.Expr.IVal <= 256 &&
ED_IsRVal (&Arg2.Expr) && ED_IsLocStack (&Arg2.Expr) &&
(Offs = ED_GetStackOffs (&Arg2.Expr, 0)) == 0) {
/* Drop the generated code but leave the load of the first argument*/
RemoveCode (&Arg1.Push);
/* We need a label */
Label = GetLocalLabel ();
/* Generate memcpy code */
AddCodeLine ("sta ptr1");
AddCodeLine ("stx ptr1+1");
if (Arg3.Expr.IVal <= 127) {
AddCodeLine ("ldy #$%02X", (unsigned char) (Arg3.Expr.IVal - 1));
g_defcodelabel (Label);
AddCodeLine ("lda (sp),y");
AddCodeLine ("sta (ptr1),y");
AddCodeLine ("dey");
AddCodeLine ("bpl %s", LocalLabelName (Label));
} else {
AddCodeLine ("ldy #$00");
g_defcodelabel (Label);
AddCodeLine ("lda (sp),y");
AddCodeLine ("sta (ptr1),y");
AddCodeLine ("iny");
AddCmpCodeIfSizeNot256 ("cpy #$%02X", Arg3.Expr.IVal);
AddCodeLine ("bne %s", LocalLabelName (Label));
}
/* Reload result - X hasn't changed by the code above */
AddCodeLine ("lda ptr1");
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = GetFuncReturn (Expr->Type);
/* Bail out, no need for further processing */
goto ExitPoint;
}
}
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = GetFuncReturn (Expr->Type);
ExitPoint:
/* We expect the closing brace */
ConsumeRParen ();
}
/*****************************************************************************/
/* memset */
/*****************************************************************************/
static void StdFunc_memset (FuncDesc* F attribute ((unused)), ExprDesc* Expr)
/* Handle the memset function */
{
/* Argument types: (void*, int, size_t) */
static Type Arg1Type[] = { TYPE(T_PTR), TYPE(T_VOID), TYPE(T_END) };
static Type Arg2Type[] = { TYPE(T_INT), TYPE(T_END) };
static Type Arg3Type[] = { TYPE(T_SIZE_T), TYPE(T_END) };
ArgDesc Arg1, Arg2, Arg3;
int MemSet = 1; /* Use real memset if true */
unsigned ParamSize = 0;
unsigned Label;
/* Argument #1 */
ParseArg (&Arg1, Arg1Type);
g_push (Arg1.Flags, Arg1.Expr.IVal);
GetCodePos (&Arg1.End);
ParamSize += SizeOf (Arg1Type);
ConsumeComma ();
/* Argument #2. This argument is special in that we will call another
** function if it is a constant zero.
*/
ParseArg (&Arg2, Arg2Type);
if ((Arg2.Flags & CF_CONST) != 0 && Arg2.Expr.IVal == 0) {
/* Don't call memset, call bzero instead */
MemSet = 0;
} else {
/* Push the argument */
g_push (Arg2.Flags, Arg2.Expr.IVal);
GetCodePos (&Arg2.End);
ParamSize += SizeOf (Arg2Type);
}
ConsumeComma ();
/* Argument #3. Since memset is a fastcall function, we must load the
** arg into the primary if it is not already there. This parameter is
** also ignored for the calculation of the parameter size, since it is
** not passed via the stack.
*/
ParseArg (&Arg3, Arg3Type);
if (Arg3.Flags & CF_CONST) {
LoadExpr (CF_NONE, &Arg3.Expr);
}
/* Emit the actual function call. This will also cleanup the stack. */
g_call (CF_FIXARGC, MemSet? Func_memset : Func__bzero, ParamSize);
if (ED_IsConstAbsInt (&Arg3.Expr) && Arg3.Expr.IVal == 0) {
/* memset has been called with a count argument of zero */
Warning ("Call to memset has no effect");
/* Remove all of the generated code but the load of the first
** argument, which is what memset returns.
*/
RemoveCode (&Arg1.Push);
/* Set the function result to the first argument */
*Expr = Arg1.Expr;
/* Bail out, no need for further improvements */
goto ExitPoint;
}
if (IS_Get (&InlineStdFuncs)) {
/* We've generated the complete code for the function now and know the
** types of all parameters. Check for situations where better code can
** be generated. If such a situation is detected, throw away the
** generated, and emit better code.
** Note: Lots of improvements would be possible here, but I will
** concentrate on the most common case: memset with arguments 2 and 3
** being constant numerical values. Some checks have shown that this
** covers nearly 90% of all memset calls.
*/
if (ED_IsConstAbsInt (&Arg3.Expr) && Arg3.Expr.IVal <= 256 &&
ED_IsConstAbsInt (&Arg2.Expr) &&
((ED_IsRVal (&Arg1.Expr) && ED_IsLocConst (&Arg1.Expr)) ||
(ED_IsLVal (&Arg1.Expr) && ED_IsLocRegister (&Arg1.Expr)))) {
int Reg = ED_IsLVal (&Arg1.Expr) && ED_IsLocRegister (&Arg1.Expr);
/* Drop the generated code */
RemoveCode (&Arg1.Expr.Start);
/* We need a label */
Label = GetLocalLabel ();
/* Generate memset code */
if (Arg3.Expr.IVal <= 127) {
AddCodeLine ("ldy #$%02X", (unsigned char) (Arg3.Expr.IVal-1));
AddCodeLine ("lda #$%02X", (unsigned char) Arg2.Expr.IVal);
g_defcodelabel (Label);
if (Reg) {
AddCodeLine ("sta (%s),y", ED_GetLabelName (&Arg1.Expr, 0));
} else {
AddCodeLine ("sta %s,y", ED_GetLabelName (&Arg1.Expr, 0));
}
AddCodeLine ("dey");
AddCodeLine ("bpl %s", LocalLabelName (Label));
} else {
AddCodeLine ("ldy #$00");
AddCodeLine ("lda #$%02X", (unsigned char) Arg2.Expr.IVal);
g_defcodelabel (Label);
if (Reg) {
AddCodeLine ("sta (%s),y", ED_GetLabelName (&Arg1.Expr, 0));
} else {
AddCodeLine ("sta %s,y", ED_GetLabelName (&Arg1.Expr, 0));
}
AddCodeLine ("iny");
AddCmpCodeIfSizeNot256 ("cpy #$%02X", Arg3.Expr.IVal);
AddCodeLine ("bne %s", LocalLabelName (Label));
}
/* memset returns the address, so the result is actually identical
** to the first argument.
*/
*Expr = Arg1.Expr;
/* Bail out, no need for further processing */
goto ExitPoint;
}
if (ED_IsConstAbsInt (&Arg3.Expr) && Arg3.Expr.IVal <= 256 &&
ED_IsConstAbsInt (&Arg2.Expr) &&
ED_IsRVal (&Arg1.Expr) && ED_IsLocStack (&Arg1.Expr) &&
(Arg1.Expr.IVal - StackPtr) + Arg3.Expr.IVal < 256) {
/* Calculate the real stack offset */
int Offs = ED_GetStackOffs (&Arg1.Expr, 0);
/* Drop the generated code */
RemoveCode (&Arg1.Expr.Start);
/* We need a label */
Label = GetLocalLabel ();
/* Generate memset code */
AddCodeLine ("ldy #$%02X", (unsigned char) Offs);
AddCodeLine ("lda #$%02X", (unsigned char) Arg2.Expr.IVal);
g_defcodelabel (Label);
AddCodeLine ("sta (sp),y");
AddCodeLine ("iny");
AddCmpCodeIfSizeNot256 ("cpy #$%02X", Offs + Arg3.Expr.IVal);
AddCodeLine ("bne %s", LocalLabelName (Label));
/* memset returns the address, so the result is actually identical
** to the first argument.
*/
*Expr = Arg1.Expr;
/* Bail out, no need for further processing */
goto ExitPoint;
}
if (ED_IsConstAbsInt (&Arg3.Expr) && Arg3.Expr.IVal <= 256 &&
ED_IsConstAbsInt (&Arg2.Expr) &&
(Arg2.Expr.IVal != 0 || IS_Get (&CodeSizeFactor) > 200)) {
/* Remove all of the generated code but the load of the first
** argument.
*/
RemoveCode (&Arg1.Push);
/* We need a label */
Label = GetLocalLabel ();
/* Generate code */
AddCodeLine ("sta ptr1");
AddCodeLine ("stx ptr1+1");
if (Arg3.Expr.IVal <= 127) {
AddCodeLine ("ldy #$%02X", (unsigned char) (Arg3.Expr.IVal-1));
AddCodeLine ("lda #$%02X", (unsigned char) Arg2.Expr.IVal);
g_defcodelabel (Label);
AddCodeLine ("sta (ptr1),y");
AddCodeLine ("dey");
AddCodeLine ("bpl %s", LocalLabelName (Label));
} else {
AddCodeLine ("ldy #$00");
AddCodeLine ("lda #$%02X", (unsigned char) Arg2.Expr.IVal);
g_defcodelabel (Label);
AddCodeLine ("sta (ptr1),y");
AddCodeLine ("iny");
AddCmpCodeIfSizeNot256 ("cpy #$%02X", Arg3.Expr.IVal);
AddCodeLine ("bne %s", LocalLabelName (Label));
}
/* Load the function result pointer into a/x (x is still valid). This
** code will get removed by the optimizer if it is not used later.
*/
AddCodeLine ("lda ptr1");
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = GetFuncReturn (Expr->Type);
/* Bail out, no need for further processing */
goto ExitPoint;
}
}
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = GetFuncReturn (Expr->Type);
ExitPoint:
/* We expect the closing brace */
ConsumeRParen ();
}
/*****************************************************************************/
/* strcmp */
/*****************************************************************************/
static void StdFunc_strcmp (FuncDesc* F attribute ((unused)), ExprDesc* Expr)
/* Handle the strcmp function */
{
/* Argument types: (const char*, const char*) */
static Type Arg1Type[] = { TYPE(T_PTR), TYPE(T_CHAR|T_QUAL_CONST), TYPE(T_END) };
static Type Arg2Type[] = { TYPE(T_PTR), TYPE(T_CHAR|T_QUAL_CONST), TYPE(T_END) };
ArgDesc Arg1, Arg2;
unsigned ParamSize = 0;
long ECount1;
long ECount2;
int IsArray;
int Offs;
/* Setup the argument type string */
Arg1Type[1].C = GetDefaultChar () | T_QUAL_CONST;
Arg2Type[1].C = GetDefaultChar () | T_QUAL_CONST;
/* Argument #1 */
ParseArg (&Arg1, Arg1Type);
g_push (Arg1.Flags, Arg1.Expr.IVal);
ParamSize += SizeOf (Arg1Type);
ConsumeComma ();
/* Argument #2. */
ParseArg (&Arg2, Arg2Type);
/* Since strcmp is a fastcall function, we must load the
** arg into the primary if it is not already there. This parameter is
** also ignored for the calculation of the parameter size, since it is
** not passed via the stack.
*/
if (Arg2.Flags & CF_CONST) {
LoadExpr (CF_NONE, &Arg2.Expr);
}
/* Emit the actual function call. This will also cleanup the stack. */
g_call (CF_FIXARGC, Func_strcmp, ParamSize);
/* Get the element counts of the arguments. Then get the larger of the
** two into ECount1. This removes FLEXIBLE and UNSPECIFIED automatically
*/
ECount1 = ArrayElementCount (&Arg1);
ECount2 = ArrayElementCount (&Arg2);
if (ECount2 > ECount1) {
ECount1 = ECount2;
}
if (IS_Get (&InlineStdFuncs)) {
/* If the second argument is the empty string literal, we can generate
** more efficient code.
*/
if (ED_IsLocLiteral (&Arg2.Expr) &&
IS_Get (&WritableStrings) == 0 &&
GetLiteralSize (Arg2.Expr.LVal) == 1 &&
GetLiteralStr (Arg2.Expr.LVal)[0] == '\0') {
/* Drop the generated code so we have the first argument in the
** primary
*/
RemoveCode (&Arg1.Push);
/* We don't need the literal any longer */
ReleaseLiteral (Arg2.Expr.LVal);
/* We do now have Arg1 in the primary. Load the first character from
** this string and cast to int. This is the function result.
*/
IsArray = IsTypeArray (Arg1.Type) && ED_IsRVal (&Arg1.Expr);
if (IsArray && ED_IsLocStack (&Arg1.Expr) &&
(Offs = ED_GetStackOffs (&Arg1.Expr, 0) < 256)) {
/* Drop the generated code */
RemoveCode (&Arg1.Load);
/* Generate code */
AddCodeLine ("ldy #$%02X", Offs);
AddCodeLine ("ldx #$00");
AddCodeLine ("lda (sp),y");
} else if (IsArray && ED_IsLocConst (&Arg1.Expr)) {
/* Drop the generated code */
RemoveCode (&Arg1.Load);
/* Generate code */
AddCodeLine ("ldx #$00");
AddCodeLine ("lda %s", ED_GetLabelName (&Arg1.Expr, 0));
} else {
/* Drop part of the generated code so we have the first argument
** in the primary
*/
RemoveCode (&Arg1.Push);
/* Fetch the first char */
g_getind (CF_CHAR | CF_UNSIGNED, 0);
}
} else if ((IS_Get (&CodeSizeFactor) >= 165) &&
((ED_IsRVal (&Arg2.Expr) && ED_IsLocConst (&Arg2.Expr)) ||
(ED_IsLVal (&Arg2.Expr) && ED_IsLocRegister (&Arg2.Expr))) &&
((ED_IsRVal (&Arg1.Expr) && ED_IsLocConst (&Arg1.Expr)) ||
(ED_IsLVal (&Arg1.Expr) && ED_IsLocRegister (&Arg1.Expr))) &&
(IS_Get (&EagerlyInlineFuncs) || (ECount1 > 0 && ECount1 < 256))) {
unsigned Entry, Loop, Fin; /* Labels */
const char* Load;
const char* Compare;
if (ED_IsLVal (&Arg1.Expr) && ED_IsLocRegister (&Arg1.Expr)) {
Load = "lda (%s),y";
} else {
Load = "lda %s,y";
}
if (ED_IsLVal (&Arg2.Expr) && ED_IsLocRegister (&Arg2.Expr)) {
Compare = "cmp (%s),y";
} else {
Compare = "cmp %s,y";
}
/* Drop the generated code */
RemoveCode (&Arg1.Expr.Start);
/* We need labels */
Entry = GetLocalLabel ();
Loop = GetLocalLabel ();
Fin = GetLocalLabel ();
/* Generate strcmp code */
AddCodeLine ("ldy #$00");
AddCodeLine ("beq %s", LocalLabelName (Entry));
g_defcodelabel (Loop);
AddCodeLine ("tax");
AddCodeLine ("beq %s", LocalLabelName (Fin));
AddCodeLine ("iny");
g_defcodelabel (Entry);
AddCodeLine (Load, ED_GetLabelName (&Arg1.Expr, 0));
AddCodeLine (Compare, ED_GetLabelName (&Arg2.Expr, 0));
AddCodeLine ("beq %s", LocalLabelName (Loop));
AddCodeLine ("ldx #$01");
AddCodeLine ("bcs %s", LocalLabelName (Fin));
AddCodeLine ("ldx #$FF");
g_defcodelabel (Fin);
} else if ((IS_Get (&CodeSizeFactor) > 190) &&
((ED_IsRVal (&Arg2.Expr) && ED_IsLocConst (&Arg2.Expr)) ||
(ED_IsLVal (&Arg2.Expr) && ED_IsLocRegister (&Arg2.Expr))) &&
(IS_Get (&EagerlyInlineFuncs) || (ECount1 > 0 && ECount1 < 256))) {
unsigned Entry, Loop, Fin; /* Labels */
const char* Compare;
if (ED_IsLVal (&Arg2.Expr) && ED_IsLocRegister (&Arg2.Expr)) {
Compare = "cmp (%s),y";
} else {
Compare = "cmp %s,y";
}
/* Drop the generated code */
RemoveCode (&Arg1.Push);
/* We need labels */
Entry = GetLocalLabel ();
Loop = GetLocalLabel ();
Fin = GetLocalLabel ();
/* Store Arg1 into ptr1 */
AddCodeLine ("sta ptr1");
AddCodeLine ("stx ptr1+1");
/* Generate strcmp code */
AddCodeLine ("ldy #$00");
AddCodeLine ("beq %s", LocalLabelName (Entry));
g_defcodelabel (Loop);
AddCodeLine ("tax");
AddCodeLine ("beq %s", LocalLabelName (Fin));
AddCodeLine ("iny");
g_defcodelabel (Entry);
AddCodeLine ("lda (ptr1),y");
AddCodeLine (Compare, ED_GetLabelName (&Arg2.Expr, 0));
AddCodeLine ("beq %s", LocalLabelName (Loop));
AddCodeLine ("ldx #$01");
AddCodeLine ("bcs %s", LocalLabelName (Fin));
AddCodeLine ("ldx #$FF");
g_defcodelabel (Fin);
}
}
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = GetFuncReturn (Expr->Type);
/* We expect the closing brace */
ConsumeRParen ();
}
/*****************************************************************************/
/* strcpy */
/*****************************************************************************/
static void StdFunc_strcpy (FuncDesc* F attribute ((unused)), ExprDesc* Expr)
/* Handle the strcpy function */
{
/* Argument types: (char*, const char*) */
static Type Arg1Type[] = { TYPE(T_PTR), TYPE(T_CHAR), TYPE(T_END) };
static Type Arg2Type[] = { TYPE(T_PTR), TYPE(T_CHAR|T_QUAL_CONST), TYPE(T_END) };
ArgDesc Arg1, Arg2;
unsigned ParamSize = 0;
long ECount;
unsigned L1;
/* Setup the argument type string */
Arg1Type[1].C = GetDefaultChar ();
Arg2Type[1].C = GetDefaultChar () | T_QUAL_CONST;
/* Argument #1 */
ParseArg (&Arg1, Arg1Type);
g_push (Arg1.Flags, Arg1.Expr.IVal);
GetCodePos (&Arg1.End);
ParamSize += SizeOf (Arg1Type);
ConsumeComma ();
/* Argument #2. Since strcpy is a fastcall function, we must load the
** arg into the primary if it is not already there. This parameter is
** also ignored for the calculation of the parameter size, since it is
** not passed via the stack.
*/
ParseArg (&Arg2, Arg2Type);
if (Arg2.Flags & CF_CONST) {
LoadExpr (CF_NONE, &Arg2.Expr);
}
/* Emit the actual function call. This will also cleanup the stack. */
g_call (CF_FIXARGC, Func_strcpy, ParamSize);
/* Get the element count of argument 1 if it is an array */
ECount = ArrayElementCount (&Arg1);
if (IS_Get (&InlineStdFuncs)) {
/* We've generated the complete code for the function now and know the
** types of all parameters. Check for situations where better code can
** be generated. If such a situation is detected, throw away the
** generated, and emit better code.
*/
if (((ED_IsRVal (&Arg2.Expr) && ED_IsLocConst (&Arg2.Expr)) ||
(ED_IsLVal (&Arg2.Expr) && ED_IsLocRegister (&Arg2.Expr))) &&
((ED_IsRVal (&Arg1.Expr) && ED_IsLocConst (&Arg1.Expr)) ||
(ED_IsLVal (&Arg1.Expr) && ED_IsLocRegister (&Arg1.Expr))) &&
(IS_Get (&EagerlyInlineFuncs) ||
(ECount != UNSPECIFIED && ECount < 256))) {
const char* Load;
const char* Store;
if (ED_IsLVal (&Arg2.Expr) && ED_IsLocRegister (&Arg2.Expr)) {
Load = "lda (%s),y";
} else {
Load = "lda %s,y";
}
if (ED_IsLVal (&Arg1.Expr) && ED_IsLocRegister (&Arg1.Expr)) {
Store = "sta (%s),y";
} else {
Store = "sta %s,y";
}
/* Drop the generated code */
RemoveCode (&Arg1.Expr.Start);
/* We need labels */
L1 = GetLocalLabel ();
/* Generate strcpy code */
AddCodeLine ("ldy #$FF");
g_defcodelabel (L1);
AddCodeLine ("iny");
AddCodeLine (Load, ED_GetLabelName (&Arg2.Expr, 0));
AddCodeLine (Store, ED_GetLabelName (&Arg1.Expr, 0));
AddCodeLine ("bne %s", LocalLabelName (L1));
/* strcpy returns argument #1 */
*Expr = Arg1.Expr;
/* Bail out, no need for further processing */
goto ExitPoint;
}
if (ED_IsRVal (&Arg2.Expr) && ED_IsLocStack (&Arg2.Expr) &&
StackPtr >= -255 &&
ED_IsRVal (&Arg1.Expr) && ED_IsLocConst (&Arg1.Expr)) {
/* It is possible to just use one index register even if the stack
** offset is not zero, by adjusting the offset to the constant
** address accordingly. But we cannot do this if the data in
** question is in the register space or at an absolute address less
** than 256. Register space is zero page, which means that the
** address calculation could overflow in the linker.
*/
int AllowOneIndex = !ED_IsLocRegister (&Arg1.Expr) &&
!(ED_IsLocAbs (&Arg1.Expr) && Arg1.Expr.IVal < 256);
/* Calculate the real stack offset */
int Offs = ED_GetStackOffs (&Arg2.Expr, 0);
/* Drop the generated code */
RemoveCode (&Arg1.Expr.Start);
/* We need labels */
L1 = GetLocalLabel ();
/* Generate strcpy code */
AddCodeLine ("ldy #$%02X", (unsigned char) (Offs - 1));
if (Offs == 0 || AllowOneIndex) {
g_defcodelabel (L1);
AddCodeLine ("iny");
AddCodeLine ("lda (sp),y");
AddCodeLine ("sta %s,y", ED_GetLabelName (&Arg1.Expr, -Offs));
} else {
AddCodeLine ("ldx #$FF");
g_defcodelabel (L1);
AddCodeLine ("iny");
AddCodeLine ("inx");
AddCodeLine ("lda (sp),y");
AddCodeLine ("sta %s,x", ED_GetLabelName (&Arg1.Expr, 0));
}
AddCodeLine ("bne %s", LocalLabelName (L1));
/* strcpy returns argument #1 */
*Expr = Arg1.Expr;
/* Bail out, no need for further processing */
goto ExitPoint;
}
if (ED_IsRVal (&Arg2.Expr) && ED_IsLocConst (&Arg2.Expr) &&
ED_IsRVal (&Arg1.Expr) && ED_IsLocStack (&Arg1.Expr) &&
StackPtr >= -255) {
/* It is possible to just use one index register even if the stack
** offset is not zero, by adjusting the offset to the constant
** address accordingly. But we cannot do this if the data in
** question is in the register space or at an absolute address less
** than 256. Register space is zero page, which means that the
** address calculation could overflow in the linker.
*/
int AllowOneIndex = !ED_IsLocRegister (&Arg2.Expr) &&
!(ED_IsLocAbs (&Arg2.Expr) && Arg2.Expr.IVal < 256);
/* Calculate the real stack offset */
int Offs = ED_GetStackOffs (&Arg1.Expr, 0);
/* Drop the generated code */
RemoveCode (&Arg1.Expr.Start);
/* We need labels */
L1 = GetLocalLabel ();
/* Generate strcpy code */
AddCodeLine ("ldy #$%02X", (unsigned char) (Offs - 1));
if (Offs == 0 || AllowOneIndex) {
g_defcodelabel (L1);
AddCodeLine ("iny");
AddCodeLine ("lda %s,y", ED_GetLabelName (&Arg2.Expr, -Offs));
AddCodeLine ("sta (sp),y");
} else {
AddCodeLine ("ldx #$FF");
g_defcodelabel (L1);
AddCodeLine ("iny");
AddCodeLine ("inx");
AddCodeLine ("lda %s,x", ED_GetLabelName (&Arg2.Expr, 0));
AddCodeLine ("sta (sp),y");
}
AddCodeLine ("bne %s", LocalLabelName (L1));
/* strcpy returns argument #1 */
*Expr = Arg1.Expr;
/* Bail out, no need for further processing */
goto ExitPoint;
}
}
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = GetFuncReturn (Expr->Type);
ExitPoint:
/* We expect the closing brace */
ConsumeRParen ();
}
/*****************************************************************************/
/* strlen */
/*****************************************************************************/
static void StdFunc_strlen (FuncDesc* F attribute ((unused)), ExprDesc* Expr)
/* Handle the strlen function */
{
static Type ArgType[] = { TYPE(T_PTR), TYPE(T_CHAR|T_QUAL_CONST), TYPE(T_END) };
ExprDesc Arg;
int IsArray;
int IsPtr;
int IsByteIndex;
long ECount;
unsigned L;
/* Setup the argument type string */
ArgType[1].C = GetDefaultChar () | T_QUAL_CONST;
/* Evaluate the parameter */
hie1 (&Arg);
/* Check if the argument is an array. If so, remember the element count.
** Otherwise set the element count to undefined.
*/
IsArray = IsTypeArray (Arg.Type);
if (IsArray) {
ECount = GetElementCount (Arg.Type);
if (ECount == FLEXIBLE) {
/* Treat as unknown */
ECount = UNSPECIFIED;
}
IsPtr = 0;
} else {
ECount = UNSPECIFIED;
IsPtr = IsTypePtr (Arg.Type);
}
/* Check if the elements of an array can be addressed by a byte sized
** index. This is true if the size of the array is known and less than
** 256.
*/
IsByteIndex = (ECount != UNSPECIFIED && ECount < 256);
/* Do type conversion */
TypeConversion (&Arg, ArgType);
if (IS_Get (&Optimize)) {
/* If the expression is a literal, and if string literals are read
** only, we can calculate the length of the string and remove it
** from the literal pool. Otherwise we have to calculate the length
** at runtime.
*/
if (ED_IsLocLiteral (&Arg) && IS_Get (&WritableStrings) == 0) {
/* Constant string literal */
ED_MakeConstAbs (Expr, GetLiteralSize (Arg.LVal) - 1, type_size_t);
/* We don't need the literal any longer */
ReleaseLiteral (Arg.LVal);
/* Bail out, no need for further improvements */
goto ExitPoint;
}
}
if (IS_Get (&InlineStdFuncs)) {
/* We will inline strlen for arrays with constant addresses, if either
** requested on the command line, or the array is smaller than 256,
** so the inlining is considered safe.
*/
if (ED_IsLocConst (&Arg) && IsArray &&
(IS_Get (&EagerlyInlineFuncs) || IsByteIndex)) {
/* Generate the strlen code */
L = GetLocalLabel ();
AddCodeLine ("ldy #$FF");
g_defcodelabel (L);
AddCodeLine ("iny");
AddCodeLine ("lda %s,y", ED_GetLabelName (&Arg, 0));
AddCodeLine ("bne %s", LocalLabelName (L));
AddCodeLine ("tax");
AddCodeLine ("tya");
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = type_size_t;
/* Bail out, no need for further processing */
goto ExitPoint;
}
/* We will inline strlen for arrays on the stack, if the array is
** completely within the reach of a byte sized index register.
*/
if (ED_IsLocStack (&Arg) && IsArray && IsByteIndex &&
(Arg.IVal - StackPtr) + ECount < 256) {
/* Calculate the true stack offset */
int Offs = ED_GetStackOffs (&Arg, 0);
/* Generate the strlen code */
L = GetLocalLabel ();
AddCodeLine ("ldx #$FF");
AddCodeLine ("ldy #$%02X", (unsigned char) (Offs-1));
g_defcodelabel (L);
AddCodeLine ("inx");
AddCodeLine ("iny");
AddCodeLine ("lda (sp),y");
AddCodeLine ("bne %s", LocalLabelName (L));
AddCodeLine ("txa");
AddCodeLine ("ldx #$00");
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = type_size_t;
/* Bail out, no need for further processing */
goto ExitPoint;
}
/* strlen for a string that is pointed to by a register variable will only
** get inlined if requested on the command line, since we cannot know how
** big the buffer actually is, so inlining is not always safe.
*/
if (ED_IsLocRegister (&Arg) && ED_IsLVal (&Arg) && IsPtr &&
IS_Get (&EagerlyInlineFuncs)) {
/* Generate the strlen code */
L = GetLocalLabel ();
AddCodeLine ("ldy #$FF");
g_defcodelabel (L);
AddCodeLine ("iny");
AddCodeLine ("lda (%s),y", ED_GetLabelName (&Arg, 0));
AddCodeLine ("bne %s", LocalLabelName (L));
AddCodeLine ("tax");
AddCodeLine ("tya");
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = type_size_t;
/* Bail out, no need for further processing */
goto ExitPoint;
}
/* Last check: We will inline a generic strlen routine if inlining was
** requested on the command line, and the code size factor is more than
** 400 (code is 13 bytes vs. 3 for a jsr call).
*/
if (IS_Get (&CodeSizeFactor) > 400 && IS_Get (&EagerlyInlineFuncs)) {
/* Load the expression into the primary */
LoadExpr (CF_NONE, &Arg);
/* Inline the function */
L = GetLocalLabel ();
AddCodeLine ("sta ptr1");
AddCodeLine ("stx ptr1+1");
AddCodeLine ("ldy #$FF");
g_defcodelabel (L);
AddCodeLine ("iny");
AddCodeLine ("lda (ptr1),y");
AddCodeLine ("bne %s", LocalLabelName (L));
AddCodeLine ("tax");
AddCodeLine ("tya");
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = type_size_t;
/* Bail out, no need for further processing */
goto ExitPoint;
}
}
/* Load the expression into the primary */
LoadExpr (CF_NONE, &Arg);
/* Call the strlen function */
AddCodeLine ("jsr _%s", Func_strlen);
/* The function result is an rvalue in the primary register */
ED_MakeRValExpr (Expr);
Expr->Type = type_size_t;
ExitPoint:
/* We expect the closing brace */
ConsumeRParen ();
}
/*****************************************************************************/
/* Code */
/*****************************************************************************/
int FindStdFunc (const char* Name)
/* Determine if the given function is a known standard function that may be
** called in a special way. If so, return the index, otherwise return -1.
*/
{
/* Look into the table for known names */
struct StdFuncDesc* D =
bsearch (Name, StdFuncs, FUNC_COUNT, sizeof (StdFuncs[0]), CmpFunc);
/* Return the function index or -1 */
if (D == 0) {
return -1;
} else {
return D - StdFuncs;
}
}
void HandleStdFunc (int Index, FuncDesc* F, ExprDesc* lval)
/* Generate code for a known standard function. */
{
struct StdFuncDesc* D;
/* Get a pointer to the table entry */
CHECK (Index >= 0 && Index < (int)FUNC_COUNT);
D = StdFuncs + Index;
/* Call the handler function */
D->Handler (F, lval);
}