From 03ad51c9ab3b637016f8d961c7ede2eef03f19a6 Mon Sep 17 00:00:00 2001 From: cassowarii <2374677+cassowarii@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:45:43 -0700 Subject: [PATCH] first pass at pointers. they do work! mostly! --- src/compile/emit.c | 138 ++++++++++++++++++++++++++++++++++++------------- src/compile/ir.c | 55 ++++++++++++-------- src/compile/ir.h | 22 ++++---- src/compile/print_ir.c | 14 ++--- src/lib/sentinel.c | 3 ++ src/lib/sentinel.h | 1 + src/parse/ast.h | 2 +- src/vm/block.h | 2 +- src/vm/bytecode.h | 1 + src/vm/exec.c | 6 ++- src/vm/operations.c | 32 ++++++++++++ src/vm/operations.h | 4 ++ 12 files changed, 201 insertions(+), 79 deletions(-) diff --git a/src/compile/emit.c b/src/compile/emit.c index 84b5377..fb38ba6 100644 --- a/src/compile/emit.c +++ b/src/compile/emit.c @@ -173,13 +173,40 @@ void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt) { if (cm->debugmode) debug("\n"); break; case IR_S_ASSIGN: - compile_expr(cm, stmt->assign.expr); - if (stmt->assign.var->is_upvalue) { - EMIT(BC_ST_UPVAL); + compile_expr(cm, stmt->assign.value); + if (stmt->assign.where->type == IR_E_VAR) { + /* direct assignment to some variable name or other */ + sbIrVariable *var = stmt->assign.where->var; + if (var->is_reference) { + if (!var->is_upvalue) { + /* let &a = &b; a = 5 # <-- should set b, too! */ + EMIT(BC_LD_VAR); + } else { + /* same, but for upvalue */ + EMIT(BC_LD_UPVAL); + } + EARG(var->slot_id); + EMIT(BC_REF_PUT); + } else { + if (!var->is_upvalue) { + /* normal assignment to normal variable */ + EMIT(BC_ST_VAR); + } else { + /* assignment to normal upvalue variable */ + EMIT(BC_ST_UPVAL); + } + EARG(var->slot_id); + } + } else if (stmt->assign.where->type == IR_E_OP && stmt->assign.where->op.type == AST_OP_DEREF) { + /* assignment to like *<....> = <....> */ + compile_expr(cm, stmt->assign.where->op.left); + /* this should leave us with a pointer to expr on top of stack */ + EMIT(BC_REF_PUT); } else { - EMIT(BC_ST_VAR); + /* TODO: Now we can support stuff like index-assignment also if we want to. + * Probably do this next */ + PANIC("This type of assignment operation is not supported!"); } - EARG(stmt->assign.var->slot_id); break; case IR_S_BIND: if (stmt->bind.values) { @@ -270,30 +297,42 @@ void compile_hash(sbVmCompiler *cm, sbIrExpr *expr) { void compile_bind_list(sbVmCompiler *cm, sbIrBindList *list) { for (sbIrBindList *considering = list; considering; considering = considering->next) { sbIrExpr *elem = considering->this; - if (elem->type == IR_E_OP && elem->op.type == AST_OP_SPLAT) { - /* okay. we want to leave some number of elements on the stack - * for whatever other arguments there are. currently we have the - * total count on top of the stack. so, we'll reduce the count - * by the number we want to save, gather into a list, and assign - * to that, then replace the count we wanted to keep on the stack */ - if (list->pre_splat_count > 0) { - EMIT(BC_LD_IMM); - EARG(list->pre_splat_count); - EMIT(BC_OP_SUB); - } - /* create list of this length and store in thing */ - EMIT(BC_LIST_GATHER); - /* TODO actually, vv THIS vv should be a recursive call. otherwise, - * we don't actually check that it's a variable that the "..." is - * attached to, and we may fail in weird cases like "...2". but we - * need to restructure the BindList data structure. */ - EMIT(BC_ST_VAR); - EARG(elem->op.left->var->slot_id); - if (list->pre_splat_count > 0) { - /* put pre splat count back if we need it, to bind the rest of - * the variables */ - EMIT(BC_LD_IMM); - EARG(list->pre_splat_count); + if (elem->type == IR_E_OP) { + if (elem->op.type == AST_OP_SPLAT) { + /* okay. we want to leave some number of elements on the stack + * for whatever other arguments there are. currently we have the + * total count on top of the stack. so, we'll reduce the count + * by the number we want to save, gather into a list, and assign + * to that, then replace the count we wanted to keep on the stack */ + if (list->pre_splat_count > 0) { + EMIT(BC_LD_IMM); + EARG(list->pre_splat_count); + EMIT(BC_OP_SUB); + } + /* create list of this length and store in thing */ + EMIT(BC_LIST_GATHER); + /* TODO actually, vv THIS vv should be a recursive call. otherwise, + * we don't actually check that it's a variable that the "..." is + * attached to, and we may fail in weird cases like "...2". but we + * need to restructure the BindList data structure. */ + EMIT(BC_ST_VAR); + EARG(elem->op.left->var->slot_id); + if (list->pre_splat_count > 0) { + /* put pre splat count back if we need it, to bind the rest of + * the variables */ + EMIT(BC_LD_IMM); + EARG(list->pre_splat_count); + } + } else if (elem->op.type == AST_OP_REF) { + /* this should also probably be some kind of recursive thing, to handle + * situations like let &&a = whatever. (if that's even doable...?) + * but right now we just only permit & before variable names on the + * left side of a let */ + /* storing here is the same as normal, because the actual variable + * slot saves the reference. however, assignment etc. is statically + * known to work differently so we will adjust those */ + EMIT(BC_ST_ARG); + EARG(elem->op.left->var->slot_id); } } else if (elem->type == IR_E_VAR) { /* normal sequence, no splat (so far): top thing goes in this @@ -306,15 +345,20 @@ void compile_bind_list(sbVmCompiler *cm, sbIrBindList *list) { } } +void compile_ref(sbVmCompiler *cm, sbIrExpr *expr); void compile_op(sbVmCompiler *cm, sbAstOp op); void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) { switch(expr->type) { case IR_E_OP: - compile_expr(cm, expr->op.left); - if (expr->op.right) { - compile_expr(cm, expr->op.right); + if (expr->op.type == AST_OP_REF) { + compile_ref(cm, expr->op.left); + } else { + compile_expr(cm, expr->op.left); + if (expr->op.right) { + compile_expr(cm, expr->op.right); + } + compile_op(cm, expr->op.type); } - compile_op(cm, expr->op.type); break; case IR_E_CALL: /* calling convention: store argument count on stack */ @@ -338,6 +382,12 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) { EMIT(BC_LD_VAR); } EARG(expr->var->slot_id); + + /* when a variable that is_reference is referenced + * in non-assignment context, it automatically dereferences */ + if (expr->var->is_reference) { + EMIT(BC_OP_DEREF); + } break; case IR_E_FUNC: if (expr->func.bound.size > 0) { @@ -346,12 +396,10 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) { /* BC_LD_UPREF: closed over variables are always on * the heap, so all upval refs are rrefs */ EMIT(BC_LD_UPREF); - } else if ((*var)->closed_over) { + } else { /* BC_LD_RREF: everything we are closing over from the * current scope needs to move to the heap */ EMIT(BC_LD_RREF); - } else { - PANIC("cannot have a direct variable in closure!"); } EARG((*var)->slot_id); } @@ -394,6 +442,23 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) { } } +/* compiling the left hand side of an assignment (that isn't straightforwardly + * a variable) to return some kind of reference */ +void compile_assign_left(sbVmCompiler *cm, sbIrExpr *expr) { +} + +/* when we see an expression of the form '&expr', we have to + * handle this specially depending on what 'expr' is (and sometimes + * we just aren't allowed to do it) */ +void compile_ref(sbVmCompiler *cm, sbIrExpr *expr) { + if (expr->type == IR_E_VAR) { + EMIT(BC_LD_RREF); + EARG(expr->var->slot_id); + } else { + PANIC("cannot & non-variable-name! (todo)"); + } +} + void compile_op(sbVmCompiler *cm, sbAstOp op) { switch (op) { case AST_OP_ADD: EMIT(BC_OP_ADD); break; @@ -412,6 +477,7 @@ void compile_op(sbVmCompiler *cm, sbAstOp op) { case AST_OP_OR: EMIT(BC_OP_OR); break; case AST_OP_INDEX: EMIT(BC_OP_INDEXVAL); break; case AST_OP_RANGEINDEX: EMIT(BC_OP_RANGEINDEX); break; + case AST_OP_DEREF: EMIT(BC_OP_DEREF); break; /* op range is currently only used in rangeindex; just pass them to it directly */ case AST_OP_RANGE: break; case AST_OP_DIVBY: EMIT(BC_OP_MOD, BC_LD_IMM); EARG(0); EMIT(BC_OP_EQ); break; diff --git a/src/compile/ir.c b/src/compile/ir.c index 9461593..232610d 100644 --- a/src/compile/ir.c +++ b/src/compile/ir.c @@ -142,8 +142,9 @@ static sbIrVariable *create_upvalue(sbIrChunk *ck, sbIrVariable *v, usize slot_i * scope. */ new_var->introduced = v->introduced; - /* Upvalues actually can be closed over, but when initially created they won't be. */ - new_var->closed_over = FALSE; + /* An upvalue can be to a refaliased variable! So we have to remember to treat it + * specially too, just like the original */ + new_var->is_reference = v->is_reference; /* This tells us where in the sequence of nested scopes this variable exists. */ new_var->mapping_index = v->mapping_index; @@ -158,7 +159,6 @@ static sbIrVariable *create_upvalue(sbIrChunk *ck, sbIrVariable *v, usize slot_i * value with that variable's value */ static sbIrVariable *register_upvalue(hIrChunk ck, usize variable_index) { varmapentry *e = &BUFFER_INDEX(ck->program->varmapping, varmapentry, variable_index); - e->var->closed_over = TRUE; sbIrVariable *existing_upvalue = NULL; BUFFER_ITER(ck->closed_vars, sbIrVariable*, var) { @@ -315,12 +315,8 @@ static sbIrExpr *expr_value(hIrChunk ck, hVal *value) { static sbIrExpr *expr_func(hIrChunk ck, sbIrChunk *func) { /* when creating a 'literal' of a function 'func' inside another chunk 'ck', * 'func' tells us which variables it closes over from the outer scope. we need - * to convert these into references to variables in ck's scope so that it knows - * which of its variables to save for this particular function. (it also knows - * to heap-allocate those variables as sbRef because their closed_over flag is - * set, but if we have multiple functions closing over different variables we - * need to know which is which, and also these closed variables might be upvalues - * to ck as well. */ + * to remember to move these variables to the heap and provide them when a + * closure is created (if any). */ sbIrExpr *e = new_expr(ck, &(sbIrExpr) { .type = IR_E_FUNC, .func.chunk = func, @@ -490,11 +486,11 @@ static void put_expr(hIrChunk ck, sbIrExpr *expr) { }); } -static void put_assign(hIrChunk ck, sbIrVariable *var, sbIrExpr *expr) { +static void put_assign(hIrChunk ck, sbIrExpr *where, sbIrExpr *value) { put_ir_stmt(ck, &(sbIrStmt) { .type = IR_S_ASSIGN, - .assign.var = var, - .assign.expr = expr, + .assign.where = where, + .assign.value = value, }); } @@ -619,7 +615,7 @@ static usize compile_ast_stmtseq_open(hIrChunk ck, sbAst seqast, flag implicit_r sbIrVariable *V1 = lookup_node_var(ck, node->seq.left); sbIrChunk *C1 = compile_ast_function(ck->program, params, body); sbIrExpr *E1 = expr_func(ck, C1); - put_assign(ck, V1, E1); + put_assign(ck, expr_var(ck, V1), E1); } if (node->type == AST_NODE_LET) { @@ -704,9 +700,8 @@ static void compile_ast_stmtseq(hIrChunk ck, sbAst seqast, flag implicit_return) } static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { - sbIrExpr *E1; + sbIrExpr *E1, *E2; sbIrLabel *L1, *L2; - sbIrVariable *V1; sbAst N1, N2; switch (node->type) { case AST_NODE_RETURN: @@ -768,9 +763,9 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { N1 = node->seq.left; /* things to bind to */ N2 = node->seq.right; /* values to assign */ while (N1 != NO_NODE && N2 != NO_NODE) { - V1 = compile_ast_var(ck, N1->seq.right); - E1 = compile_ast_expr(ck, N2->seq.right, TRUE); - put_assign(ck, V1, E1); + E1 = compile_ast_expr(ck, N1->seq.right, TRUE); + E2 = compile_ast_expr(ck, N2->seq.right, TRUE); + put_assign(ck, E1, E2); N1 = N1->seq.left; N2 = N2->seq.left; } @@ -944,9 +939,9 @@ static sbIrExpr *compile_ast_hash(hIrChunk ck, sbAst node) { return list; } -/* compile one individual element to bind to: might be a bare variable name, - * or might be "...name", or might be some other expression, in which case - * we bind by matching it exactly? (TODO) */ +/* compile one individual element to bind to: might be a bare variable name, or might + * be "...name" or "&name", or might be some other expression, in which case we bind + * by matching it exactly? (TODO) */ static sbIrExpr *compile_ast_binding(hIrChunk ck, sbAst node, flag should_create_var, sbIrNameIntroduceType type) { if (node->type == AST_NODE_NAME) { sbIrVariable *V1; @@ -957,6 +952,22 @@ static sbIrExpr *compile_ast_binding(hIrChunk ck, sbAst node, flag should_create } V1->introduced = type; return expr_var(ck, V1); + } else if (node->type == AST_NODE_OP && node->op.type == AST_OP_REF) { + /* let &a = f() or some such: this actually makes 'a' a different type + * of variable, which is aliased to whatever the reference is. so, + * assigning to a for example is more like *a = whatever. so we actually + * have to save the reference itself in this variable slot, and remember + * that assigning to it, etc., does something different */ + sbIrExpr *ref_to = compile_ast_binding(ck, node->op.left, should_create_var, type); + if (ref_to->type == IR_E_VAR) { + /* from the declaration, we know this variable is a reference-variable. + * so we can rewrite assignments etc to it at the EMIT stage. */ + ref_to->var->is_reference = TRUE; + return expr_op(ck, AST_OP_REF, ref_to, NULL); + } else { + chunk_error(ck, "cannot destructure into a `&<...>`"); + return NULL; + } } else if (node->type == AST_NODE_OP) { sbIrExpr *left = NULL, *right = NULL; if (node->op.left != NO_NODE) { @@ -1071,7 +1082,7 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node, flag list_context) { PANIC("Pipe cannot currently be used in this context. I will fix it"); } sbIrExpr *left = compile_ast_expr(ck, node->op.left, FALSE); - put_assign(ck, pipe_var(ck), left); + put_assign(ck, expr_var(ck, pipe_var(ck)), left); ck->pipe_var_in_use = TRUE; sbIrExpr *right = compile_ast_expr(ck, node->op.right, FALSE); ck->pipe_var_in_use = FALSE; diff --git a/src/compile/ir.h b/src/compile/ir.h index 8b9c107..bbba211 100644 --- a/src/compile/ir.h +++ b/src/compile/ir.h @@ -62,18 +62,18 @@ typedef enum sbIrNameIntroduceType { } sbIrNameIntroduceType; typedef struct sbIrLabel { - flag found_yet; + flag found_yet : 1; + i32 id : 31; struct sbIrLabel *aliased_to; - usize id; - usize block_position; + i32 block_position; } sbIrLabel; typedef struct sbIrVariable { - usize slot_id; - sbIrNameIntroduceType introduced; - flag closed_over; - flag is_upvalue; - usize mapping_index; + flag is_reference : 1; + flag is_upvalue : 1; + sbIrNameIntroduceType introduced : 4; + i32 mapping_index : 26; + i32 slot_id; } sbIrVariable; typedef struct sbIrJump { @@ -113,8 +113,8 @@ typedef struct sbIrExpr { } sbIrExpr; typedef struct sbIrAssign { - sbIrVariable *var; - sbIrExpr *expr; + sbIrExpr *where; + sbIrExpr *value; } sbIrAssign; typedef struct sbIrBindList { @@ -143,7 +143,7 @@ struct sbIrProgram; typedef struct sbIrChunk { struct sbIrProgram *program; flag pipe_var_in_use : 1; - i32 id; + i32 id : 31; i16 num_args; i16 num_upvalues; i32 label_count; diff --git a/src/compile/print_ir.c b/src/compile/print_ir.c index 096cbd9..9f3ef8b 100644 --- a/src/compile/print_ir.c +++ b/src/compile/print_ir.c @@ -28,14 +28,14 @@ void sbIrProgram_print(hIrProgram ir) { /* --- */ static void print_var(sbIrVariable *v) { - if (v->closed_over) { + if (v->is_reference) { debug("special "); } if (v->is_upvalue) { - debug("upvalue %zu", v->slot_id); + debug("upvalue %d", v->slot_id); } else { - debug("variable %zu", v->slot_id); + debug("variable %d", v->slot_id); } } @@ -134,10 +134,10 @@ static void print_stmt(sbIrStmt *s) { debug("]\n"); break; case IR_S_LABEL: - debug("label %zu:\n", s->label->id); + debug("label %d:\n", s->label->id); break; case IR_S_JUMP: - debug(" jump to label %zu", s->jump.label->id); + debug(" jump to label %d", s->jump.label->id); if (s->jump.condition) { if (s->jump.inverted) { debug(" unless "); @@ -164,9 +164,9 @@ static void print_stmt(sbIrStmt *s) { break; case IR_S_ASSIGN: debug(" "); - print_var(s->assign.var); + print_expr(s->assign.where); debug(" = "); - print_expr(s->assign.expr); + print_expr(s->assign.value); debug("\n"); break; default: diff --git a/src/lib/sentinel.c b/src/lib/sentinel.c index 1a1261e..e7bc792 100644 --- a/src/lib/sentinel.c +++ b/src/lib/sentinel.c @@ -5,6 +5,7 @@ hSymbol S_OP_ADD, S_OP_SUB, S_OP_MUL, S_OP_DIV, S_OP_INTDIV, S_OP_MOD; hSymbol S_OP_EQ, S_OP_LT, S_OP_LE; hSymbol S_OP_CALL, S_OP_SET, S_OP_INDEX, S_OP_SETINDEX, S_OP_RANGEINDEX, S_OP_SETRANGEINDEX; +hSymbol S_OP_DEREF, S_OP_REFSET; hSymbol S_OP_TO_STRING, S_OP_TO_INT, S_OP_TO_FLOAT, S_OP_TO_LIST, S_OP_TO_HASH; void sbLib_create_sentinels() { @@ -19,6 +20,8 @@ void sbLib_create_sentinels() { S_OP_LE = SENTINEL(""); S_OP_CALL = SENTINEL(""); S_OP_SET = SENTINEL(""); + S_OP_DEREF = SENTINEL(""); + S_OP_REFSET = SENTINEL(""); S_OP_INDEX = SENTINEL(""); S_OP_SETINDEX = SENTINEL(""); S_OP_RANGEINDEX = SENTINEL(""); diff --git a/src/lib/sentinel.h b/src/lib/sentinel.h index 4a394d8..b0dfa9b 100644 --- a/src/lib/sentinel.h +++ b/src/lib/sentinel.h @@ -3,6 +3,7 @@ extern hSymbol S_OP_ADD, S_OP_SUB, S_OP_MUL, S_OP_DIV, S_OP_INTDIV, S_OP_MOD; extern hSymbol S_OP_EQ, S_OP_LT, S_OP_LE; extern hSymbol S_OP_CALL, S_OP_SET, S_OP_INDEX, S_OP_SETINDEX, S_OP_RANGEINDEX, S_OP_SETRANGEINDEX; +extern hSymbol S_OP_DEREF, S_OP_REFSET; extern hSymbol S_OP_TO_STRING, S_OP_TO_INT, S_OP_TO_FLOAT, S_OP_TO_LIST, S_OP_TO_HASH; void sbLib_create_sentinels(); diff --git a/src/parse/ast.h b/src/parse/ast.h index dc317dc..415403a 100644 --- a/src/parse/ast.h +++ b/src/parse/ast.h @@ -54,7 +54,6 @@ typedef enum sbAstOp { AST_OP_LT = '<', AST_OP_GT = '>', AST_OP_REF = '&', - AST_OP_DEREF = '*', AST_OP_PIPE = '|', AST_OP_LE = 128, AST_OP_GE, @@ -76,6 +75,7 @@ typedef enum sbAstOp { AST_OP_IN, AST_OP_SEND, AST_OP_SPLAT, + AST_OP_DEREF, } sbAstOp; typedef struct sbAstNode { diff --git a/src/vm/block.h b/src/vm/block.h index 05bfe3b..b3be746 100644 --- a/src/vm/block.h +++ b/src/vm/block.h @@ -13,7 +13,7 @@ /* dynamic sized block that we can more easily add * things to while compiling */ typedef struct sbVmCompiler { - flag debugmode; + flag debugmode : 1; sbBuffer bytecode; sbBuffer constants; sbBuffer label_positions; diff --git a/src/vm/bytecode.h b/src/vm/bytecode.h index bff3a32..364aa9d 100644 --- a/src/vm/bytecode.h +++ b/src/vm/bytecode.h @@ -31,6 +31,7 @@ typedef enum sbOpcode { BC_JT, // jump if true BC_JF, // jump if false BC_SEND, // like call, but jump to object method + BC_REF_PUT, // assign through pointer BC_OP_EQ, // == BC_OP_NOT, // boolean not BC_OP_ADD, // add diff --git a/src/vm/exec.c b/src/vm/exec.c index 84568ba..da484a4 100644 --- a/src/vm/exec.c +++ b/src/vm/exec.c @@ -432,6 +432,9 @@ void execute_instruction(hVm vm) { case BC_SEND: sbLib_resolve_method(vm); break; + case BC_REF_PUT: + sbV_refput(vm); + break; case BC_NUMARG: param = get_param(vm); v = pop_stack(vm); @@ -572,7 +575,8 @@ void execute_instruction(hVm vm) { push_stack_immediate(vm, &res); break; case BC_OP_DEREF: - PANIC("todo"); + sbV_deref(vm); + break; case BC_ALLOC_VARS: /* TODO check for overflow */ param = get_param(vm); diff --git a/src/vm/operations.c b/src/vm/operations.c index d299bfe..fddb958 100644 --- a/src/vm/operations.c +++ b/src/vm/operations.c @@ -194,3 +194,35 @@ void sbV_index_set(hVal *obj, hVal *key, hVal *value) { PANIC("todo %lld", (long long)obj->type); } } + +void sbV_deref(hVm vm) { + hVal *a = sbVm_peek(vm, 0); + if (a->type == IT_REF) { + hVar v = sbVar_deref(a); + sbVm_pop(vm); + hVal *result = sbVar_get_value_ptr(v); + sbVm_push(vm, result); + } else { + sbVm_push_immediate(vm, &HVSYM(S_OP_DEREF)); /* a op::deref */ + sbVm_swap(vm); /* op::deref a */ + sbVm_push_immediate(vm, &HVINT(1)); /* op::deref a 1 */ + sbVm_swap(vm); /* op::deref 1 a */ + sbLib_resolve_method(vm); + } +} + +void sbV_refput(hVm vm) { + hVal *ref = sbVm_peek(vm, 0); + hVal *val = sbVm_peek(vm, 1); + if (ref->type == IT_REF) { + hVar v = sbVar_deref(ref); + sbVar_set_value(v, val); + sbVm_npop(vm, 2); + } else { + sbVm_push_immediate(vm, &HVSYM(S_OP_REFSET)); /* val ref op::refset */ + sbVm_swap(vm); /* val op::refset ref */ + sbVm_push_immediate(vm, &HVINT(2)); /* val op::refset ref 2 */ + sbVm_swap(vm); /* val op::refset 2 a */ + sbLib_resolve_method(vm); + } +} diff --git a/src/vm/operations.h b/src/vm/operations.h index c8b9c00..ddccd00 100644 --- a/src/vm/operations.h +++ b/src/vm/operations.h @@ -29,3 +29,7 @@ void sbV_index_value(hVm vm); hVal sbV_rangeindex(hVal *a, hVal *b, hVal *c); void sbV_index_set(hVal *obj, hVal *key, hVal *value); + +void sbV_deref(hVm vm); + +void sbV_refput(hVm vm); -- 1.8.3.1