{ Copyright (c) 2014 by Jonas Maebe, Member of the Free Pascal development team. This unit implements helper routines for "blocks" support (http://en.wikipedia.org/wiki/Blocks_(C_language_extension) ) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. **************************************************************************** } unit blockutl; {$i fpcdefs.inc} interface uses node,nld,ncnv, symtype,symdef; { accepts a loadnode for a procdef returns a node representing the converted code to implement this conversion (this node is valid both for typed constant declarations and in function bodies). The node is not reused } function generate_block_for_procaddr(procloadnode: tloadnode): tnode; { for a procdef, return a recorddef representing a block literal for this procdef for a procvardef, return a basic recorddef representing a block literal with enough info to call this procvardef } function get_block_literal_type_for_proc(pd: tabstractprocdef): trecorddef; implementation uses verbose,globtype,cutils,constexp, pass_1,pparautl,fmodule, aasmdata, nbas,ncon,nmem,nutils, symbase,symconst,symtable,symsym,symcreat,objcutil,defutil, paramgr; function get_block_literal_type_for_proc(pd: tabstractprocdef): trecorddef; begin if pd.typ=procvardef then result:=trecorddef(search_named_unit_globaltype('BLOCKRTL','FPC_BLOCK_LITERAL_BASE',true).typedef) else if pd.is_addressonly then result:=trecorddef(search_named_unit_globaltype('BLOCKRTL','FPC_BLOCK_LITERAL_STATIC',true).typedef) { todo: nested functions and Objective-C methods } else if not is_nested_pd(pd) and not is_objcclass(tdef(pd.owner.defowner)) then result:=trecorddef(search_named_unit_globaltype('BLOCKRTL','FPC_BLOCK_LITERAL_COMPLEX_PROCVAR',true).typedef) else internalerror(2014071304); end; function get_block_literal_isa(orgpd: tprocdef): tstaticvarsym; var srsym: tsym; srsymtable: tsymtable; name: tidstring; begin if orgpd.is_addressonly then name:='_NSCONCRETEGLOBALBLOCK' else name:='_NSCONCRETESTACKBLOCK'; if not searchsym_in_named_module('BLOCKRTL',name,srsym,srsymtable) or (srsym.typ<>staticvarsym) then internalerror(2014071501); result:=tstaticvarsym(srsym); end; function get_block_literal_flags(orgpd, invokepd: tprocdef): longint; { BlockLiteralFlags } const BLOCK_HAS_COPY_DISPOSE = 1 shl 25; BLOCK_HAS_CXX_OBJ = 1 shl 26; BLOCK_IS_GLOBAL = 1 shl 28; BLOCK_USE_STRET = 1 shl 29; BLOCK_HAS_SIGNATURE = 1 shl 30; BLOCK_HAS_EXTENDED_LAYOUT = 1 shl 31; begin result:=0; { BLOCK_HAS_COPY_DISPOSE : copy/dispose will be necessary once we support nested procedures, in case they capture reference counted types, Objective-C class instances or block-type variables } { BLOCK_HAS_CXX_OBJ: we don't support C++ (stack-based) class instances yet } { BLOCK_IS_GLOBAL: set in case the block does not capture any local state; used for global functions and in theory also possible for nested functions that do not access any variables from their parentfp context } if orgpd.is_addressonly then result:=result or BLOCK_IS_GLOBAL; { BLOCK_USE_STRET: set in case the invoke function returns its result via a hidden parameter } if paramanager.ret_in_param(invokepd.returndef,orgpd) then result:=result or BLOCK_USE_STRET; { BLOCK_HAS_SIGNATURE: only if this bit is set, the above bit will actually be taken into account (for backward compatibility). We need it so that our invoke function isn't called as a variadic function, but on the downside this requires Mac OS X 10.7 or later } result:=result or BLOCK_HAS_SIGNATURE; { BLOCK_HAS_EXTENDED_LAYOUT: no documentation about what this means or what it's good for (clang adds it for Objective-C 1 platforms in case garbage collection is switched off, but then you also have to actually generate this layout) } end; function get_block_literal_descriptor(invokepd: tprocdef; block_literal_size: tcgint): tstaticvarsym; var descriptordef: tdef; descriptor: tstaticvarsym; name: tsymstr; srsym: tsym; srsymtable: tsymtable; begin (* FPC_Block_descriptor_simple = record reserved: culong; Block_size: culong; { signatures are only for the "ABI.2010.3.16" version, but that's all we support because otherwise the callback has to be a C-style variadic function, which we cannot (yet?) generate } signature: pchar; end; *) { must be a valid Pascal identifier, because we will reference it when constructing the block initialiser } { we don't have to include the moduleid in this mangledname, because the invokepd is a local procedure in the current unit -> unique_id_str by itself is unique } name:='__FPC_BLOCK_DESCRIPTOR_SIMPLE_'+invokepd.unique_id_str; { already exists -> return } if searchsym(name,srsym,srsymtable) then begin if srsym.typ<>staticvarsym then internalerror(2014071402); result:=tstaticvarsym(srsym); exit; end; { find the type of the descriptor structure } descriptordef:=search_named_unit_globaltype('BLOCKRTL','FPC_BLOCK_DESCRIPTOR_SIMPLE',true).typedef; { create new static variable } descriptor:=cstaticvarsym.create(name,vs_value,descriptordef,[],true); symtablestack.top.insert(descriptor); include(descriptor.symoptions,sp_internal); { create typed constant for the descriptor } str_parse_typedconst(current_asmdata.AsmLists[al_const], '(reserved: 0; Block_size: '+tostr(block_literal_size)+ '; signature: '''+objcencodemethod(invokepd)+''');',descriptor); result:=descriptor; end; { creates a wrapper function for pd with the C calling convention and an extra first parameter pointing to the block "self" pointer. This wrapper is what will be assigned to the "invoke" field of the block } function get_invoke_wrapper(orgpd: tprocdef; orgpv: tprocvardef): tprocdef; var wrappername: TIDString; srsym: tsym; srsymtable: tsymtable; begin { the copy() is to ensure we don't overflow the maximum identifier length; the combination of owner.moduleid and defid will make the name unique } wrappername:='__FPC_BLOCK_INVOKE_'+upper(copy(orgpd.procsym.realname,1,60))+'_'+tostr(orgpd.owner.moduleid)+'_'+orgpd.unique_id_str; { already an invoke wrapper for this procsym -> reuse } if searchsym(wrappername,srsym,srsymtable) then begin if (srsym.typ<>procsym) or (tprocsym(srsym).procdeflist.count<>1) then internalerror(2014071503); result:=tprocdef(tprocsym(srsym).procdeflist[0]); exit; end; { bare copy, so that self etc are not inserted } result:=tprocdef(orgpd.getcopyas(procdef,pc_bareproc,'')); { will be called accoding to the ABI conventions } result.proccalloption:=pocall_cdecl; { add po_is_block so that a block "self" pointer gets added (of the type returned by get_block_literal_type_for_proc()) } include(result.procoptions,po_is_block); { now insert self/vmt/funcret according to the newly set calling convention } insert_self_and_vmt_para(result); insert_funcret_para(result); finish_copied_procdef(result,wrappername,current_module.localsymtable,nil); if orgpd.is_addressonly then begin result.synthetickind:=tsk_callthrough; result.skpara:=orgpd; end else begin { alias for the type to invoke the procvar, used in the symcreat handling of tsk_block_invoke_procvar } result.localst.insert(ctypesym.create('__FPC_BLOCK_INVOKE_PV_TYPE',orgpv,true)); result.synthetickind:=tsk_block_invoke_procvar; end; end; { compose a block literal for a static block (one without context) } function get_global_proc_literal_sym(blockliteraldef: tdef; blockisasym: tstaticvarsym; blockflags: longint; invokepd: tprocdef; descriptor: tstaticvarsym): tstaticvarsym; var literalname: TIDString; srsym: tsym; srsymtable: tsymtable; begin literalname:='block_literal_for_'+invokepd.procsym.realname; { already exists -> return } if searchsym(literalname,srsym,srsymtable) then begin if srsym.typ<>staticvarsym then internalerror(2014071506); result:=tstaticvarsym(srsym); exit; end; { create new block literal symbol } result:=cstaticvarsym.create( '$'+literalname, vs_value, blockliteraldef,[],true); include(result.symoptions,sp_internal); symtablestack.top.insert(result); { initialise it } str_parse_typedconst(current_asmdata.AsmLists[al_const], '(base: (isa : @'+blockisasym.realname+ '; flags : '+tostr(blockflags)+ '; reserved : 0'+ '; invoke : @'+invokepd.procsym.realname+ '; descriptor: @'+descriptor.realname+ '));', result); end; { compose an on-stack block literal for a "procedure of object" } function get_pascal_method_literal(blockliteraldef: tdef; blockisasym: tstaticvarsym; blockflags: longint; procvarnode: tnode; invokepd: tprocdef; orgpv: tprocvardef; descriptor: tstaticvarsym): tnode; var statement: tstatementnode; literaltemp: ttempcreatenode; begin result:=internalstatements(statement); { create new block literal structure } literaltemp:=ctempcreatenode.create(blockliteraldef,blockliteraldef.size,tt_persistent,false); addstatement(statement,literaltemp); { temp.base.isa:=@blockisasym } addstatement(statement,cassignmentnode.create( genloadfield(genloadfield(ctemprefnode.create(literaltemp),'BASE'),'ISA'), caddrnode.create(cloadnode.create(blockisasym,blockisasym.owner)))); { temp.base.flags:=blockflags } addstatement(statement,cassignmentnode.create( genloadfield(genloadfield(ctemprefnode.create(literaltemp),'BASE'),'FLAGS'), genintconstnode(blockflags))); { temp.base.reserved:=0 } addstatement(statement,cassignmentnode.create( genloadfield(genloadfield(ctemprefnode.create(literaltemp),'BASE'),'RESERVED'), genintconstnode(0))); { temp.base.invoke:=tmethod(@invokepd) } addstatement(statement,cassignmentnode.create( genloadfield(genloadfield(ctemprefnode.create(literaltemp),'BASE'),'INVOKE'), ctypeconvnode.create_proc_to_procvar( cloadnode.create_procvar(invokepd.procsym,invokepd,invokepd.owner)))); { temp.base.descriptor:=@descriptor } addstatement(statement,cassignmentnode.create( genloadfield(genloadfield(ctemprefnode.create(literaltemp),'BASE'),'DESCRIPTOR'), caddrnode.create(cloadnode.create(descriptor,descriptor.owner)))); { temp.pv:=tmethod(@orgpd) } addstatement(statement,cassignmentnode.create( ctypeconvnode.create_explicit(genloadfield(ctemprefnode.create(literaltemp),'PV'),orgpv), procvarnode.getcopy)); { and return the address of the temp } addstatement(statement,caddrnode.create(ctemprefnode.create(literaltemp))); { typecheck this now, because the current source may be written in TP/ Delphi/MacPas mode and the above node tree has been constructed for ObjFPC mode, which has been set by replace_scanner (in Delphi, the assignment to invoke would be without the proc_to_procvar conversion) } typecheckpass(result); end; function generate_block_for_procaddr(procloadnode: tloadnode): tnode; var procvarnode: tnode; { procvardef representing the original function we want to invoke } orgpv: tprocvardef; { procdef of the original function we want to invoke } orgpd, { procdef for the invoke-wrapper that we generated to call the original function via a procvar } invokepd: tprocdef; blockliteraldef: tdef; descriptor, blockisasym, blockliteralsym: tstaticvarsym; blockflags: longint; old_symtablestack: tsymtablestack; sstate: tscannerstate; begin result:=nil; { supported? (should be caught earlier) } if (procloadnode.resultdef.typ<>procdef) or is_nested_pd(tprocdef(procloadnode.resultdef)) or is_objcclass(tdef(procloadnode.resultdef.owner.defowner)) then internalerror(2014071401); { add every symbol that we create here to the unit-level symbol table } old_symtablestack:=symtablestack; symtablestack:=old_symtablestack.getcopyuntil(current_module.localsymtable); { save scanner state } replace_scanner('block literal creation',sstate); { def representing the original function } orgpd:=tprocdef(procloadnode.resultdef); { def representing the corresponding procvar type } procvarnode:=ctypeconvnode.create_proc_to_procvar(procloadnode.getcopy); typecheckpass(procvarnode); orgpv:=tprocvardef(procvarnode.resultdef); { get blockdef for this kind of procdef } blockliteraldef:=get_block_literal_type_for_proc(orgpd); { get the invoke wrapper } invokepd:=get_invoke_wrapper(orgpd,orgpv); { get the descriptor } descriptor:=get_block_literal_descriptor(invokepd,blockliteraldef.size); { get the ISA pointer for the literal } blockisasym:=get_block_literal_isa(orgpd); { get the flags for the block } blockflags:=get_block_literal_flags(orgpd,invokepd); { global/simple procedure -> block literal is a typed constant } if orgpd.is_addressonly then begin blockliteralsym:=get_global_proc_literal_sym(blockliteraldef,blockisasym,blockflags,invokepd,descriptor); { result: address of the block literal } result:=caddrnode.create(cloadnode.create(blockliteralsym,blockliteralsym.owner)); end else begin result:=get_pascal_method_literal(blockliteraldef,blockisasym,blockflags,procvarnode,invokepd,orgpv,descriptor) end; procvarnode.free; { restore scanner } restore_scanner(sstate); { restore symtable stack } symtablestack.free; symtablestack:=old_symtablestack; end; end.