diff --git a/compiler/hlcgobj.pas b/compiler/hlcgobj.pas index e73930720b..3f4300720c 100644 --- a/compiler/hlcgobj.pas +++ b/compiler/hlcgobj.pas @@ -4523,7 +4523,11 @@ implementation if (tparavarsym(p).varspez=vs_value) then begin include(current_procinfo.flags,pi_needs_implicit_finally); - location_get_data_ref(list,tparavarsym(p).vardef,tparavarsym(p).localloc,href,is_open_array(tparavarsym(p).vardef),sizeof(pint)); + location_get_data_ref(list,tparavarsym(p).vardef,tparavarsym(p).localloc,href, + is_open_array(tparavarsym(p).vardef) or + ((target_info.system in systems_caller_copy_addr_value_para) and + paramanager.push_addr_param(vs_value,tparavarsym(p).vardef,current_procinfo.procdef.proccalloption)), + sizeof(pint)); if is_open_array(tparavarsym(p).vardef) then begin if paramanager.push_high_param(tparavarsym(p).varspez,tparavarsym(p).vardef,current_procinfo.procdef.proccalloption) then @@ -4543,7 +4547,8 @@ implementation end; end; { open arrays can contain elements requiring init/final code, so the else has been removed here } - if (tparavarsym(p).varspez=vs_value) and + if not(target_info.system in systems_caller_copy_addr_value_para) and + (tparavarsym(p).varspez=vs_value) and (is_open_array(tparavarsym(p).vardef) or is_array_of_const(tparavarsym(p).vardef)) then begin @@ -4581,7 +4586,11 @@ implementation if not((tparavarsym(p).vardef.typ=variantdef) and paramanager.push_addr_param(tparavarsym(p).varspez,tparavarsym(p).vardef,current_procinfo.procdef.proccalloption)) then begin - location_get_data_ref(list,tparavarsym(p).vardef,tparavarsym(p).initialloc,href,is_open_array(tparavarsym(p).vardef),sizeof(pint)); + location_get_data_ref(list,tparavarsym(p).vardef,tparavarsym(p).initialloc,href, + is_open_array(tparavarsym(p).vardef) or + ((target_info.system in systems_caller_copy_addr_value_para) and + paramanager.push_addr_param(vs_value,tparavarsym(p).vardef,current_procinfo.procdef.proccalloption)), + sizeof(pint)); if is_open_array(tparavarsym(p).vardef) then begin if paramanager.push_high_param(tparavarsym(p).varspez,tparavarsym(p).vardef,current_procinfo.procdef.proccalloption) then @@ -4656,7 +4665,8 @@ implementation { generate copies of call by value parameters, must be done before the initialization and body is parsed because the refcounts are incremented using the local copies } - current_procinfo.procdef.parast.SymList.ForEachCall(@g_copyvalueparas,list); + if not(target_info.system in systems_caller_copy_addr_value_para) then + current_procinfo.procdef.parast.SymList.ForEachCall(@g_copyvalueparas,list); if not(po_assembler in current_procinfo.procdef.procoptions) then begin diff --git a/compiler/ncal.pas b/compiler/ncal.pas index 3fc004a8a1..71c4d6fe3f 100644 --- a/compiler/ncal.pas +++ b/compiler/ncal.pas @@ -201,6 +201,10 @@ interface fparainit, fparacopyback: tnode; procedure handlemanagedbyrefpara(orgparadef: tdef);virtual;abstract; + { on some targets, value parameters that are passed by reference must + be copied to a temp location by the caller (and then a reference to + this temp location must be passed) } + procedure copy_value_by_ref_para; public callparaflags : tcallparaflags; parasym : tparavarsym; @@ -592,6 +596,208 @@ implementation TCALLPARANODE ****************************************************************************} + procedure tcallparanode.copy_value_by_ref_para; + var + initstat, + copybackstat, + finistat: tstatementnode; + finiblock: tblocknode; + paratemp: ttempcreatenode; + arraysize, + arraybegin: tnode; + lefttemp: ttempcreatenode; + vardatatype, + temparraydef: tdef; + begin + { this routine is for targets where by-reference value parameters need + to be copied by the caller. It's basically the node-level equivalent + of thlcgobj.g_copyvalueparas } + + { in case of an array constructor, we don't need a copy since the array + constructor itself is already constructed on the fly (and hence if + it's modified by the caller, that's no problem) } + if not is_array_constructor(left.resultdef) then + begin + fparainit:=internalstatements(initstat); + fparacopyback:=internalstatements(copybackstat); + finiblock:=internalstatements(finistat); + paratemp:=nil; + + { making a copy of an open array, an array of const or a dynamic + array requires dynamic memory allocation since we don't know the + size at compile time } + if is_open_array(left.resultdef) or + is_array_of_const(left.resultdef) or + (is_dynamic_array(left.resultdef) and + is_open_array(parasym.vardef)) then + begin + paratemp:=ctempcreatenode.create(voidpointertype,voidpointertype.size,tt_persistent,true); + if is_dynamic_array(left.resultdef) then + begin + { note that in insert_typeconv, this dynamic array was + already converted into an open array (-> dereferenced) + and then its resultdef was restored to the original + dynamic array one -> get the address before treating it + as a dynamic array here } + { first restore the actual resultdef of left } + temparraydef:=left.resultdef; + left.resultdef:=parasym.vardef; + { get its address } + lefttemp:=ctempcreatenode.create(voidpointertype,voidpointertype.size,tt_persistent,true); + addstatement(initstat,lefttemp); + addstatement(finistat,ctempdeletenode.create(lefttemp)); + addstatement(initstat, + cassignmentnode.create( + ctemprefnode.create(lefttemp), + caddrnode.create_internal(left) + ) + ); + { restore the resultdef } + left.resultdef:=temparraydef; + { now treat that address (correctly) as the original + dynamic array to get its start and length } + arraybegin:=cvecnode.create( + ctypeconvnode.create_explicit(ctemprefnode.create(lefttemp), + left.resultdef), + genintconstnode(0) + ); + arraysize:=caddnode.create(muln, + geninlinenode(in_length_x,false, + ctypeconvnode.create_explicit(ctemprefnode.create(lefttemp), + left.resultdef) + ), + genintconstnode(tarraydef(left.resultdef).elementdef.size) + ); + end + else + begin + { no problem here that left is used multiple times, as + sizeof() will simply evaluate to the high parameter } + arraybegin:=left.getcopy; + arraysize:=geninlinenode(in_sizeof_x,false,left); + end; + addstatement(initstat,paratemp); + { paratemp:=getmem(sizeof(para)) } + addstatement(initstat, + cassignmentnode.create( + ctemprefnode.create(paratemp), + ccallnode.createintern('fpc_getmem', + ccallparanode.create( + arraysize.getcopy,nil + ) + ) + ) + ); + { move(para,temp,sizeof(arr)) (no "left.getcopy" below because + we replace left afterwards) } + addstatement(initstat, + ccallnode.createintern('MOVE', + ccallparanode.create( + arraysize, + ccallparanode.create( + cderefnode.create(ctemprefnode.create(paratemp)), + ccallparanode.create( + arraybegin,nil + ) + ) + ) + ) + ); + { no reference count increases, that's still done on the callee + side because for compatibility with targets that perform this + copy on the callee side, that should only be done for non- + assember functions (and we can't know that 100% certain here, + e.g. in case of external declarations) (*) } + + { free the memory again after the call: freemem(paratemp) } + addstatement(finistat, + ccallnode.createintern('fpc_freemem', + ccallparanode.create( + ctemprefnode.create(paratemp),nil + ) + ) + ); + { replace the original parameter with a dereference of the + temp typecasted to the same type as the original parameter + (don't free left, it has been reused above) } + left:=ctypeconvnode.create_internal( + cderefnode.create(ctemprefnode.create(paratemp)), + left.resultdef); + end + else if is_shortstring(parasym.vardef) then + begin + { the shortstring parameter may have a different size than the + parameter type -> assign and truncate/extend } + paratemp:=ctempcreatenode.create(parasym.vardef,parasym.vardef.size,tt_persistent,false); + addstatement(initstat,paratemp); + { assign shortstring } + addstatement(initstat, + cassignmentnode.create( + ctemprefnode.create(paratemp),left + ) + ); + { replace parameter with temp (don't free left, it has been + reused above) } + left:=ctemprefnode.create(paratemp); + end + else if parasym.vardef.typ=variantdef then + begin + vardatatype:=search_system_type('TVARDATA').typedef; + paratemp:=ctempcreatenode.create(vardatatype,vardatatype.size,tt_persistent,false); + addstatement(initstat,paratemp); + addstatement(initstat, + ccallnode.createintern('fpc_variant_copy_overwrite', + ccallparanode.create( + ctypeconvnode.create_explicit(ctemprefnode.create(paratemp), + vardatatype + ), + ccallparanode.create(ctypeconvnode.create_explicit(left, + vardatatype), + nil + ) + ) + ) + ); + { replace parameter with temp (don't free left, it has been + reused above) } + left:=ctypeconvnode.create_explicit(ctemprefnode.create(paratemp),parasym.vardef); + end + else if is_managed_type(left.resultdef) then + begin + { don't increase/decrease the reference count here, will be done by + the callee (see (*) above) -> typecast to array of byte + for the assignment to the temp } + temparraydef:=getarraydef(u8inttype,left.resultdef.size); + paratemp:=ctempcreatenode.create(temparraydef,temparraydef.size,tt_persistent,false); + addstatement(initstat,paratemp); + addstatement(initstat, + cassignmentnode.create( + ctemprefnode.create(paratemp), + ctypeconvnode.create_internal(left,temparraydef) + ) + ); + left:=ctypeconvnode.create_explicit(ctemprefnode.create(paratemp),left.resultdef); + end + else + begin + paratemp:=ctempcreatenode.create(left.resultdef,left.resultdef.size,tt_persistent,false); + addstatement(initstat,paratemp); + addstatement(initstat, + cassignmentnode.create(ctemprefnode.create(paratemp),left) + ); + { replace parameter with temp (don't free left, it has been + reused above) } + left:=ctemprefnode.create(paratemp); + end; + addstatement(finistat,ctempdeletenode.create(paratemp)); + addstatement(copybackstat,finiblock); + firstpass(fparainit); + firstpass(left); + firstpass(fparacopyback); + end; + end; + + constructor tcallparanode.create(expr,next : tnode); begin @@ -721,6 +927,7 @@ implementation tcallparanode(right).firstcallparan; if not assigned(left.resultdef) then get_paratype; + if assigned(parasym) and (target_info.system in systems_managed_vm) and (parasym.varspez in [vs_var,vs_out,vs_constref]) and @@ -729,6 +936,23 @@ implementation (left.nodetype<>nothingn) then handlemanagedbyrefpara(left.resultdef); + { for targets that have to copy "value parameters by reference" on the + caller side + + aktcallnode may not be assigned in case firstcallparan is called for + fake parameters to inline nodes (in that case, we don't have a real + call and hence no "caller side" either) + } + if assigned(aktcallnode) and + (target_info.system in systems_caller_copy_addr_value_para) and + ((assigned(parasym) and + (parasym.varspez=vs_value)) or + (cpf_varargs_para in callparaflags)) and + (left.nodetype<>nothingn) and + paramanager.push_addr_param(vs_value,left.resultdef, + aktcallnode.procdefinition.proccalloption) then + copy_value_by_ref_para; + { does it need to load RTTI? } if assigned(parasym) and (parasym.varspez=vs_out) and (cs_create_pic in current_settings.moduleswitches) and diff --git a/compiler/ncgutil.pas b/compiler/ncgutil.pas index 56db5b1b21..c8862dc5a8 100644 --- a/compiler/ncgutil.pas +++ b/compiler/ncgutil.pas @@ -698,7 +698,11 @@ implementation if not((tparavarsym(p).vardef.typ=variantdef) and paramanager.push_addr_param(tparavarsym(p).varspez,tparavarsym(p).vardef,current_procinfo.procdef.proccalloption)) then begin - hlcg.location_get_data_ref(list,tparavarsym(p).vardef,tparavarsym(p).initialloc,href,is_open_array(tparavarsym(p).vardef),sizeof(pint)); + hlcg.location_get_data_ref(list,tparavarsym(p).vardef,tparavarsym(p).initialloc,href, + is_open_array(tparavarsym(p).vardef) or + ((target_info.system in systems_caller_copy_addr_value_para) and + paramanager.push_addr_param(vs_value,tparavarsym(p).vardef,current_procinfo.procdef.proccalloption)), + sizeof(pint)); if is_open_array(tparavarsym(p).vardef) then begin { open arrays do not contain correct element count in their rtti, @@ -1330,7 +1334,8 @@ implementation { generate copies of call by value parameters, must be done before the initialization and body is parsed because the refcounts are incremented using the local copies } - current_procinfo.procdef.parast.SymList.ForEachCall(@copyvalueparas,list); + if not(target_info.system in systems_caller_copy_addr_value_para) then + current_procinfo.procdef.parast.SymList.ForEachCall(@copyvalueparas,list); {$ifdef powerpc} { unget the register that contains the stack pointer before the procedure entry, } { which is used to access the parameters in their original callee-side location } diff --git a/compiler/pparautl.pas b/compiler/pparautl.pas index ee195e130a..de10f0a1a8 100644 --- a/compiler/pparautl.pas +++ b/compiler/pparautl.pas @@ -335,7 +335,8 @@ implementation if (varspez=vs_value) and paramanager.push_addr_param(varspez,vardef,pd.proccalloption) and not(is_open_array(vardef) or - is_array_of_const(vardef)) then + is_array_of_const(vardef)) and + not(target_info.system in systems_caller_copy_addr_value_para) then include(varoptions,vo_has_local_copy); { needs high parameter ? } diff --git a/compiler/systems.pas b/compiler/systems.pas index 3831165ef4..19f741f89b 100644 --- a/compiler/systems.pas +++ b/compiler/systems.pas @@ -338,6 +338,10 @@ interface system_jvm_android32 ]; + { all systems where a value parameter passed by reference must be copied + on the caller side rather than on the callee side } + systems_caller_copy_addr_value_para = [system_aarch64_darwin]; + { pointer checking (requires special code in FPC_CHECKPOINTER, and can never work for libc-based targets or any other program linking to an external library)