Executables and Compiling

Forum for discussion about the documentation project.
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Executables and Compiling

Post by coderJeff »

For these module constructors and module destructors, it really should be noted with a big WARNING on their use. Because this code is run outside of user code, it's quite likely that fbc's error checking and some runtime facilities won't work as expected.

The result is that if an error occurs in the module constructor (implicit or explicit), that you can end up with the program aborting with no message even when '-exx' option for extra error checking is used. I think there was a discussion about module constructor / destructor priorities elsewhere on the forum and maybe this behaviour could be fixed up a little bit.

An alternate pattern to follow would be to do what the powerbasic users are doing and have only one module, and include all the sources. There's benefits to doing it that way, which I won't try to describe. I'll leave that to the pb experts.

A high level description of the start-up framework
1) At compile time make arrays of addresses for all the module constructors and destructors
2) At link time, store the arrays in the executable
3) At run time, the start-up framework will:
- call all the module constructors in the array
- call the main module user code
- on exit (or error), call all the module destructors (usually, depends on how the program failed though)

The start-up framework does other stuff too. What's important to note that the user (programmer) doesn't have a lot of control over the order in which constructors / destructors are executed. It's all handled by the start-up framework (and to some extent other compilers and the linker).

Overall, I think it would be good practice that module-level code only be in the main module. Currently, there's no compiler option that would enforce that, or for example require an explicit definition for the start of user code (aka main function).

For now though, I think should continue to document current state of fbc to give an understanding of it's workings. I don't really want to go way off on trying to fix everything that's wrong with fbc or what features that need to be added. I really just want to have some docs on the current state of fbc.

Next post I'll show an example where we can put the concept of module constructors & destructors in to user code with a few helper functions. I think this can provide an alternative method and also help explain what's going on under the hood when discussing the multiple modules and initializing and exiting code.
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Executables and Compiling

Post by coderJeff »

So far as discussed in this topic, the module constructors / destructors are a low level feature of the start-up framework which involves gcc & c runtime code that executes before and after our usercode. We don't have much control over it.

Here we're going to build our own user code for constructors / destructors. We will still use the built-in module constructors / destructors (aka global ctors & dtors) but only to register the initialization and exit procedures (aka user ctors and dtors) in our user code. And we delay the execution of our procedures until we have control in main module user code.

To make this work, we need a little framework for registering our user code ctors & dtors. I set this up to build as a library file.
- cdtors.bas
- cdtors.bi
$ fbc -lib -g cdtors.bas

And a demo program:
- main.bas
- f1.bas
- f2.bas
$ fbc main.bas f1.bas f2.bas

Example OUTPUT:
f2.bas:CTOR
f1.bas:CTORA
f1.bas:CTORB
main.bas:CTOR
MAIN MODULE USER CODE
main.bas:DTOR
f1.bas:DTORB
f1.bas:DTORA
f2.bas:DTOR



cdtors.bi
API for the cdtors routines

Code: Select all

#pragma once

#inclib "cdtors"

#ifndef NULL
	#define NULL 0
#endif

namespace cdtors

	type USERCTORDTORPROC as sub()

	type USERCTORDTORNODE
		proc as USERCTORDTORPROC     '' pointer to the ctor/dtor sub
		next as USERCTORDTORNODE ptr '' pointer to next node in the list
	end type

	declare sub registerUserCtorNode _
		( _
			byval node as USERCTORDTORNODE ptr, _
			byval proc as USERCTORDTORPROC _
		)

	declare sub registerUserDtorNode _
		( _
			byval node as USERCTORDTORNODE ptr, _
			byval proc as USERCTORDTORPROC _
		)

	declare sub doUserCtors()
	declare sub doUserDtors()

end namespace

#macro RegisterUserCTOR( procptr )
	__FB_UNIQUEID_PUSH__( usercdtors )
	static __FB_UNIQUEID__( usercdtors ) as cdtors.USERCTORDTORNODE
	cdtors.registerUserCtorNode( @__FB_UNIQUEID__( usercdtors ), procptr )
	__FB_UNIQUEID_POP__( usercdtors )
#endmacro

#macro RegisterUserDTOR( procptr )
	__FB_UNIQUEID_PUSH__( usercdtors )
	static __FB_UNIQUEID__( usercdtors ) as cdtors.USERCTORDTORNODE
	cdtors.registerUserDtorNode( @__FB_UNIQUEID__( usercdtors ), procptr )
	__FB_UNIQUEID_POP__( usercdtors )
#endmacro
cdtors.bas

Code: Select all

#include once "cdtors.bi"

