fixing bugs related to stack, class stack, exceptio stack handling

This commit is contained in:
hyung-hwan 2022-02-27 19:35:47 +00:00
parent 3f03140dcc
commit f4661d018a
5 changed files with 207 additions and 83 deletions

View File

@ -33,7 +33,7 @@
## class declaration with methods.
(defclass X
(defclass X
| x y | ; instance variables
::: | bob jim | ; class variables
@ -44,53 +44,71 @@
(set bob "Bob") ; can access class variables. disallowed to access instance variables
(defun setX (a)
(set self.x a)
;(super.setX a)
(set x a)
)
; instance method. a method name must not collide with instance variable names and class variable names.
; the name can be the same as method names of parent classes.
(defun K (a b)
(self.Y a)
(:self Y a)
(return (+ a b x y))
)
(defun Y (a)
(printf ("Y=>%d [%s]\n", a, bob)
(printf "Y=>%d [%s]\n" a bob)
)
(defun ::: KK (a b)
(printf "K=>%s\n", bob) ; a class method can access class variables but not instance variables
(printf "K=>%s\n" bob) ; a class method can access class variables but not instance variables
(return (+ a b))
)
(set jim (lambda (a b) (+ a b))) ; the anonymous function created is
)
(set jim (lambda (a b) (+ a b))) ; an anonymous function created
)
(set r (object-new X))
(:r Y 10)
(printf ">>%d\n" (:X KK 77 99))
## method invocation
a period isn't a good token to use for chaining method invocation.
super.a().b().c()
push super
send_to_super a
send_to_self b
send_to_self c
(send_to_xxx is lookup + call)
send the message aaa to the receiver
(:self aaa)
send the message aaa to the receiver but let it resolve the method in the superclass side.
(:super aaa)
we need a way to swap the first parameter and the called function
(: a b 2 3 4)
send the message dump to the object pointed to by x with arguments 1, 2, 3.
(:x dump 1 2 3)
(a b 2 3 4)
(a.b.c 20 30 40)
((a:b 20 30):c 30)
## method types
- class method
- class instantiation method
- instance method
## dynamic dispatching by method name
(defclass X
(defun t1 (x) (printf "t1 = %d\n" (+ x x x)))
(defun t2 (x) (printf "t2 = %d\n" (* x x x)))
)
(defun get-name-1() "t1")
(defun get-name-2() "t2")
(set x (object-new X))
(:x (get-name-1) 100) ; must be same as (:x t1 100)
(:x (get-name-2) 100) ; must be same as (:x t2 100)
## Something to look into..
normal function call
(f arg1 arg2 arg3)
(rcv f arg1 arg2)
## dynamic method invocation???
(:X (f) arg1 arg2)
as long as f returns a symbol, it can also invoke a method??

View File

@ -299,6 +299,7 @@ static int find_variable_backward (hcl_t* hcl, const hcl_cnode_t* token, hcl_var
for (fi = hcl->c->fnblk.depth + 1; fi > i; ) /* TOOD: review this loop for correctness */
{
/* 'i' is the function level that hold the class defintion block. the check must not go past it */
if (hcl->c->fnblk.info[--fi].fun_type == FUN_CM)
{
/* the function where this variable is defined is a class method or an plain function block within a class method*/
@ -1550,11 +1551,12 @@ static int compile_break (hcl_t* hcl, hcl_cnode_t* src)
goto inside_loop;
case HCL_CBLK_TYPE_TRY:
/*must emit an instruction to exit from the try loop.*/
/* emit an instruction to exit from the try loop. */
if (emit_byte_instruction(hcl, HCL_CODE_TRY_EXIT, HCL_CNODE_GET_LOC(src)) <= -1) return -1;
break;
case HCL_CBLK_TYPE_CLASS:
/* emit an instruction to exit from the class definition scope being defined */
if (emit_byte_instruction(hcl, HCL_CODE_CLASS_EXIT, HCL_CNODE_GET_LOC(src)) <= -1) return -1;
break;
}
@ -2243,6 +2245,7 @@ static HCL_INLINE int compile_class_p1 (hcl_t* hcl)
}
if (push_clsblk(hcl, &cf->u._class.start_loc, nivars, ncvars, &hcl->c->tv.s.ptr[ivar_start], ivar_len, &hcl->c->tv.s.ptr[cvar_start], cvar_len) <= -1) goto oops;
if (push_cblk(hcl, &cf->u._class.start_loc, HCL_CBLK_TYPE_CLASS) <= -1) goto oops; /* the class block shall be treated as a control block, too */
/* discard the instance variables and class variables in the temporary variable collection buffer
* because they have been pushed to the class block structure */
@ -2299,6 +2302,7 @@ static HCL_INLINE int compile_class_p2 (hcl_t* hcl)
if (emit_byte_instruction(hcl, HCL_CODE_POP_STACKTOP, HCL_CNODE_GET_LOC(cf->operand)) <= -1) return -1;
}
pop_cblk (hcl);
pop_clsblk (hcl); /* end of the class block */
if (emit_byte_instruction(hcl, HCL_CODE_CLASS_PEXIT, HCL_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

View File

@ -140,7 +140,7 @@ static void terminate_all_processes (hcl_t* hcl);
/* ------------------------------------------------------------------------- */
#define HCL_EXSTACK_PUSH(hcl, ctx_, ip_, clsp_) \
#define HCL_EXSTACK_PUSH(hcl, ctx_, ip_, clsp_, sp_) \
do { \
hcl_oop_process_t ap = (hcl)->processor->active; \
hcl_ooi_t exsp = HCL_OOP_TO_SMOOI(ap->exsp); \
@ -152,6 +152,7 @@ static void terminate_all_processes (hcl_t* hcl);
exsp++; ap->slot[exsp] = (ctx_); \
exsp++; ap->slot[exsp] = HCL_SMOOI_TO_OOP(ip_); \
exsp++; ap->slot[exsp] = HCL_SMOOI_TO_OOP(clsp_); \
exsp++; ap->slot[exsp] = HCL_SMOOI_TO_OOP(sp_); \
ap->exsp = HCL_SMOOI_TO_OOP(exsp); \
} while (0)
@ -159,14 +160,15 @@ static void terminate_all_processes (hcl_t* hcl);
do { \
hcl_oop_process_t ap = (hcl)->processor->active; \
hcl_ooi_t exsp = HCL_OOP_TO_SMOOI(ap->exsp); \
exsp -= 3; \
exsp -= 4; \
ap->exsp = HCL_SMOOI_TO_OOP(exsp); \
} while (0)
#define HCL_EXSTACK_POP_TO(hcl, ctx_, ip_, clsp_) \
#define HCL_EXSTACK_POP_TO(hcl, ctx_, ip_, clsp_, sp_) \
do { \
hcl_oop_process_t ap = (hcl)->processor->active; \
hcl_ooi_t exsp = HCL_OOP_TO_SMOOI(ap->exsp); \
sp_ = HCL_OOP_TO_SMOOI(ap->slot[exsp]); exsp--; \
clsp_ = HCL_OOP_TO_SMOOI(ap->slot[exsp]); exsp--; \
ip_ = HCL_OOP_TO_SMOOI(ap->slot[exsp]); exsp--; \
ctx_ = ap->slot[exsp]; exsp--; \
@ -602,7 +604,7 @@ static hcl_oop_process_t make_process (hcl_t* hcl, hcl_oop_context_t c)
static HCL_INLINE void sleep_active_process (hcl_t* hcl, int state)
{
STORE_ACTIVE_SP(hcl);
STORE_ACTIVE_SP (hcl);
/* store the current active context to the current process.
* it is the suspended context of the process to be suspended */
@ -903,10 +905,12 @@ static void dump_process_info (hcl_t* hcl, hcl_bitmask_t log_mask)
static HCL_INLINE void reset_process_stack_pointers (hcl_t* hcl, hcl_oop_process_t proc)
{
#if defined(HCL_DEBUG_VM_PROCESSOR)
HCL_LOG9 (hcl, HCL_LOG_IC | HCL_LOG_DEBUG,
"Processor - process[%zd] SP: %zd ST: %zd EXSP: %zd(%zd) EXST: %zd CLSP: %zd(%zd) CLST: %zd\n",
HCL_LOG4 (hcl, HCL_LOG_IC | HCL_LOG_DEBUG,
"Processor - process[%zd] SP: %zd(%zd) ST: %zd",
HCL_OOP_TO_SMOOI(proc->id),
HCL_OOP_TO_SMOOI(proc->sp), HCL_OOP_TO_SMOOI(proc->st),
HCL_OOP_TO_SMOOI(proc->sp), HCL_OOP_TO_SMOOI(proc->sp) - (-1), HCL_OOP_TO_SMOOI(proc->st));
HCL_LOG6 (hcl, HCL_LOG_IC | HCL_LOG_DEBUG,
" EXSP: %zd(%zd) EXST: %zd CLSP: %zd(%zd) CLST: %zd\n",
HCL_OOP_TO_SMOOI(proc->exsp), HCL_OOP_TO_SMOOI(proc->exsp) - HCL_OOP_TO_SMOOI(proc->st), HCL_OOP_TO_SMOOI(proc->exst),
HCL_OOP_TO_SMOOI(proc->clsp), HCL_OOP_TO_SMOOI(proc->clsp) - HCL_OOP_TO_SMOOI(proc->exst), HCL_OOP_TO_SMOOI(proc->clst));
#endif
@ -930,12 +934,16 @@ static void terminate_process (hcl_t* hcl, hcl_oop_process_t proc)
{
hcl_oop_process_t nrp;
/* terminating the active process */
HCL_ASSERT (hcl, proc->state == HCL_SMOOI_TO_OOP(PROC_STATE_RUNNING));
nrp = find_next_runnable_process(hcl);
STORE_ACTIVE_SP (hcl); /* commit the stack pointer before termination */
unchain_from_processor (hcl, proc, PROC_STATE_TERMINATED);
reset_process_stack_pointers (hcl, proc); /* invalidate the process stack */
proc->current_context = proc->initial_context; /* not needed but just in case */
/* a runnable or running process must not be chanined to the
* process list of a semaphore */
HCL_ASSERT (hcl, (hcl_oop_t)proc->sem == hcl->_nil);
@ -960,11 +968,14 @@ static void terminate_process (hcl_t* hcl, hcl_oop_process_t proc)
}
else
{
/* there are other processes to schedule */
switch_to_process (hcl, nrp, PROC_STATE_TERMINATED);
}
}
else
{
/* termiante a runnable process which is not an actively running process */
HCL_ASSERT (hcl, proc->state == HCL_SMOOI_TO_OOP(PROC_STATE_RUNNABLE));
unchain_from_processor (hcl, proc, PROC_STATE_TERMINATED);
reset_process_stack_pointers (hcl, proc); /* invalidate the process stack */
}
@ -2137,7 +2148,8 @@ static hcl_oop_block_t find_cmethod_noseterr (hcl_t* hcl, hcl_oop_class_t class_
/* TODO: implement method cache */
HCL_ASSERT (hcl, HCL_IS_CLASS(hcl, class_));
HCL_ASSERT (hcl, HCL_IS_SYMBOL(hcl, op_name));
/*HCL_ASSERT (hcl, HCL_IS_SYMBOL(hcl, op_name));*/
HCL_ASSERT (hcl, HCL_OBJ_IS_CHAR_POINTER(op_name));
name.ptr = HCL_OBJ_GET_CHAR_SLOT(op_name);
name.len = HCL_OBJ_GET_SIZE(op_name);
@ -2179,15 +2191,16 @@ static hcl_oop_block_t find_cmethod_noseterr (hcl_t* hcl, hcl_oop_class_t class_
return HCL_NULL;
}
static hcl_oop_block_t find_imethod_noseterr (hcl_t* hcl, hcl_oop_class_t class_, hcl_oop_t op, int to_super, hcl_ooi_t* ivaroff, hcl_oop_class_t* owner)
static hcl_oop_block_t find_imethod_noseterr (hcl_t* hcl, hcl_oop_class_t class_, hcl_oop_t op_name, int to_super, hcl_ooi_t* ivaroff, hcl_oop_class_t* owner)
{
hcl_oocs_t name;
HCL_ASSERT (hcl, HCL_IS_CLASS(hcl, class_));
HCL_ASSERT (hcl, HCL_IS_SYMBOL(hcl, op));
/*HCL_ASSERT (hcl, HCL_IS_SYMBOL(hcl, op_name));*/
HCL_ASSERT (hcl, HCL_OBJ_IS_CHAR_POINTER(op_name));
name.ptr = HCL_OBJ_GET_CHAR_SLOT(op);
name.len = HCL_OBJ_GET_SIZE(op);
name.ptr = HCL_OBJ_GET_CHAR_SLOT(op_name);
name.len = HCL_OBJ_GET_SIZE(op_name);
if (to_super)
{
@ -2234,7 +2247,8 @@ static HCL_INLINE int send_message (hcl_t* hcl, hcl_oop_t rcv, hcl_oop_t msg, in
hcl_ooi_t ivaroff;
int x;
HCL_ASSERT (hcl, HCL_IS_SYMBOL(hcl, msg));
HCL_ASSERT (hcl, HCL_OBJ_IS_CHAR_POINTER(msg));
/*HCL_ASSERT (hcl, HCL_IS_SYMBOL(hcl, msg));*/
/* ============================= */
/* TODO: implement methods cache */
@ -2272,7 +2286,7 @@ static HCL_INLINE int send_message (hcl_t* hcl, hcl_oop_t rcv, hcl_oop_t msg, in
static HCL_INLINE int do_throw (hcl_t* hcl, hcl_oop_t val, hcl_ooi_t ip)
{
hcl_oop_context_t catch_ctx;
hcl_ooi_t catch_ip, clsp;
hcl_ooi_t catch_ip, clsp, sp;
if (HCL_EXSTACK_IS_EMPTY(hcl))
{
@ -2300,7 +2314,7 @@ static HCL_INLINE int do_throw (hcl_t* hcl, hcl_oop_t val, hcl_ooi_t ip)
}
/* pop the exception stack to get information to rewind context */
HCL_EXSTACK_POP_TO (hcl, catch_ctx, catch_ip, clsp);
HCL_EXSTACK_POP_TO (hcl, catch_ctx, catch_ip, clsp, sp);
/* discard unfinished class definitions for the exception thrown.
*
@ -2319,6 +2333,8 @@ static HCL_INLINE int do_throw (hcl_t* hcl, hcl_oop_t val, hcl_ooi_t ip)
SWITCH_ACTIVE_CONTEXT (hcl, catch_ctx);
hcl->ip = catch_ip; /* override the instruction pointer */
hcl->sp = sp; /* restore the stack pointer of the active process context */
/* push the exception value to the stack */
HCL_STACK_PUSH (hcl, val);
return 0;
@ -2893,7 +2909,7 @@ switch_to_next:
/* ------------------------------------------------------------------------- */
static HCL_INLINE int do_return (hcl_t* hcl, hcl_oop_t return_value)
static HCL_INLINE int do_return_from_home (hcl_t* hcl, hcl_oop_t return_value)
{
/* if (hcl->active_context == hcl->processor->active->initial_context) // read the interactive mode note below... */
if ((hcl_oop_t)hcl->active_context->home == hcl->_nil)
@ -2967,13 +2983,13 @@ static HCL_INLINE int do_return (hcl_t* hcl, hcl_oop_t return_value)
}
hcl->active_context->home->ip = HCL_SMOOI_TO_OOP(-1); /* mark that this context has returned */
hcl->ip = -1; /* mark that the active context has returned. saved into hcl->active_context->ip in SWITCH_ACTIVE_CONTEXT() */
hcl->ip = -1; /* mark that the active context has returned. committed to hcl->active_context->ip in SWITCH_ACTIVE_CONTEXT() */
SWITCH_ACTIVE_CONTEXT (hcl, hcl->active_context->home->sender);
/* push the return value to the stack of the new active context */
HCL_STACK_PUSH (hcl, return_value);
#if 0
/* stack dump */
HCL_DEBUG1 (hcl, "****** non local returning %O\n", return_value);
@ -3005,18 +3021,69 @@ static HCL_INLINE void do_return_from_block (hcl_t* hcl)
}
else
{
/* The compiler produces the class_exit instruction and the try_exit instruction
* for return, break, continue in a class defintion scope and in a try-catch scope
* respectively.
/*
TODO: should i restore the class stack pointer too???
let context remeber the it and use it to restore
[CASE 1]
(defclass X
; ....
(return 20) ; the class defintion isn't over, but return is executed?? or simply disallow return in the class context outside a method?
(return 20) ; the class defintion isn't over, but return is executed,
; ....
)
*/
[CASE 2]
(try
(defclass C
(return 200)
(printf "============================\n"))
catch (e)
(printf "EXCEPTION => %O\n" e)
)
[CASE 3]
(defclass C
(try
(return 99)
catch (e)
(printf "EXCEPTOIN => %O\n" e)
)
(printf "============================\n")
)
[CASE 4]
(try
(defclass C
(try
(return 99)
catch (e)
(printf "EXCEPTOIN => %O\n" e)
)
(printf "============================\n")
)
catch (e)
(printf "EXCEPTOIN => %O\n" e)
)
[CASE 5]
(try
(defclass D
(defclass C
(try
(return 99)
catch (e)
(printf "EXCEPTOIN => %O\n" e)
)
(printf "============================\n")
)
}
catch (e)
(printf "EXCEPTOIN => %O\n" e)
)
* the actual return instruction handler doesn't need to care about the
* class stack and exception stack.
*/
/* it is a normal block return as the active block context
* is not the initial context of a process */
@ -3063,9 +3130,34 @@ static int execute (hcl_t* hcl)
if (hcl->abort_req < 0) goto oops;
if (hcl->abort_req > 0 || (!hcl->no_proc_switch && switch_process_if_needed(hcl) == 0)) break;
if (HCL_UNLIKELY(hcl->ip >= HCL_FUNCTION_GET_CODE_SIZE(hcl->active_function)))
if (HCL_UNLIKELY(hcl->ip < 0 || hcl->ip >= HCL_FUNCTION_GET_CODE_SIZE(hcl->active_function)))
{
HCL_DEBUG2 (hcl, "Stopping execution as IP reached the end of bytecode(%zu) - SP %zd\n", hcl->code.bc.len, hcl->sp);
if (hcl->ip < 0)
{
/* do_return_from_home() implements a simple check against a dead context.
* but the check is far from perfect. there are many ways to return from an
* active context and enter a dead context thereafter.
(defun t(f)
(set q (lambda()
(printf "hello word\n")
(return-from-home 200)
))
(f)
)
(defun x()
(t (lambda() (return-from-home 100)))
(printf ">>>>>>>>>>>>>>>>>>>>>>>>\n");
)
(x) ; x is exited by (return-from-home 100) triggered by (f)
(printf "------------------------\n")
(q) ; (return-from-home 200) exits t and since t is called from x, it flows back to the dead x.
*/
HCL_DEBUG1 (hcl, "Stopping execution as a dead context gets active - IP %zd\n", hcl->ip);
}
else
{
HCL_DEBUG2 (hcl, "Stopping execution as IP reached the end of bytecode(%zu) - IP %zd\n", hcl->code.bc.len, hcl->ip);
}
return_value = hcl->_nil;
goto handle_return;
}
@ -3531,7 +3623,8 @@ if (do_throw(hcl, hcl->_nil, fetched_instruction_pointer) <= -1)
catch_ip = hcl->ip + b1;
/* TODO: ip overflow check? */
clsp = HCL_CLSTACK_GET_SP(hcl);
HCL_EXSTACK_PUSH (hcl, hcl->active_context, catch_ip, clsp);
HCL_EXSTACK_PUSH (hcl, hcl->active_context, catch_ip, clsp, hcl->sp);
break;
}
@ -3541,10 +3634,12 @@ if (do_throw(hcl, hcl->_nil, fetched_instruction_pointer) <= -1)
FETCH_PARAM_CODE_TO (hcl, b1);
LOG_INST_1 (hcl, "try_enter2 %zu", b1);
catch_ip = hcl->ip + MAX_CODE_JUMP + b1;
/* TODO: ip overflow check? */
clsp = HCL_CLSTACK_GET_SP(hcl);
HCL_EXSTACK_PUSH (hcl, hcl->active_context, catch_ip, clsp);
HCL_EXSTACK_PUSH (hcl, hcl->active_context, catch_ip, clsp, hcl->sp);
break;
}
@ -3860,7 +3955,8 @@ if (do_throw(hcl, hcl->_nil, fetched_instruction_pointer) <= -1)
handle_send_2:
rcv = HCL_STACK_GETRCV(hcl, b1);
op = HCL_STACK_GETOP(hcl, b1);
if (!HCL_IS_SYMBOL(hcl, op))
/*if (!HCL_IS_SYMBOL(hcl, op))*/
if (!HCL_OBJ_IS_CHAR_POINTER(op))
{
hcl_seterrbfmt (hcl, HCL_ECALL, "unable to send %O to %O - invalid message", op, rcv); /* TODO: change to HCL_ESEND?? */
goto cannot_send;
@ -3879,6 +3975,7 @@ if (do_throw(hcl, hcl->_nil, fetched_instruction_pointer) <= -1)
{
hcl_seterrbfmt (hcl, HCL_ECALL, "unable to send %O to %O - invalid receiver", op, rcv); /* TODO: change to HCL_ESEND?? */
cannot_send:
//HCL_STACK_POPS (hcl, b1 + 2); /* pop the receiver, message, and arguments as the call fails. TODO: check if this clearing is correct */
if (do_throw_with_internal_errmsg(hcl, fetched_instruction_pointer) >= 0) break;
goto oops_with_errmsg_supplement;
}
@ -4315,7 +4412,8 @@ if (do_throw(hcl, hcl->_nil, fetched_instruction_pointer) <= -1)
break;
case HCL_CODE_RETURN_STACKTOP:
/* this implements the non-local return. the non-local return is not compatible with stack based try-catch implementation. */
/* [NOTE] this implements the non-local return. the non-local return is not compatible with stack based try-catch implementation.
* [TODO] can make it compatiable? */
LOG_INST_0 (hcl, "return_stacktop");
return_value = HCL_STACK_GETTOP(hcl);
HCL_STACK_POP (hcl);
@ -4327,7 +4425,7 @@ if (do_throw(hcl, hcl->_nil, fetched_instruction_pointer) <= -1)
handle_return:
hcl->last_retv = return_value;
if (do_return(hcl, return_value) <= -1) goto oops;
if (do_return_from_home(hcl, return_value) <= -1) goto oops;
break;
case HCL_CODE_RETURN_FROM_BLOCK:

View File

@ -566,10 +566,10 @@ void hcl_gc (hcl_t* hcl)
{
HCL_ASSERT (hcl, (hcl_oop_t)hcl->processor != hcl->_nil);
HCL_ASSERT (hcl, (hcl_oop_t)hcl->processor->active != hcl->_nil);
/* store the stack pointer to the active process */
HCL_ASSERT (hcl, HCL_IS_PROCESS(hcl, hcl->processor->active));
/* commit the stack pointer to the active process */
hcl->processor->active->sp = HCL_SMOOI_TO_OOP(hcl->sp);
/* store the instruction pointer to the active context */
/* commit the instruction pointer to the active context */
hcl->active_context->ip = HCL_SMOOI_TO_OOP(hcl->ip);
}

View File

@ -608,7 +608,7 @@ struct hcl_block_t
hcl_oop_t tmpr_mask; /* smooi */
hcl_oop_context_t home; /* home context */
hcl_oop_t ip; /* smooi. instruction pointer where the byte code begins in home->origin */
hcl_oop_t ip; /* smooi. instruction pointer where the byte code begins in home->base */
};
struct hcl_context_t
@ -1730,12 +1730,16 @@ struct hcl_t
(hcl)->processor->active->slot[(hcl)->sp] = v; \
} while (0)
#define HCL_STACK_GET(hcl,v_sp) ((hcl)->processor->active->slot[v_sp])
#define HCL_STACK_SET(hcl,v_sp,v_obj) ((hcl)->processor->active->slot[v_sp] = v_obj)
#define HCL_STACK_GET(hcl,sp_) ((hcl)->processor->active->slot[sp_])
#define HCL_STACK_SET(hcl,sp_,obj_) ((hcl)->processor->active->slot[sp_] = obj_)
#define HCL_STACK_GETTOP(hcl) HCL_STACK_GET(hcl, (hcl)->sp)
#define HCL_STACK_SETTOP(hcl,v_obj) HCL_STACK_SET(hcl, (hcl)->sp, v_obj)
#define HCL_STACK_SETTOP(hcl,obj_) HCL_STACK_SET(hcl, (hcl)->sp, obj_)
/* [NOTE]
* the following macros don't commit the active stack pointer(hcl->sp)
* to hcl->processor->active->sp immediately.
*/
#define HCL_STACK_POP(hcl) ((hcl)->sp = (hcl)->sp - 1)
#define HCL_STACK_POPS(hcl,count) ((hcl)->sp = (hcl)->sp - (count))
#define HCL_STACK_POP_TO(hcl,v) \