diff --git a/VERSIONS b/VERSIONS index 927ab8bbd..2d4385113 100644 --- a/VERSIONS +++ b/VERSIONS @@ -2,6 +2,24 @@ ChucK VERSIONS log ------------------ +1.5.1.7 (October 2023) +======= +- (added) new type 'vec2' 2D vector type + for 2D animation (ChuGL) as well as texture mapping UV or ST + coordinates; access fields with .x/.y OR .u/.v OR .s/.t +- (added) new examples/vector/vec2.ck +- (reworked) memory managment across function calls that return + Object types; now correctly tracks all instances within a + statement and balances reference counts; previously there was + a chance that object refcount were not correctly released, + causing incorrect behavior including memory leaks. + examples (assuming foo() returns an Object of some kind): + `foo().bar;` or `foo(), foo()` (multi-expression stmt) +- (fixed) incorrect reference count for certain usages of 'null' + where the expression was interpreted incorrectly as a + function call, e.g., obj == null + + 1.5.1.6 (October 2023) patch release ======= - (fixed) a serious issue accessing vec3/vec4 fields (.x, .y, .z. .w) diff --git a/examples/vector/vecs-cast.ck b/examples/vector/vecs-cast.ck new file mode 100644 index 000000000..647eb6e6e --- /dev/null +++ b/examples/vector/vecs-cast.ck @@ -0,0 +1,13 @@ +// casting between vec2, vec3, vec4 + +// vec2 to vec3 +@(1,2) $ vec4 => vec4 v4; <<< v4 >>>; +// vec2 to vec4 +@(1,2) $ vec4 => v4; <<< v4 >>>; +// vec3 to vec4 +@(1,2,3) $ vec4 => v4; <<< v4 >>>; + +// vec4 to vec3 +@(1,2,3,4) $ vec3 => vec3 v3; <<< v3 >>>; +// vec4 to vec2 +@(1,2,3,4) $ vec2 => vec2 v2; <<< v2 >>>; diff --git a/src/core/chuck_absyn.h b/src/core/chuck_absyn.h index 2a98240d1..f4a99a26b 100644 --- a/src/core/chuck_absyn.h +++ b/src/core/chuck_absyn.h @@ -428,6 +428,8 @@ struct a_Stmt_ ae_Stmt_Type s_type; // used to track control paths in non-void functions t_CKBOOL allControlPathsReturn; // 1.5.1.0 (ge) added + // number of obj refs that needs releasing after | 1.5.1.7 + t_CKUINT numObjsToRelease; // mushed into one! union diff --git a/src/core/chuck_emit.cpp b/src/core/chuck_emit.cpp index c3cb12261..92b4ad5d7 100644 --- a/src/core/chuck_emit.cpp +++ b/src/core/chuck_emit.cpp @@ -62,7 +62,7 @@ t_CKBOOL emit_engine_emit_break( Chuck_Emitter * emit, a_Stmt_Break br ); t_CKBOOL emit_engine_emit_continue( Chuck_Emitter * emit, a_Stmt_Continue cont ); t_CKBOOL emit_engine_emit_return( Chuck_Emitter * emit, a_Stmt_Return stmt ); t_CKBOOL emit_engine_emit_switch( Chuck_Emitter * emit, a_Stmt_Switch stmt ); -t_CKBOOL emit_engine_emit_exp( Chuck_Emitter * emit, a_Exp exp, t_CKBOOL doAddRef = FALSE ); +t_CKBOOL emit_engine_emit_exp( Chuck_Emitter * emit, a_Exp exp, t_CKBOOL doAddRef = FALSE, a_Stmt enclosingStmt = NULL ); t_CKBOOL emit_engine_emit_exp_binary( Chuck_Emitter * emit, a_Exp_Binary binary ); t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a_Exp rhs, a_Exp_Binary binary ); t_CKBOOL emit_engine_emit_op_chuck( Chuck_Emitter * emit, a_Exp lhs, a_Exp rhs, a_Exp_Binary binary ); @@ -104,6 +104,8 @@ t_CKBOOL emit_engine_emit_cast( Chuck_Emitter * emit, Chuck_Type * to, Chuck_Typ t_CKBOOL emit_engine_emit_symbol( Chuck_Emitter * emit, S_Symbol symbol, Chuck_Value * v, t_CKBOOL emit_var, t_CKUINT line, t_CKUINT where ); +Chuck_Instr_Stmt_Start * emit_engine_track_stmt_refs_start( Chuck_Emitter * emit, a_Stmt stmt ); +void emit_engine_track_stmt_refs_cleanup( Chuck_Emitter * emit, Chuck_Instr_Stmt_Start * start ); // disabled until further notice (added 1.3.0.0) // t_CKBOOL emit_engine_emit_spork( Chuck_Emitter * emit, a_Stmt stmt ); @@ -168,7 +170,7 @@ Chuck_VM_Code * emit_engine_emit_prog( Chuck_Emitter * emit, a_Program prog, // make sure the code is NULL assert( emit->code == NULL ); // make sure the stack is empty - assert( emit->stack.size() == 0 ); + assert( emit->code_stack.size() == 0 ); // make sure there is a context to emit assert( emit->env->context != NULL ); // make sure no code @@ -193,7 +195,7 @@ Chuck_VM_Code * emit_engine_emit_prog( Chuck_Emitter * emit, a_Program prog, // reset the func emit->func = NULL; // clear the code stack - emit->stack.clear(); + emit->code_stack.clear(); // whether code need this emit->code->need_this = TRUE; // keep track of full path (added 1.3.0.0) @@ -279,23 +281,26 @@ Chuck_VM_Code * emit_engine_emit_prog( Chuck_Emitter * emit, a_Program prog, // clear the code stack Chuck_Code * code = NULL; // while stack not empty - while( emit->stack.size() ) + while( emit->code_stack.size() ) { // get the top of the stack - code = emit->stack.back(); + code = emit->code_stack.back(); // manually delete the instructions for( t_CKUINT i = 0; i < code->code.size(); i++ ) { CK_SAFE_DELETE( code->code[i] ); } // delete the code CK_SAFE_DELETE( code ); // pop the stack - emit->stack.pop_back(); + emit->code_stack.pop_back(); } } // clear the code CK_SAFE_DELETE( emit->code ); + // clear stmt_stack (no need to delete contents; only contains instructions that should be cleaned up above) + emit->stmt_stack.clear(); + // pop indent EM_poplog(); @@ -460,20 +465,21 @@ t_CKBOOL emit_engine_emit_stmt( Chuck_Emitter * emit, a_Stmt stmt, t_CKBOOL pop switch( stmt->s_type ) { case ae_stmt_exp: // expression statement - // emit it - ret = emit_engine_emit_exp( emit, stmt->stmt_exp ); + { + // emit the expression(s) that make up this stmt + ret = emit_engine_emit_exp( emit, stmt->stmt_exp, FALSE, stmt ); if( !ret ) return FALSE; // need to pop the final value from stack - if( ret && pop && stmt->stmt_exp->type->size > 0 ) + if( pop ) { - // sanity - assert( stmt->stmt_exp->cast_to == NULL ); - // exp a_Exp exp = stmt->stmt_exp; - // hack + + // HACK: this won't work if <<< >>> is not the first in an expression chain + // <<< 1 >>>, 2, 3, 4 won't clean up the reg stack correctly + // 1, <<< 2 >>>, 3 needs to be checked if( exp->s_type == ae_exp_primary && exp->primary.s_type == ae_primary_hack ) exp = exp->primary.exp; @@ -486,11 +492,17 @@ t_CKBOOL emit_engine_emit_stmt( Chuck_Emitter * emit, a_Stmt stmt, t_CKBOOL pop exp = exp->next; } - // loop over the expression that is the statement (prior to 1.5.0.0 comment: "HACK" hmmm...) + // loop over the expressions that is the statement (prior to 1.5.0.0 comment: "HACK" hmmm...) for( t_CKINT i = pxe.size()-1; i >= 0; i-- ) - // for( t_CKINT i = 0; i < pxe.size(); i++ ) { + // exp to work with exp = pxe[i]; + + // if zero size, move on + if( exp->type->size == 0 ) continue; + // make sure, the stmt-exp cannot be a cast-to + assert( exp->cast_to == NULL ); + // if decl, then expect only one word per var // added 1.3.1.0: iskindofint -- since on some 64-bit systems sz_INT == sz_FLOAT if( exp->s_type == ae_exp_decl ) @@ -501,22 +513,32 @@ t_CKBOOL emit_engine_emit_stmt( Chuck_Emitter * emit, a_Stmt stmt, t_CKBOOL pop } else if( exp->type->size == sz_INT && iskindofint(emit->env, exp->type) ) // ISSUE: 64-bit (fixed 1.3.1.0) { + // pop int value + // NOTE if obj left from exp being a func_call, the ref is now released by stmt mechanic | 1.5.1.7 + emit->append( new Chuck_Instr_Reg_Pop_Int ); + // is an object left on the stack for the stmt - if( exp->s_type == ae_exp_func_call && isobj(emit->env, exp->type) ) - { emit->append( new Chuck_Instr_Release_Object3_Pop_Int ); } - else // not an object - { emit->append( new Chuck_Instr_Reg_Pop_Int ); } + // if( exp->s_type == ae_exp_func_call && isobj(emit->env, exp->type) ) + // { emit->append( new Chuck_Instr_Release_Object3_Pop_Int ); } + // else // not an object + // { emit->append( new Chuck_Instr_Reg_Pop_Int ); } } else if( exp->type->size == sz_FLOAT ) // ISSUE: 64-bit (fixed 1.3.1.0) + { emit->append( new Chuck_Instr_Reg_Pop_Float ); + } else if( exp->type->size == sz_VEC2 ) // ISSUE: 64-bit (fixed 1.3.1.0) { emit->append( new Chuck_Instr_Reg_Pop_Vec2ComplexPolar ); } else if( exp->type->size == sz_VEC3 ) // ge: added 1.3.5.3 + { emit->append( new Chuck_Instr_Reg_Pop_WordsMulti(sz_VEC3/sz_WORD) ); + } else if( exp->type->size == sz_VEC4 ) // ge: added 1.3.5.3 + { emit->append( new Chuck_Instr_Reg_Pop_WordsMulti(sz_VEC4/sz_WORD) ); + } else { EM_error2( exp->where, @@ -530,6 +552,7 @@ t_CKBOOL emit_engine_emit_stmt( Chuck_Emitter * emit, a_Stmt stmt, t_CKBOOL pop // add semicolon to code str codestr += ";"; break; + } case ae_stmt_if: // if statement ret = emit_engine_emit_if( emit, &stmt->stmt_if ); @@ -658,7 +681,7 @@ t_CKBOOL emit_engine_emit_if( Chuck_Emitter * emit, a_Stmt_If stmt ) emit->push_scope(); // emit the condition - ret = emit_engine_emit_exp( emit, stmt->cond ); + ret = emit_engine_emit_exp( emit, stmt->cond, FALSE, stmt->self ); if( !ret ) return FALSE; @@ -844,7 +867,7 @@ t_CKBOOL emit_engine_emit_for( Chuck_Emitter * emit, a_Stmt_For stmt ) // get the index t_CKUINT c3_index = emit->next_index(); // emit the expression - ret = emit_engine_emit_exp( emit, stmt->c3 ); + ret = emit_engine_emit_exp( emit, stmt->c3, FALSE, stmt->self ); if( !ret ) return FALSE; // add code str emit->code->code[c3_index]->prepend_codestr( codestr_prefix + " " + codestr ); @@ -935,8 +958,8 @@ t_CKBOOL emit_engine_emit_foreach( Chuck_Emitter * emit, a_Stmt_ForEach stmt ) codestr = absyn2str( stmt->theIter ); // get the index cond_index = emit->next_index(); - // emit the iter part - ret = emit_engine_emit_exp( emit, stmt->theIter ); + // emit the iter part (can/should only be a decl; no releasing of stmt->exp dangling references) + ret = emit_engine_emit_exp( emit, stmt->theIter /*, FALSE, stmt->self */ ); if( !ret ) return FALSE; // add code str | 1.5.0.8 emit->code->code[cond_index]->prepend_codestr( codestr_prefix + " " + codestr ); @@ -962,8 +985,13 @@ t_CKBOOL emit_engine_emit_foreach( Chuck_Emitter * emit, a_Stmt_ForEach stmt ) codestr = absyn2str( stmt->theArray ); // get the index cond_index = emit->next_index(); + // track dangling refs, but instead of doing this inside the emit_exp(), + // we need to this here, outside, since we must ensure that the array left + // on the stack can't be released until after we make the assignment to + // @foreach_array | 1.5.1.7 + Chuck_Instr_Stmt_Start * start = emit_engine_track_stmt_refs_start( emit, stmt->self ); // emit the array part - ret = emit_engine_emit_exp( emit, stmt->theArray ); + ret = emit_engine_emit_exp( emit, stmt->theArray /*, FALSE, stmt->self */ ); if( !ret ) return FALSE; // add code str | 1.5.0.8 emit->code->code[cond_index]->prepend_codestr( codestr_prefix + " " + codestr ); @@ -985,6 +1013,8 @@ t_CKBOOL emit_engine_emit_foreach( Chuck_Emitter * emit, a_Stmt_ForEach stmt ) emit->append( new Chuck_Instr_Alloc_Word( localArray->offset, TRUE ) ); // copy object emit->append( new Chuck_Instr_Assign_Object() ); + // clean up any dangling object refs | 1.5.1.7 + emit_engine_track_stmt_refs_cleanup( emit, start ); //------------------------------------------------------------------------- // emit implicit iter COUNTER @@ -1133,7 +1163,7 @@ t_CKBOOL emit_engine_emit_while( Chuck_Emitter * emit, a_Stmt_While stmt ) t_CKUINT cond_index = emit->next_index(); // emit the cond - ret = emit_engine_emit_exp( emit, stmt->cond ); + ret = emit_engine_emit_exp( emit, stmt->cond, FALSE, stmt->self ); if( !ret ) return FALSE; // add code str | 1.5.0.8 @@ -1255,7 +1285,7 @@ t_CKBOOL emit_engine_emit_do_while( Chuck_Emitter * emit, a_Stmt_While stmt ) t_CKUINT cond_index = emit->next_index(); // emit the cond - ret = emit_engine_emit_exp( emit, stmt->cond ); + ret = emit_engine_emit_exp( emit, stmt->cond, FALSE, stmt->self ); if( !ret ) return FALSE; // add code str | 1.5.0.8 @@ -1353,7 +1383,7 @@ t_CKBOOL emit_engine_emit_until( Chuck_Emitter * emit, a_Stmt_Until stmt ) t_CKUINT cond_index = emit->next_index(); // emit the cond - ret = emit_engine_emit_exp( emit, stmt->cond ); + ret = emit_engine_emit_exp( emit, stmt->cond, FALSE, stmt->self ); if( !ret ) return FALSE; // add code str | 1.5.0.8 @@ -1474,7 +1504,7 @@ t_CKBOOL emit_engine_emit_do_until( Chuck_Emitter * emit, a_Stmt_Until stmt ) t_CKUINT cond_index = emit->next_index(); // emit the cond - ret = emit_engine_emit_exp( emit, stmt->cond ); + ret = emit_engine_emit_exp( emit, stmt->cond, FALSE, stmt->self ); if( !ret ) return FALSE; // add code str | 1.5.0.8 @@ -1558,7 +1588,7 @@ t_CKBOOL emit_engine_emit_loop( Chuck_Emitter * emit, a_Stmt_Loop stmt ) t_CKUINT cond_index = emit->next_index(); // emit the cond - ret = emit_engine_emit_exp( emit, stmt->cond ); + ret = emit_engine_emit_exp( emit, stmt->cond, FALSE, stmt->self ); if( !ret ) return FALSE; // add code str | 1.5.0.8 @@ -1694,8 +1724,13 @@ t_CKBOOL emit_engine_emit_continue( Chuck_Emitter * emit, a_Stmt_Continue cont ) //----------------------------------------------------------------------------- t_CKBOOL emit_engine_emit_return( Chuck_Emitter * emit, a_Stmt_Return stmt ) { + // track dangling refs, but instead of doing this inside the emit_exp(), + // we need to this here, outside, since we must ensure that the object left + // on the stack can't be released until after we make add_ref below | 1.5.1.7 + Chuck_Instr_Stmt_Start * start = emit_engine_track_stmt_refs_start( emit, stmt->self ); + // emit the value - if( !emit_engine_emit_exp( emit, stmt->val ) ) + if( !emit_engine_emit_exp( emit, stmt->val /*, FALSE, stmt->self */ ) ) return FALSE; // if return is an object type @@ -1705,6 +1740,9 @@ t_CKBOOL emit_engine_emit_return( Chuck_Emitter * emit, a_Stmt_Return stmt ) emit->append( new Chuck_Instr_Reg_AddRef_Object3 ); } + // clean up any dangling object refs | 1.5.1.7 + emit_engine_track_stmt_refs_cleanup( emit, start ); + // 1.5.0.0 (ge) | traverse and unwind current scope frames, release object // references given the current state of the stack frames; every `return` // statement needs its own scope unwinding; FYI: this does not change the @@ -1735,21 +1773,74 @@ t_CKBOOL emit_engine_emit_switch( Chuck_Emitter * emit, a_Stmt_Switch stmt ); +//----------------------------------------------------------------------------- +// name: emit_engine_track_stmt_refs_start() +// desc: as needed start tracking stmt-level dangling object refs +//----------------------------------------------------------------------------- +Chuck_Instr_Stmt_Start * emit_engine_track_stmt_refs_start( Chuck_Emitter * emit, a_Stmt stmt ) +{ + // the stmt_start to push into stack | 1.5.1.7 + Chuck_Instr_Stmt_Start * start = NULL; + // see if we need to emit an instruction + if( stmt && stmt->numObjsToRelease ) + { + // emit statement release + emit->append( start = new Chuck_Instr_Stmt_Start( stmt->numObjsToRelease ) ); + // push stack + emit->stmt_stack.push_back( start ); + } + // done; could be NULL + return start; +} + + + + +//----------------------------------------------------------------------------- +// name: emit_engine_track_stmt_refs_cleanup() +// desc: as needed cleanup tracking stmt-level dangling object refs +//----------------------------------------------------------------------------- +void emit_engine_track_stmt_refs_cleanup( Chuck_Emitter * emit, Chuck_Instr_Stmt_Start * start ) +{ + // if objs to release | 1.5.1.7 + if( start ) + { + // emit statement release + emit->append( new Chuck_Instr_Stmt_Cleanup( start ) ); + // pop stmt stack | 1.5.1.7 (ge) added + emit->stmt_stack.pop_back(); + } +} + + + + //----------------------------------------------------------------------------- // name: emit_engine_emit_exp() -// desc: (doAddRef added 1.3.0.0 -- typically this is set to TRUE for function -// calls so that pointers on the reg is accounted for; this is important +// desc: emit code for an expression +// 1.3.0.0 | added doAddRef -- typically this is set to TRUE for function +// calls so that arguments on reg stack are accounted for; this is important // in case the object is released/reclaimed before the value is used; -// on particular case is when sporking with a local object as argument) +// one particular case is when sporking with a local object as argument) +// 1.5.1.7 | added enclosingStmt -- this should only be set by an enclosing +// statement to track object references that needs releasing, e.g., +// returning Object from a function, or a `new` allocation; this argument +// enables checking and emitting instructions to track and clean these +// "dangling" reference at the stmt->exp level //----------------------------------------------------------------------------- -t_CKBOOL emit_engine_emit_exp( Chuck_Emitter * emit, a_Exp exp, t_CKBOOL doAddRef ) +t_CKBOOL emit_engine_emit_exp( Chuck_Emitter * emit, a_Exp exp, t_CKBOOL doAddRef, + a_Stmt enclosingStmt ) { // for now... // assert( exp->next == NULL ); + // the stmt_start to push into stack | 1.5.1.7 + Chuck_Instr_Stmt_Start * start = emit_engine_track_stmt_refs_start( emit, enclosingStmt ); + // loop over while( exp ) { + // check expression format switch( exp->s_type ) { case ae_exp_binary: @@ -1790,6 +1881,22 @@ t_CKBOOL emit_engine_emit_exp( Chuck_Emitter * emit, a_Exp exp, t_CKBOOL doAddRe case ae_exp_func_call: if( !emit_engine_emit_exp_func_call( emit, &exp->func_call ) ) return FALSE; + + // if the function returns an Object | 1.5.1.7 + if( isobj( emit->env, exp->func_call.ret_type ) ) + { + // the return needs to be released (later at the end of the stmt that contains this) + Chuck_Instr_Stmt_Start * start = emit->stmt_stack.size() ? emit->stmt_stack.back() : NULL; + // check it + if( start ) + { + t_CKUINT offset = 0; + // acquire next offset + if( !start->nextOffset( offset ) ) return FALSE; + // append instruction + emit->append( new Chuck_Instr_Stmt_Remember_Object( start, offset ) ); + } + } break; case ae_exp_dot_member: @@ -1832,9 +1939,13 @@ t_CKBOOL emit_engine_emit_exp( Chuck_Emitter * emit, a_Exp exp, t_CKBOOL doAddRe emit->append( new Chuck_Instr_Reg_AddRef_Object3() ); } + // next exp exp = exp->next; } + // clean up any dangling object refs + emit_engine_track_stmt_refs_cleanup( emit, start ); + return TRUE; } @@ -1921,7 +2032,8 @@ t_CKBOOL emit_engine_emit_exp_binary( Chuck_Emitter * emit, a_Exp_Binary binary t_CKBOOL doRefLeft = FALSE; t_CKBOOL doRefRight = FALSE; // check to see if this is a function call (added 1.3.0.2) - if( isa( binary->rhs->type, emit->env->ckt_function ) ) + // added ae_op_chuck and make sure not null, latter has type-equivalence with object types in certain contexts | 1.5.1.7 + if( binary->op == ae_op_chuck && isa( binary->rhs->type, emit->env->ckt_function ) && !isnull( emit->env, binary->rhs->type ) ) { // take care of objects in terms of reference counting doRefLeft = TRUE; @@ -2656,8 +2768,14 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a emit->append( instr = new Chuck_Instr_Op_string( op ) ); instr->set_linepos( lhs->line ); } - else if( isa( t_left, emit->env->ckt_object ) && isa( t_right, emit->env->ckt_object ) ) + else if( ( isa( t_left, emit->env->ckt_object ) && isa( t_right, emit->env->ckt_object ) ) || + ( isa( t_left, emit->env->ckt_object ) && isnull( emit->env, t_right ) ) || + ( isnull( emit->env,t_left ) && isa( t_right, emit->env->ckt_object ) ) || + ( isnull( emit->env,t_left ) && isnull( emit->env, t_right ) ) ) + { + // object reference compare emit->append( instr = new Chuck_Instr_Eq_int ); + } else { switch( left ) @@ -2697,8 +2815,13 @@ t_CKBOOL emit_engine_emit_op( Chuck_Emitter * emit, ae_Operator op, a_Exp lhs, a emit->append( instr = new Chuck_Instr_Op_string( op ) ); instr->set_linepos( lhs->line ); } - else if( isa( t_left, emit->env->ckt_object ) && isa( t_right, emit->env->ckt_object ) ) + else if( ( isa( t_left, emit->env->ckt_object ) && isa( t_right, emit->env->ckt_object ) ) || + ( isa( t_left, emit->env->ckt_object ) && isnull( emit->env, t_right ) ) || + ( isnull( emit->env,t_left ) && isa( t_right, emit->env->ckt_object ) ) || + ( isnull( emit->env,t_left ) && isnull( emit->env, t_right ) ) ) + { emit->append( instr = new Chuck_Instr_Neq_int ); + } else { switch( left ) @@ -3088,7 +3211,8 @@ t_CKBOOL emit_engine_emit_op_chuck( Chuck_Emitter * emit, a_Exp lhs, a_Exp rhs, } // func call - if( isa( right, emit->env->ckt_function ) ) + // make sure not 'null' which also looks like any object | 1.5.1.7 + if( isa( right, emit->env->ckt_function ) && !isnull(emit->env,right) ) { assert( binary->ck_func != NULL ); @@ -5250,7 +5374,7 @@ t_CKBOOL emit_engine_emit_func_def( Chuck_Emitter * emit, a_Func_Def func_def ) // set the func emit->env->func = func; // push the current code - emit->stack.push_back( emit->code ); + emit->code_stack.push_back( emit->code ); // make a new one emit->code = new Chuck_Code; // name the code | 1.5.1.5 use signature() @@ -5383,9 +5507,9 @@ t_CKBOOL emit_engine_emit_func_def( Chuck_Emitter * emit, a_Func_Def func_def ) // delete the code CK_SAFE_DELETE( emit->code ); // pop the code - assert( emit->stack.size() ); - emit->code = emit->stack.back(); - emit->stack.pop_back(); + assert( emit->code_stack.size() ); + emit->code = emit->code_stack.back(); + emit->code_stack.pop_back(); return TRUE; } @@ -5427,7 +5551,7 @@ t_CKBOOL emit_engine_emit_class_def( Chuck_Emitter * emit, a_Class_Def class_def emit->env->class_stack.push_back( emit->env->class_def ); emit->env->class_def = type; // push the current code - emit->stack.push_back( emit->code ); + emit->code_stack.push_back( emit->code ); // make a new one emit->code = new Chuck_Code; // name the code @@ -5524,9 +5648,9 @@ t_CKBOOL emit_engine_emit_class_def( Chuck_Emitter * emit, a_Class_Def class_def // delete the code CK_SAFE_DELETE( emit->code ); // pop the code - assert( emit->stack.size() ); - emit->code = emit->stack.back(); - emit->stack.pop_back(); + assert( emit->code_stack.size() ); + emit->code = emit->code_stack.back(); + emit->code_stack.pop_back(); return ret; } @@ -5561,7 +5685,7 @@ t_CKBOOL emit_engine_emit_spork( Chuck_Emitter * emit, a_Exp_Func_Call exp ) } // push the current code - emit->stack.push_back( emit->code ); + emit->code_stack.push_back( emit->code ); // make a new one (spork~exp shred) emit->code = new Chuck_Code; // handle need this @@ -5617,10 +5741,10 @@ t_CKBOOL emit_engine_emit_spork( Chuck_Emitter * emit, a_Exp_Func_Call exp ) // code->name = string("spork~exp"); // restore the code to sporker shred - assert( emit->stack.size() > 0 ); - emit->code = emit->stack.back(); + assert( emit->code_stack.size() > 0 ); + emit->code = emit->code_stack.back(); // pop - emit->stack.pop_back(); + emit->code_stack.pop_back(); a_Exp e = exp->args; t_CKUINT size = 0; diff --git a/src/core/chuck_emit.h b/src/core/chuck_emit.h index a058f338e..c3562a99a 100644 --- a/src/core/chuck_emit.h +++ b/src/core/chuck_emit.h @@ -42,6 +42,7 @@ // forward references struct Chuck_Instr; struct Chuck_Instr_Goto; +struct Chuck_Instr_Stmt_Start; struct Chuck_VM_Code; struct Chuck_VM_Shred; @@ -119,10 +120,12 @@ struct Chuck_Emitter : public Chuck_VM_Object // current function definition Chuck_Func * func; - // code stack - std::vector stack; // locals std::vector locals; + // code stack + std::vector code_stack; + // stmt stack + std::vector stmt_stack; // dump t_CKBOOL dump; diff --git a/src/core/chuck_instr.cpp b/src/core/chuck_instr.cpp index 388e01315..08aa741c0 100644 --- a/src/core/chuck_instr.cpp +++ b/src/core/chuck_instr.cpp @@ -5226,7 +5226,7 @@ void Chuck_Instr_Func_Call_Member::execute( Chuck_VM * vm, Chuck_VM_Shred * shre if( m_func_ref && isobj(vm->env(), m_func_ref->def()->ret_type) ) { // get return value as object reference - Chuck_VM_Object * obj = (Chuck_VM_Object *) retval.v_uint; + Chuck_VM_Object * obj = (Chuck_VM_Object *)retval.v_uint; if( obj ) { // always add reference to returned objects (should release outside) @@ -5362,7 +5362,7 @@ void Chuck_Instr_Func_Call_Static::execute( Chuck_VM * vm, Chuck_VM_Shred * shre if( m_func_ref && isobj(vm->env(), m_func_ref->def()->ret_type) ) { // get return value as object reference - Chuck_VM_Object * obj = (Chuck_VM_Object *) retval.v_uint; + Chuck_VM_Object * obj = (Chuck_VM_Object *)retval.v_uint; if( obj ) { // always add reference to returned objects (should release outside) @@ -5488,7 +5488,7 @@ void Chuck_Instr_Func_Call_Global::execute( Chuck_VM * vm, Chuck_VM_Shred * shre if( m_func_ref && isobj(vm->env(), m_func_ref->def()->ret_type) ) { // get return value as object reference - Chuck_VM_Object * obj = (Chuck_VM_Object *) retval.v_uint; + Chuck_VM_Object * obj = (Chuck_VM_Object *)retval.v_uint; if( obj ) { // always add reference to returned objects (should release outside) @@ -5552,7 +5552,7 @@ void Chuck_Instr_Func_Call_Global::execute( Chuck_VM * vm, Chuck_VM_Shred * shre //----------------------------------------------------------------------------- // name: execute() -// desc: ... +// desc: return from a function //----------------------------------------------------------------------------- void Chuck_Instr_Func_Return::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) { @@ -5578,9 +5578,250 @@ void Chuck_Instr_Func_Return::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) +//----------------------------------------------------------------------------- +// name: params() +// desc: for instruction dumps +//----------------------------------------------------------------------------- +// for printing +const char * Chuck_Instr_Stmt_Start::params() const +{ + static char buffer[CK_PRINT_BUF_LENGTH]; + snprintf( buffer, CK_PRINT_BUF_LENGTH, "numObjReleases=%lu this=%p", (unsigned long)m_numObjReleases, this ); + return buffer; +} + + + + //----------------------------------------------------------------------------- // name: execute() -// desc: ... +// desc: executed at the start of a statement (see header for details) +//----------------------------------------------------------------------------- +void Chuck_Instr_Stmt_Start::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) +{ + // if nothing to push, no op + if( !m_numObjReleases ) return; + + // make room + if( !m_objectsToRelease ) + { + // allocate for re-use + m_objectsToRelease = new t_CKUINT[m_numObjReleases]; + } + + // make room for all the object references to release + for( t_CKUINT i = 0; i < m_numObjReleases; i++ ) + { + // zero out + m_objectsToRelease[i] = 0; + } +} + + + + +//----------------------------------------------------------------------------- +// name: nextOffset() +// desc: returns next offset on success; 0 if we have exceeded numObjeReleases +//----------------------------------------------------------------------------- +t_CKBOOL Chuck_Instr_Stmt_Start::nextOffset( t_CKUINT & offset ) +{ + // clear offset + offset = 0; + // check + if( m_nextOffset >= m_numObjReleases ) + { + EM_exception( + "(internal error) out of bounds in Stmt_Start.nextIndex(): nextOffset == %lu", + m_nextOffset ); + + // return + return FALSE; + } + + // copy into return variable, then increment + offset = m_nextOffset++; + // return + return TRUE; +} + + + + +//----------------------------------------------------------------------------- +// name: setObject() +// desc: set object into data region of objects to release by stmt's end +//----------------------------------------------------------------------------- +t_CKBOOL Chuck_Instr_Stmt_Start::setObject( Chuck_VM_Object * object, t_CKUINT offset ) +{ + // check + if( offset >= m_numObjReleases ) + { + EM_exception( + "(internal error) offset out of bounds in Stmt_Start.setObject(): offset == %lu", + offset ); + // return + return FALSE; + } + + // pointer arithmetic + t_CKUINT * pInt = m_objectsToRelease + offset; + + // release if not NULL; what was previously there is no-longer accessible + // NOTE this could happen in the case of a loop: + // e.g., while( foo() ) { ... } // where foo() returns an object + Chuck_VM_Object * outgoing = (Chuck_VM_Object *)(*pInt); + CK_SAFE_RELEASE( outgoing ); + + // copy incoming object pointer + *pInt = (t_CKUINT)object; + // done + return TRUE; +} + + + + +//----------------------------------------------------------------------------- +// name: cleanupRefs() +// desc: clean up references +//----------------------------------------------------------------------------- +t_CKBOOL Chuck_Instr_Stmt_Start::cleanupRefs( Chuck_VM_Shred * shred ) +{ + // if nothing to push, no op + if( !m_numObjReleases ) return TRUE; + + // if no stack pointer + if( !m_objectsToRelease ) + { + // we have a problem + EM_exception( + "(internal error) NULL data region in Stmt_Start.cleanupRef() on shred[id=%lu:%s]", + shred->xid, shred->name.c_str() ); + // bail out + return FALSE; + } + + // cast pointer to data region as Object pointers + t_CKUINT * pInt = m_objectsToRelease; + + // make room for all the object references to release + for( t_CKUINT i = 0; i < m_numObjReleases; i++ ) + { + // object pointer + Chuck_VM_Object * object = (Chuck_VM_Object *)(*pInt); + // release (could be NULL) + CK_SAFE_RELEASE( object ); + // advance pointer + pInt++; + } + + return TRUE; +} + + + + + +//----------------------------------------------------------------------------- +// name: params() +// desc: for instruction dumps +//----------------------------------------------------------------------------- +const char * Chuck_Instr_Stmt_Remember_Object::params() const +{ + static char buffer[CK_PRINT_BUF_LENGTH]; + snprintf( buffer, CK_PRINT_BUF_LENGTH, "offset=%lu start=%p", (unsigned long)m_offset, m_stmtStart ); + return buffer; +} + + + + +//----------------------------------------------------------------------------- +// name: execute() +// desc: remember obj ref on reg stack for stmt-related cleanup +//----------------------------------------------------------------------------- +void Chuck_Instr_Stmt_Remember_Object::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) +{ + // get stack pointer + t_CKUINT * reg_sp = (t_CKUINT *)shred->reg->sp; + Chuck_VM_Object * obj = (Chuck_VM_Object *)(*(reg_sp-1)); + + // check + if( !m_stmtStart ) + { + EM_exception( + "(internal error) missing data region information in Stmt_Remember_Object instruction..." ); + goto error; + } + + // clear the objects returns by function calls in the statement + if( !m_stmtStart->setObject( obj, m_offset ) ) + { + EM_exception( + "(internal error) cannot setObject() in Stmt_Remember_Object instruction..." ); + goto error; + } + + // done + return; + +error: + // done + shred->is_running = FALSE; + shred->is_done = TRUE; +} + + + + +//----------------------------------------------------------------------------- +// name: params() +// desc: for instruction dumps +//----------------------------------------------------------------------------- +const char * Chuck_Instr_Stmt_Cleanup::params() const +{ + static char buffer[CK_PRINT_BUF_LENGTH]; + snprintf( buffer, CK_PRINT_BUF_LENGTH, "numObjRelease=%lu start=%p", (unsigned long)(m_stmtStart ? m_stmtStart->m_numObjReleases : 0), m_stmtStart ); + return buffer; +} + + + + +//----------------------------------------------------------------------------- +// name: execute() +// desc: clean up after a statement +//----------------------------------------------------------------------------- +void Chuck_Instr_Stmt_Cleanup::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) +{ + // check + if( !m_stmtStart ) + { + EM_exception( + "(internal error) missing data region in Stmt_Cleanup instruction..." ); + goto error; + } + + // clean up references + if( !m_stmtStart->cleanupRefs( shred ) ) + goto error; + + // done + return; + +error: + // done + shred->is_running = FALSE; + shred->is_done = TRUE; +} + + + + +//----------------------------------------------------------------------------- +// name: execute() +// desc: spork a shred from code //----------------------------------------------------------------------------- void Chuck_Instr_Spork::execute( Chuck_VM * vm, Chuck_VM_Shred * shred ) { @@ -7012,7 +7253,7 @@ void Chuck_Instr_Dot_Member_Data::execute( Chuck_VM * vm, Chuck_VM_Shred * shred { // 4 or 8 or 16 // 1.3.1.0: check type to use kind instead of size - if( m_kind == kindof_INT ) { push_( sp, *((t_CKUINT *)data) ); } // ISSUE: 64-bit (fixed 1.3.1.0) + if( m_kind == kindof_INT ) { push_( sp, *((t_CKINT *)data) ); } // ISSUE: 64-bit (fixed 1.3.1.0) else if( m_kind == kindof_FLOAT ) { push_float( sp, *((t_CKFLOAT *)data) ); } // ISSUE: 64-bit (fixed 1.3.1.0) else if( m_kind == kindof_VEC2 ) { push_vec2( sp, *((t_CKVEC2 *)data) ); } // ISSUE: 64-bit (fixed 1.3.1.0) | in this context, vec2 = complex = polar else if( m_kind == kindof_VEC3 ) { push_vec3( sp, *((t_CKVEC3 *)data) ); } // 1.3.5.3 diff --git a/src/core/chuck_instr.h b/src/core/chuck_instr.h index 5e4790f07..2db6193ee 100644 --- a/src/core/chuck_instr.h +++ b/src/core/chuck_instr.h @@ -3348,14 +3348,117 @@ struct Chuck_Instr_Func_Return : public Chuck_Instr +//----------------------------------------------------------------------------- +// name: struct Chuck_Instr_Stmt_Start +// desc: executed at the start of a statement: +// 1) during type-checking, each stmt notes how many objects needs +// releasing by stmt end, including a) func-calls that return Objects +// b) `new` expressions which may or may not be assigned to vars +// (verify these auto-add_refs to make the math work out) +// 2) during emit, if a stmt has any objects to release, one of these +// instruction will be emitted to begin a statement, purpose: +// make room on reg stack for objects to release at end of stmt\ +// 3) operations that return Objects (func calls and `new; not variables +// since those references are accounted for) should be given the means +// to add its return value to the list of objects refs to release +// in the reg stack section allocated by the Stmt_Start instr +// 4) at stmt's end, a Stmt_Cleanup instr will be issued that cleans up +// the reg stack, including all objects references +//----------------------------------------------------------------------------- +struct Chuck_Instr_Stmt_Start : public Chuck_Instr +{ +public: + // constructor + Chuck_Instr_Stmt_Start( t_CKUINT numObjReleases ) + { m_numObjReleases = numObjReleases; m_objectsToRelease = NULL; m_nextOffset = 0; } + // destructor + virtual ~Chuck_Instr_Stmt_Start() + { CK_SAFE_DELETE_ARRAY( m_objectsToRelease ); } + // execute + virtual void execute( Chuck_VM * vm, Chuck_VM_Shred * shred ); + // for printing + virtual const char * params() const; + +public: + // get next index; returns offset on success; 0 if we have exceeded numObjeReleases + t_CKBOOL nextOffset( t_CKUINT & offset ); + // set object reference by offset + t_CKBOOL setObject( Chuck_VM_Object * object, t_CKUINT offset ); + // clean up object references stored in this + t_CKBOOL cleanupRefs( Chuck_VM_Shred * shred ); + +public: + // next index + t_CKUINT m_nextOffset; + // number of objects to release at the end of statement + t_CKUINT m_numObjReleases; + // pointer to beginning of objects section on reg stack + t_CKUINT * m_objectsToRelease; +}; + + + + +//----------------------------------------------------------------------------- +// name: struct Chuck_Instr_Stmt_Remember_Object +// desc: called to remember obj ref on reg stack for stmt-related cleanup +// does not alter contents of stack (in-place) +//----------------------------------------------------------------------------- +struct Chuck_Instr_Stmt_Remember_Object : public Chuck_Instr +{ +public: + // constructor + Chuck_Instr_Stmt_Remember_Object( Chuck_Instr_Stmt_Start * start, t_CKUINT offset ) + { m_stmtStart = start; m_offset = offset; } + // execute + virtual void execute( Chuck_VM * vm, Chuck_VM_Shred * shred ); + // for printing + virtual const char * params() const; + +protected: + // pointer to corresponding Stmt_Start + Chuck_Instr_Stmt_Start * m_stmtStart; + // data offset + t_CKUINT m_offset; +}; + + + + +//----------------------------------------------------------------------------- +// name: struct Chuck_Instr_Stmt_Cleanup +// desc: called at the end of each statement for cleanup +//----------------------------------------------------------------------------- +struct Chuck_Instr_Stmt_Cleanup : public Chuck_Instr +{ +public: + // constructor + Chuck_Instr_Stmt_Cleanup( Chuck_Instr_Stmt_Start * start = NULL ) + { m_stmtStart = start; } + // execute + virtual void execute( Chuck_VM * vm, Chuck_VM_Shred * shred ); + +public: + // for printing + virtual const char * params() const; + +protected: + // pointer to corresponding Stmt_Start + Chuck_Instr_Stmt_Start * m_stmtStart; +}; + + + + //----------------------------------------------------------------------------- // name: struct Chuck_Instr_Spork -// desc: ... +// desc: spork instruction //----------------------------------------------------------------------------- struct Chuck_Instr_Spork : public Chuck_Instr_Unary_Op { public: Chuck_Instr_Spork( t_CKUINT v = 0 ) { this->set( v ); } + public: virtual void execute( Chuck_VM * vm, Chuck_VM_Shred * shred ); }; @@ -4468,13 +4571,13 @@ void ck_throw_exception(Chuck_VM_Shred * shred, const char * name, const char * void ck_handle_overflow( Chuck_VM_Shred * shred, Chuck_VM * vm, const std::string & reason = "" ); // define SP offset -#define push_( sp, val ) *(sp) = (val); (sp)++ -#define push_float( sp, val ) *((t_CKFLOAT *&)sp) = (val); ((t_CKFLOAT *&)sp)++ -#define push_complex( sp, val ) *((t_CKCOMPLEX *&)sp) = (val); ((t_CKCOMPLEX *&)sp)++ -#define push_vec2( sp, val ) *((t_CKVEC2 *&)sp) = (val); ((t_CKVEC2 *&)sp)++ -#define push_vec3( sp, val ) *((t_CKVEC3 *&)sp) = (val); ((t_CKVEC3 *&)sp)++ -#define push_vec4( sp, val ) *((t_CKVEC4 *&)sp) = (val); ((t_CKVEC4 *&)sp)++ -#define pop_( sp, n ) sp -= (n) +#define push_( sp, val ) do { *(sp) = (val); (sp)++; } while(0) +#define push_float( sp, val ) do { *((t_CKFLOAT *&)sp) = (val); ((t_CKFLOAT *&)sp)++; } while(0) +#define push_complex( sp, val ) do { *((t_CKCOMPLEX *&)sp) = (val); ((t_CKCOMPLEX *&)sp)++; } while(0) +#define push_vec2( sp, val ) do { *((t_CKVEC2 *&)sp) = (val); ((t_CKVEC2 *&)sp)++; } while(0) +#define push_vec3( sp, val ) do { *((t_CKVEC3 *&)sp) = (val); ((t_CKVEC3 *&)sp)++; } while(0) +#define push_vec4( sp, val ) do { *((t_CKVEC4 *&)sp) = (val); ((t_CKVEC4 *&)sp)++; } while(0) +#define pop_( sp, n ) do { sp -= (n); } while(0) #define val_( sp ) *(sp) // stack overflow detection diff --git a/src/core/chuck_io.cpp b/src/core/chuck_io.cpp index a6894409a..221e0ccfe 100644 --- a/src/core/chuck_io.cpp +++ b/src/core/chuck_io.cpp @@ -204,6 +204,11 @@ t_CKBOOL init_class_io( Chuck_Env * env, Chuck_Type * type ) // if( !type_engine_import_sfun( env, func ) ) goto error; // new line string initialize_object( g_newline, env->ckt_string, NULL, NULL ); + // add reference, since we are keeping a reference of it | 1.5.1.7 + CK_SAFE_ADD_REF( g_newline ); + // also lock it | 1.5.1.7 + g_newline->lock(); + // set as newline g_newline->set( "\n" ); // add TYPE_ASCII diff --git a/src/core/chuck_type.cpp b/src/core/chuck_type.cpp index 8e865312f..949334f9f 100644 --- a/src/core/chuck_type.cpp +++ b/src/core/chuck_type.cpp @@ -1038,6 +1038,9 @@ t_CKBOOL type_engine_check_stmt( Chuck_Env * env, a_Stmt stmt ) if( !stmt ) return TRUE; + // push stmt | 1.5.1.7 + env->stmt_stack.push_back( stmt ); + // the type of stmt switch( stmt->s_type ) { @@ -1133,6 +1136,9 @@ t_CKBOOL type_engine_check_stmt( Chuck_Env * env, a_Stmt stmt ) break; } + // pop stmt stack | 1.5.1.7 + env->stmt_stack.pop_back(); + return ret; } @@ -1735,6 +1741,12 @@ t_CKTYPE type_engine_check_exp( Chuck_Env * env, a_Exp exp ) curr->func_call.ret_type = curr->type; // add reference | 1.5.0.5 CK_SAFE_ADD_REF( curr->func_call.ret_type ); + // check if return type is an Obj | 1.5.1.7 + if( curr->type && isobj( env, curr->type ) && env->stmt_stack.size() ) + { + // increment # of objects in this stmt that needs release + env->stmt_stack.back()->numObjsToRelease++; + } break; case ae_exp_dot_member: @@ -2321,12 +2333,10 @@ t_CKTYPE type_engine_check_op( Chuck_Env * env, ae_Operator op, a_Exp lhs, a_Exp case ae_op_eq: case ae_op_neq: - // null - // if( isa( left, env->ckt_object ) && isa( right, env->ckt_null ) ) return env->ckt_int; - // if( isa( left, env->ckt_null ) && isa( right, env->ckt_object ) ) return env->ckt_int; CK_LR( te_vec2, te_vec2 ) return env->ckt_int; // 1.5.1.7 CK_LR( te_vec3, te_vec3 ) return env->ckt_int; // 1.3.5.3 CK_LR( te_vec4, te_vec4 ) return env->ckt_int; // 1.3.5.3 + if( isa( left, env->ckt_object ) && isa( right, env->ckt_object ) ) return env->ckt_int; case ae_op_lt: case ae_op_le: { @@ -2362,7 +2372,9 @@ t_CKTYPE type_engine_check_op( Chuck_Env * env, ae_Operator op, a_Exp lhs, a_Exp // CK_COMMUTE( te_vec2, te_vec3 ) return env->ckt_int; // 1.5.1.7 // CK_COMMUTE( te_vec2, te_vec4 ) return env->ckt_int; // 1.5.1.7 // CK_COMMUTE( te_vec3, te_vec4 ) return env->ckt_int; // 1.3.5.3 - if( isa( left, env->ckt_object ) && isa( right, env->ckt_object ) ) return env->ckt_int; + + // disallowed | 1.5.1.7 (moved to EQ and NEQ only) + // if( isa( left, env->ckt_object ) && isa( right, env->ckt_object ) ) return env->ckt_int; break; case ae_op_shift_left: @@ -2542,7 +2554,7 @@ t_CKTYPE type_engine_check_op_chuck( Chuck_Env * env, a_Exp lhs, a_Exp rhs, t_CKTYPE left = lhs->type, right = rhs->type; // chuck to function - if( isa( right, env->ckt_function ) ) + if( !isnull(env, right) && isa( right, env->ckt_function ) ) { // treat this function call return type_engine_check_exp_func_call( env, rhs, lhs, binary->ck_func, binary->where ); @@ -4306,6 +4318,14 @@ t_CKTYPE type_engine_check_exp_func_call( Chuck_Env * env, a_Exp exp_func, a_Exp // void type for args t_CKTYPE a = env->ckt_void; + // make sure not 'null' + if( isnull(env, f) ) + { + EM_error2( exp_func->where, + "function call using a non-function value 'null'" ); + return NULL; + } + // make sure we have a function if( !isa( f, env->ckt_function ) ) { @@ -4389,6 +4409,7 @@ t_CKTYPE type_engine_check_exp_func_call( Chuck_Env * env, a_Exp exp_func, a_Exp } else assert( FALSE ); + // copy ck_func out (return by reference) ck_func = theFunc; // if sporking, then don't track dependencies... @@ -5428,7 +5449,7 @@ t_CKBOOL operator <=( const Chuck_Type & lhs, const Chuck_Type & rhs ) curr = curr->parent; } - // if lhs is null and rhs is a object + // if lhs is null and rhs is a object | removed 1.5.1.7? if( (lhs == *(lhs.env_ref->ckt_null)) && (rhs <= *(rhs.env_ref->ckt_object)) ) return TRUE; return FALSE; @@ -5653,6 +5674,8 @@ t_CKBOOL iskindofint( Chuck_Env * env, Chuck_Type * type ) // added 1.3.1.0 { return isa( type, env->ckt_int ) || isobj( env, type ); } t_CKBOOL isvoid( Chuck_Env * env, Chuck_Type * type ) // added 1.5.0.0 { return isa( type, env->ckt_void ); } +t_CKBOOL isnull( Chuck_Env * env, Chuck_Type * type ) // added 1.5.0.0 +{ return equals( type, env->ckt_null ); } te_KindOf getkindof( Chuck_Env * env, Chuck_Type * type ) // added 1.3.1.0 { // the kind (1.3.1.0) diff --git a/src/core/chuck_type.h b/src/core/chuck_type.h index c7ed7c9d2..83a5df3c0 100644 --- a/src/core/chuck_type.h +++ b/src/core/chuck_type.h @@ -742,6 +742,9 @@ struct Chuck_Env : public Chuck_VM_Object // control scope (for break, continue) std::vector breaks; + // stmt stack (for tracking object references) + std::vector stmt_stack; + // reserved words std::map key_words; std::map key_types; @@ -1231,6 +1234,7 @@ t_CKBOOL isprim( Chuck_Env * env, Chuck_Type * type ); t_CKBOOL isobj( Chuck_Env * env, Chuck_Type * type ); t_CKBOOL isfunc( Chuck_Env * env, Chuck_Type * type ); t_CKBOOL isvoid( Chuck_Env * env, Chuck_Type * type ); +t_CKBOOL isnull( Chuck_Env * env, Chuck_Type * type ); t_CKBOOL iskindofint( Chuck_Env * env, Chuck_Type * type ); // added 1.3.1.0: this includes int + pointers te_KindOf getkindof( Chuck_Env * env, Chuck_Type * type ); // added 1.3.1.0: to get the kindof a type diff --git a/src/test/01-Basic/219-func-call-refs.ck b/src/test/01-Basic/219-func-call-refs.ck new file mode 100644 index 000000000..8ec2db17b --- /dev/null +++ b/src/test/01-Basic/219-func-call-refs.ck @@ -0,0 +1,41 @@ +// test reference count +// related: https://github.com/ccrma/chuck/issues/374 + +// class +class Foo +{ + 5 => int x; +} + +// another class +class Bar +{ + // instance of Foo + Foo foo; + // get it + fun Foo getFoo() { return foo; } + // take as argument + fun void setFoo( Foo f ) + { + // should be 3 + <<< Machine.refcount(f) >>>; + } +} + +// instance of Bar +Bar bar; +// should be 1 +<<< Machine.refcount(bar.foo) >>>; + +// verify no missing release from getFoo() +bar.getFoo() @=> Foo @ foo; +//should be 2 +<<< Machine.refcount(bar.foo) >>>; + +// multi-exp stmt +bar.getFoo(), bar.getFoo().x, bar.getFoo(); +// should be 2 +<<< Machine.refcount(bar.foo) >>>; + +// call a function +bar.setFoo( bar.foo ); diff --git a/src/test/01-Basic/219-func-call-refs.txt b/src/test/01-Basic/219-func-call-refs.txt new file mode 100644 index 000000000..7505f9b16 --- /dev/null +++ b/src/test/01-Basic/219-func-call-refs.txt @@ -0,0 +1,4 @@ +1 :(int) +2 :(int) +2 :(int) +3 :(int) diff --git a/src/test/01-Basic/220-dangel-ref-loop.ck b/src/test/01-Basic/220-dangel-ref-loop.ck new file mode 100644 index 000000000..8fa989cbe --- /dev/null +++ b/src/test/01-Basic/220-dangel-ref-loop.ck @@ -0,0 +1,24 @@ +// verify func-call returning objects is properly refcounted +// as part or the stmt's object release mechanism | 1.5.1.7 + +// a class +class Foo +{ + 5 => int x; +} + +// a file-global instance +Foo foo; +// a function that returns foo +fun Foo bar() { return foo; } + +// since bar() returns an object every iteration, verify the +// stmt reference count is correct +while( bar().x ) +{ + // do this a few times + bar().x--; +} + +// check +if( Machine.refcount(foo) == 1 ) <<< "success" >>>; diff --git a/src/test/01-Basic/221-dangle-ref-loops.ck b/src/test/01-Basic/221-dangle-ref-loops.ck new file mode 100644 index 000000000..cc1465321 --- /dev/null +++ b/src/test/01-Basic/221-dangle-ref-loops.ck @@ -0,0 +1,90 @@ +// verify func-call returning objects is properly refcounted +// as part or the stmt's object release mechanism | 1.5.1.7 + +// a class +class Foo { 5 => int x; } +// a file-global instance +Foo foo; +// a function that returns foo +fun Foo bar() { return foo; } +// another function that returns foo +fun Object zzz() { return foo; } + +// for loop +for( int i; i < bar().x; i++ ) { } + +// reset +5 => foo.x; + +// bar() returns an object every iteration, verify the +// stmt reference count is correct +while( bar().x ) +{ + // do this a few times + bar().x--; + // break + if( bar().x == 2 ) { break; } +} + +// reset +5 => foo.x; + +// try until +until( bar().x == 0 ) +{ + // do this a few times + bar().x--; + // break + if( bar().x == 2 ) break; +} + +// reset +5 => foo.x; + +// do while +do { + // do this a few times + bar().x--; + // break + if( bar().x == 2 ) break; +} while( bar().x ); + +// reset +1001 => foo.x; + +// do until +do { + // do this a few times + bar().x--; + // break + if( bar().x == 2 ) continue; + // do this a few times + bar().x--; +} until( bar().x < 1 ); + +20 => foo.x; + +// loop +repeat( bar().x ) { bar().x--; } + +// for each with array Object +for( int x : Std.range(0,20) ) { } + +// for loop with dangling ref in conditional part 2 +for( ; zzz() == null; ) { } + +// reset +13 => bar().x; + +// since bar() returns an object every iteration, verify the +// stmt reference count is correct +while( bar().x ) +{ + // do this a few times + bar().x--; + // break + while( bar().x ) break; +} + +// check +if( Machine.refcount(foo) == 1 ) <<< "success" >>>; diff --git a/src/test/01-Basic/222-dangle-ref-return.ck b/src/test/01-Basic/222-dangle-ref-return.ck new file mode 100644 index 000000000..928923483 --- /dev/null +++ b/src/test/01-Basic/222-dangle-ref-return.ck @@ -0,0 +1,83 @@ +// test cleaning up dangling references +// code structure borrowed from Michael Heuer's LicK library + +class Procedure // or Runnable +{ + fun void run() + { + // empty + } +} + +class Append extends Procedure +{ + Procedure g; + Procedure h; + + fun void run() + { + g.run(); + h.run(); + } +} + +class SleepProcedure extends Procedure +{ + Sleep @ sleep; + + fun void run() + { + sleep.sleep(); + } +} + +class Sleep +{ + 0::ms => dur value; + SleepProcedure _procedure; + + { + this @=> _procedure.sleep; + } + + fun void sleep() + { + value => now; + } + + fun Procedure asProcedure() + { + return _procedure; + } +} + + +public class Loops +{ + 0::ms => static dur none; + // ChucK bug: this value appears not to be set as static + 52::week => static dur forever; + + fun static Procedure append(Procedure g, Procedure h) + { + Append app; + g @=> app.g; + h @=> app.h; + return app; + } + + fun static Procedure append(Procedure g, dur wait) + { + Sleep sleep; + wait => sleep.value; + return append(g, sleep.asProcedure()); + } +} + +// a procedure +Procedure g; +// call a function that returns an Object +Loops.append(g, 64::ms) @=> Procedure p; + +// if we got here +<<< "success" >>>; diff --git a/src/test/01-Basic/223-dangle-ref-func1.ck b/src/test/01-Basic/223-dangle-ref-func1.ck new file mode 100644 index 000000000..5d106b1ce --- /dev/null +++ b/src/test/01-Basic/223-dangle-ref-func1.ck @@ -0,0 +1,36 @@ +// verify dangling references are cleaned up +// across nested func calls that return Objects + +// a class +class Foo +{ + 5 => int bar; + + fun Foo yikes( Foo f) + { + return f; + } +} + +// a Foo +Foo bob; +// func returning bob +fun Foo go() { return bob; } +// print +fun void huh( Foo arg ) { <<< arg.bar >>>; } + +bob; // just bob +go(); // fine? +go().bar; // problem? +<<< go() >>>; +go().bar + 5; +// call a func with arg +huh( go() ); +go().yikes( go() ); // all instances released? +go().yikes( go() ).bar; // oh no +<<< go().yikes(go()).bar++ >>>; // this can't possibly work +<<< go().yikes(go()).bar++ >>>; +<<< go().yikes(go()).bar++ >>>; + +// test +if( Machine.refcount(bob) == 1 ) <<< "success" >>>; \ No newline at end of file diff --git a/src/test/01-Basic/224-stack-pointer-reg.ck b/src/test/01-Basic/224-stack-pointer-reg.ck new file mode 100644 index 000000000..31dc8b271 --- /dev/null +++ b/src/test/01-Basic/224-stack-pointer-reg.ck @@ -0,0 +1,18 @@ +// let's check reg stack pointers +// 1.5.1.7 or higher + +// save reg sp +Machine.sp_reg() => int sp; + +// a function +fun void nope() { } + +// one statement of several expressions +//<<< 5 >>>, +1, 2, 3, 4, nope(), 5, 6, 7, nope(), nope(); + +// print +<<< 1, 2, 3, @(4,5), @(6,7,8) >>>; + +// test +if( Machine.sp_reg() == sp ) <<< "success" >>>; \ No newline at end of file diff --git a/src/test/01-Basic/224-stack-pointer-reg.txt b/src/test/01-Basic/224-stack-pointer-reg.txt new file mode 100644 index 000000000..d92b00d6f --- /dev/null +++ b/src/test/01-Basic/224-stack-pointer-reg.txt @@ -0,0 +1,2 @@ +1 2 3 @(4.0000,5.0000) #(6.0000,7.0000,8.0000) +"success" :(string) diff --git a/src/test/01-Basic/225-vecs-cast.ck b/src/test/01-Basic/225-vecs-cast.ck new file mode 100644 index 000000000..647eb6e6e --- /dev/null +++ b/src/test/01-Basic/225-vecs-cast.ck @@ -0,0 +1,13 @@ +// casting between vec2, vec3, vec4 + +// vec2 to vec3 +@(1,2) $ vec4 => vec4 v4; <<< v4 >>>; +// vec2 to vec4 +@(1,2) $ vec4 => v4; <<< v4 >>>; +// vec3 to vec4 +@(1,2,3) $ vec4 => v4; <<< v4 >>>; + +// vec4 to vec3 +@(1,2,3,4) $ vec3 => vec3 v3; <<< v3 >>>; +// vec4 to vec2 +@(1,2,3,4) $ vec2 => vec2 v2; <<< v2 >>>; diff --git a/src/test/01-Basic/225-vecs-cast.txt b/src/test/01-Basic/225-vecs-cast.txt new file mode 100644 index 000000000..61f519963 --- /dev/null +++ b/src/test/01-Basic/225-vecs-cast.txt @@ -0,0 +1,5 @@ +@(1.0000,2.0000,0.0000,0.0000) :(vec4) +@(1.0000,2.0000,0.0000,0.0000) :(vec4) +@(1.0000,2.0000,3.0000,0.0000) :(vec4) +@(1.0000,2.0000,3.0000) :(vec3) +@(1.0000,2.0000) :(vec2) diff --git a/src/test/01-Basic/226-vecs-foreach-read.ck b/src/test/01-Basic/226-vecs-foreach-read.ck new file mode 100644 index 000000000..00ff1d36d --- /dev/null +++ b/src/test/01-Basic/226-vecs-foreach-read.ck @@ -0,0 +1,30 @@ +// verify vec3 2D array +vec3 v2d[16][4]; + +// indices +int a, b; + +for( ; a < v2d.size(); a++ ) +{ + for( 0 => b; b < v2d[a].size(); b++ ) + { + a => v2d[a][b].x; + b => v2d[a][b].y; + } +} + +// reset +0 => a => b; +// test auto +for( auto v1d[] : v2d ) +{ + // test not auto + for( vec3 v : v1d ) + { + <<< "[",a,"][",b,"]", v.x, v.y, v.z >>>; + b++; + } + // update + 0 => b; + a++; +} \ No newline at end of file diff --git a/src/test/01-Basic/226-vecs-foreach-read.txt b/src/test/01-Basic/226-vecs-foreach-read.txt new file mode 100644 index 000000000..64008a465 --- /dev/null +++ b/src/test/01-Basic/226-vecs-foreach-read.txt @@ -0,0 +1,64 @@ +[ 0 ][ 0 ] 0.000000 0.000000 0.000000 +[ 0 ][ 1 ] 0.000000 1.000000 0.000000 +[ 0 ][ 2 ] 0.000000 2.000000 0.000000 +[ 0 ][ 3 ] 0.000000 3.000000 0.000000 +[ 1 ][ 0 ] 1.000000 0.000000 0.000000 +[ 1 ][ 1 ] 1.000000 1.000000 0.000000 +[ 1 ][ 2 ] 1.000000 2.000000 0.000000 +[ 1 ][ 3 ] 1.000000 3.000000 0.000000 +[ 2 ][ 0 ] 2.000000 0.000000 0.000000 +[ 2 ][ 1 ] 2.000000 1.000000 0.000000 +[ 2 ][ 2 ] 2.000000 2.000000 0.000000 +[ 2 ][ 3 ] 2.000000 3.000000 0.000000 +[ 3 ][ 0 ] 3.000000 0.000000 0.000000 +[ 3 ][ 1 ] 3.000000 1.000000 0.000000 +[ 3 ][ 2 ] 3.000000 2.000000 0.000000 +[ 3 ][ 3 ] 3.000000 3.000000 0.000000 +[ 4 ][ 0 ] 4.000000 0.000000 0.000000 +[ 4 ][ 1 ] 4.000000 1.000000 0.000000 +[ 4 ][ 2 ] 4.000000 2.000000 0.000000 +[ 4 ][ 3 ] 4.000000 3.000000 0.000000 +[ 5 ][ 0 ] 5.000000 0.000000 0.000000 +[ 5 ][ 1 ] 5.000000 1.000000 0.000000 +[ 5 ][ 2 ] 5.000000 2.000000 0.000000 +[ 5 ][ 3 ] 5.000000 3.000000 0.000000 +[ 6 ][ 0 ] 6.000000 0.000000 0.000000 +[ 6 ][ 1 ] 6.000000 1.000000 0.000000 +[ 6 ][ 2 ] 6.000000 2.000000 0.000000 +[ 6 ][ 3 ] 6.000000 3.000000 0.000000 +[ 7 ][ 0 ] 7.000000 0.000000 0.000000 +[ 7 ][ 1 ] 7.000000 1.000000 0.000000 +[ 7 ][ 2 ] 7.000000 2.000000 0.000000 +[ 7 ][ 3 ] 7.000000 3.000000 0.000000 +[ 8 ][ 0 ] 8.000000 0.000000 0.000000 +[ 8 ][ 1 ] 8.000000 1.000000 0.000000 +[ 8 ][ 2 ] 8.000000 2.000000 0.000000 +[ 8 ][ 3 ] 8.000000 3.000000 0.000000 +[ 9 ][ 0 ] 9.000000 0.000000 0.000000 +[ 9 ][ 1 ] 9.000000 1.000000 0.000000 +[ 9 ][ 2 ] 9.000000 2.000000 0.000000 +[ 9 ][ 3 ] 9.000000 3.000000 0.000000 +[ 10 ][ 0 ] 10.000000 0.000000 0.000000 +[ 10 ][ 1 ] 10.000000 1.000000 0.000000 +[ 10 ][ 2 ] 10.000000 2.000000 0.000000 +[ 10 ][ 3 ] 10.000000 3.000000 0.000000 +[ 11 ][ 0 ] 11.000000 0.000000 0.000000 +[ 11 ][ 1 ] 11.000000 1.000000 0.000000 +[ 11 ][ 2 ] 11.000000 2.000000 0.000000 +[ 11 ][ 3 ] 11.000000 3.000000 0.000000 +[ 12 ][ 0 ] 12.000000 0.000000 0.000000 +[ 12 ][ 1 ] 12.000000 1.000000 0.000000 +[ 12 ][ 2 ] 12.000000 2.000000 0.000000 +[ 12 ][ 3 ] 12.000000 3.000000 0.000000 +[ 13 ][ 0 ] 13.000000 0.000000 0.000000 +[ 13 ][ 1 ] 13.000000 1.000000 0.000000 +[ 13 ][ 2 ] 13.000000 2.000000 0.000000 +[ 13 ][ 3 ] 13.000000 3.000000 0.000000 +[ 14 ][ 0 ] 14.000000 0.000000 0.000000 +[ 14 ][ 1 ] 14.000000 1.000000 0.000000 +[ 14 ][ 2 ] 14.000000 2.000000 0.000000 +[ 14 ][ 3 ] 14.000000 3.000000 0.000000 +[ 15 ][ 0 ] 15.000000 0.000000 0.000000 +[ 15 ][ 1 ] 15.000000 1.000000 0.000000 +[ 15 ][ 2 ] 15.000000 2.000000 0.000000 +[ 15 ][ 3 ] 15.000000 3.000000 0.000000 diff --git a/src/test/01-Basic/227-dangle-ref-func2.ck b/src/test/01-Basic/227-dangle-ref-func2.ck new file mode 100644 index 000000000..e19fa0d51 --- /dev/null +++ b/src/test/01-Basic/227-dangle-ref-func2.ck @@ -0,0 +1,16 @@ +class Foo +{ + int i; +} + +fun void go( Foo foo ) +{ + if( Machine.refcount(foo) != 2 ) <<< "problem found" >>>; +} + +Foo foo; + +go( foo ); + +// test +if( Machine.refcount(foo) == 1 ) <<< "success" >>>; \ No newline at end of file