namespace cdtors

	'' singly-linked list of constructors and destructors
	dim shared ctorhead as USERCTORDTORNODE ptr = 0 '' NULL
	dim shared dtorhead as USERCTORDTORNODE ptr = 0 '' NULL

	private function findUserNodeByProc _
		( _
			byval head as USERCTORDTORNODE ptr, _
			byval proc as USERCTORDTORPROC _
		) as USERCTORDTORNODE ptr

		while( head )
			if( head->proc = proc ) then
				return head
			end if
			head = head->next
		wend
		return NULL
	end function

	private sub linkUserNodeFirst _
		( _
			byref head as USERCTORDTORNODE ptr, _
			byval node as USERCTORDTORNODE ptr, _
			byval proc as USERCTORDTORPROC _
		)

		'' initialize the node and add it to the head
		node->proc = proc
		node->next = head

		head = node

	end sub

	private sub linkUserNodeLast _
		( _
			byref head as USERCTORDTORNODE ptr, _
			byval node as USERCTORDTORNODE ptr, _
			byval proc as USERCTORDTORPROC _
		)

		if( head = NULL ) then
			linkUserNodeFirst( head, node, proc )
			return
		end if

		'' find the tail
		var tail = head
		while( tail->next )
			tail = tail->next
		wend

		'' initialize the node and add it to the tail
		node->proc = proc
		node->next = NULL

		tail->next = node
	end sub

	private function canInitUserNode _
		( _
			byval head as USERCTORDTORNODE ptr, _
			byval node as USERCTORDTORNODE ptr, _
			byval proc as USERCTORDTORPROC _
		) as USERCTORDTORNODE ptr

		'' can't add a node if it's NULL
		if( node = NULL ) then
			return NULL
		end if

		'' don't add a proc if it's NULL
		if( proc = NULL ) then
			return NULL
		end if

		'' don't add a proc if we already have it
		if( findUserNodeByProc( head, proc ) ) then
			return NULL
		end if

		return node
	end function

	public sub registerUserCtorNode _
		( _
			byval node as USERCTORDTORNODE ptr, _
			byval proc as USERCTORDTORPROC _
		)
		if( canInitUserNode( ctorhead, node, proc ) ) then
			linkUserNodeLast( ctorhead, node, proc )
		end if
	end sub

	public sub registerUserDtorNode _
		( _
			byval node as USERCTORDTORNODE ptr, _
			byval proc as USERCTORDTORPROC _
		)
		if( canInitUserNode( dtorhead, node, proc ) ) then
			linkUserNodeFirst( dtorhead, node, proc )
		end if
	end sub

	private sub callCtorDtorList( byval node as USERCTORDTORNODE ptr )
		while( node andalso node->proc )
			node->proc()
			node = node->next
		wend
	end sub

	public sub doUserDtors()
		'' only allow calling doUserDtors() once
		static called_already as boolean = false
		if( called_already ) then
			exit sub
		end if
		called_already = true

		callCtorDtorList( dtorhead )
	end sub

	private sub doUserDtorsAtExit cdecl ()
		doUserDtors()
	end sub

	public sub doUserCtors()

		'' only allow calling doUserCtors() once
		static called_already as boolean = false
		if( called_already ) then
			exit sub
		end if
		called_already = true

		callCtorDtorList( ctorhead )

		'' fb_atexit( byval handler as sub cdecl () )
		'' is an undocumented built-in.  It's used to
		'' call destructors for static variables when
		'' the program ends

		fb_atexit( procptr( doUserDtorsAtExit ) )
	end sub

end namespace
main.bas

Code: Select all

#include once "cdtors.bi"

private sub ctor()
	print __FILE__ & ":" & __FUNCTION__
end sub

private sub dtor()
	print __FILE__ & ":" & __FUNCTION__
end sub


'' start of MAIN MODULE user code

'' we can register CTORS/DTORS from user code
'' before we call cdtors.doUserCtors()
RegisterUserCTOR( procptr( ctor ) )
RegisterUserDTOR( procptr( dtor ) )


'' do all the user constructors
cdtors.doUserCtors()

scope
	print "MAIN MODULE USER CODE"
end scope

'' exit by calling all the dtors
cdtors.doUserDtors()
f1.bas

Code: Select all

#include once "cdtors.bi"

private sub ctorA()
	print __FILE__ & ":" & __FUNCTION__
end sub

private sub ctorB()
	print __FILE__ & ":" & __FUNCTION__
end sub

private sub dtorA()
	print __FILE__ & ":" & __FUNCTION__
end sub

private sub dtorB()
	print __FILE__ & ":" & __FUNCTION__
end sub

RegisterUserCTOR( procptr( ctorA ) )
RegisterUserDTOR( procptr( dtorA ) )

RegisterUserCTOR( procptr( ctorB ) )
RegisterUserDTOR( procptr( dtorB ) )
f2.bas

Code: Select all

#include once "cdtors.bi"

private sub ctor()
	print __FILE__ & ":" & __FUNCTION__
end sub

private sub dtor()
	print __FILE__ & ":" & __FUNCTION__
end sub

RegisterUserCTOR( procptr( ctor ) )
RegisterUserDTOR( procptr( dtor ) )

HOW IT WORKS:
1) RegisterUserCTOR( procptr( ctor ) ) and RegisterUserDTOR( procptr( dtor ) ) functions are called from the built-in module constructors (Global Ctors).
2) By registering a procedure, it's add to a singly linked list, either the CTOR list or the DTOR list
3) To avoid dealing with dynamic memory management in a Global Ctor, RegisterUserCTOR() and RegisterUserDTOR() are actually macros that allocate a static variable to hold the address of the procedure and a pointer to the next node in the list
4) Once execution gets to the user code, it's up the the user to call doUserCtors()
5) When doUserCtors() completes, it also adds an exit handler for doUserDtors() that will be called automatically when the program exits
6) Or user can call doUserDtors() directly for cleanup.

IDEAS for EXTENSIONS:
Now that the startup / exit code is under the control of the user, there could be a number of extensions could add to this:
- Ctors and Dtors with returning some value to indicate success or failure. This could allow presenting the user with more error information or logging diagnostics to a file as part of the framework itself.
- groupings or priorities for ctors and dtors to control the order or dependencies of initialization or cleanup. The example above just adds the procedures in the order that they are registered (reverse order for dtors).
- Passing parameter(s) to constructors to control initialization
- build the framework in to the compiler / run time keep and use linker / gcc / crt global ctors/dtors only for really low level stuff and not general user code
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Executables and Compiling

Post by fxm »

I will perhaps soon (if no one does before) devote myself to completing the 'Executables' article in the 'Programmer's Guide' ('Making Binaries' section), from the above Jeff's posts and by synthesizing there only the information most useful to a programmer.
speedfixer
Posts: 606
Joined: Nov 28, 2012 1:27
Location: CA, USA moving to WA, USA
Contact:

Re: Executables and Compiling

Post by speedfixer »

I agree that a user (of BASIC) should not need to be concerned with where/what/how a compiled program starts after the language init code.

@coderjeff:
For these module constructors and module destructors, it really should be noted with a big WARNING on their use. Because this code is run outside of user code, it's quite likely that fbc's error checking and some runtime facilities won't work as expected.
I don't see it, but I hear an implicit 'something needs to be changed' thought floating around that statement. I'm sure I am wrong?
Perhaps before there is an effort made to make changes, maybe we could learn what problem, if any, is perceived to be with the current system of CTORs and DTORs? Or is this discussion just for the purposes of good documentation?

Any sophisticated user already knows how to use the non class/type 'main module' CTORs and DTORs, and probably has for some time. I'm wondering what "fixes" would benefit a beginner and not break other older mature code. Too loud a warning on the keyword pages and beginners might not test them. That would be a shame.

Certainly, a better explanation of existing destructors would be helpful to me, especially relating to the graphics. These are the most usual hangups for me, though the threading fixes corrected most of the problems.

Maybe a 'fix' to the END command would be more appropriate? Currently, it has no place in a 'finished' program and isn't even worth using for debugging.

I also think that maybe you are making the topic too complex and confusing. Any code that a user writes and is not attaching itself to FB internals to be run before his own modules is user code, however it is included or constructed. From the LANGUAGE viewpoint: if code is processed with '-pp' and is visible in that listing, it is user code and would be considered part of the user 'main' module, though maybe not the formal '_main_' in the final executable. (What is what?) The only possible exceptions would be CTORs and DTORs defined as such by the user.

Describing where they exist in the FB execution chain would be of benefit. Default CTORS and DTORS for classes and types and where they in the FB startup sequencing might help, also. It is ASSUMED they happen after ALL the FB init code/before the FB closing code. Is that the case?

I support the good, in-depth explanations being generated lately, if for no other reason than to clarify everyone's thoughts about how things actually work.

Whatever is written, the viewpoint of the terminology must be clear: user or language processor.

david
marcov
Posts: 3455
Joined: Jun 16, 2005 9:45
Location: Netherlands
Contact:

Re: Executables and Compiling

Post by marcov »

A picture perhaps? For the wiki docs I mean?

Image

Extensions are unix convention vs windows (ld does .lib too nowadays). In the case of shared libs, the name for Mac deviates (.dylib)
fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Executables and Compiling

Post by fxm »

fxm wrote:I will perhaps soon (if no one does before) devote myself to completing the 'Executables' article in the 'Programmer's Guide' ('Making Binaries' section), from the above Jeff's posts and by synthesizing there only the information most useful to a programmer.
ProPgExecutables → fxm [page complemented]
Post Reply