Program¶
The main program: the entry point of an executable.
Declaration¶
Syntax¶
Program(symbol_table symtab, identifier name, identifier* dependencies,
stmt* body, identifier? global_init, location start_name,
location end_name)
Arguments¶
Argument |
Description |
|---|---|
|
the symbol table of the program. It owns the local variables of the program and the |
|
the name of the program. |
|
the names of the modules and procedures the body of the program refers to. The backends use it to order code generation. |
|
the statements of the program, in order. |
|
the name of this program’s startup initializer in |
|
the source span of the name in |
|
the source span of the name in |
Return values¶
None.
Description¶
A translation unit that is linked into an executable must contain exactly one Program. It is always owned by the global symbol table.
A program has no arguments and no return value, so it needs no
function_signature: unlike Function it can never be called
from ASR.
Statements typed directly into the REPL, or written outside of any program
unit, first appear in TranslationUnit.items. The global_stmts ASR pass
wraps them in a Program so that the rest of the compiler only has to deal
with program units.
Startup initializers¶
A declaration initializer that no target can lay out as static data — a
derived-type array broadcast such as type(t) :: a(3) = t(7), a pointer
association such as integer, pointer :: p => tgt, the allocation of a saved
coarray — has to run as executable statements before any code can observe the
variable. The global_init ASR pass moves such an initializer out of the
declaration and into a startup initializer: an argument-less procedure in
the owner’s own symbol table, named by the owner’s global_init. A
Module and a Program each name at most one, which is why the
link lives on the owner rather than on the procedure.
A procedure or a block needs no initializer procedure of its own: nothing outside it can observe its variables, so there is nobody to call one. The same pass puts their initialization straight into a guarded block at the top of their own body, which is what the save attribute Fortran gives every initialized local means.
Where an initializer lives is not a choice between two equally good forms. A
declaration initializer a target can lay out as static data — an integer or
character constant, => null(), anything on a parameter — stays on the
declaration and is never moved. Only the ones it cannot are moved, and moving
one means taking it off the declaration: after the pass, a variable carries
its initializer in exactly one of the two places, never in both. Both places
being filled is the compiler contradicting itself about who initializes the
variable, and a backend that then leaves the setup to an initializer it does
not emit produces a program that reads uninitialized memory.
The two forms do both occur before the pass runs: what semantics produces has
every initializer on its declaration, which is the state global_init
consumes. The rule above is that pass’s postcondition, not something
asr_verify can check on its own, since it cannot know
which passes have already run.
The body of an initializer is one guarded block, so calling it again does nothing:
if (.not. already_run) then
already_run = .true.
<the initialization statements>
end if
Nothing calls one of them twice — see the ordering below — so under --fast
the guard is dropped and the body is the initialization statements
themselves. The guard at the top of a procedure or block body is kept in
every mode: that one is the save attribute of an initialized local, so it
decides what the program does rather than repeating a call that cannot
happen.
Ordering is therefore expressed in ASR, not left to a target: a program’s own
initializer calls every module initializer it can observe, once each and in
module dependency order, before the program’s first statement — which is the
single call the program body gains. That list is complete rather than a list
of the program’s direct dependencies: a module the program reaches only
through another module is in it too, so no module initializer needs to call
any other. Neither link order nor a constructor priority can
change the order Fortran requires, and an initializer defined in another
object file is called through an ExternalSymbol like any
other module procedure. A backend therefore needs no support at all for module
and program initializers — only for
TranslationUnit.global_init, which no
ASR statement calls.
Putting those calls in is a second pass, global_init_wire, because a pass
that runs later than global_init can create an initializer too — coarray
does, for saved coarrays — and an initializer nothing calls would never run.
Which unit’s initializer a saved coarray goes into is not decided by where it
is declared but by which program unit encloses that declaration, because
allocating a coarray is collective and so cannot wait until control first
reaches the procedure that declares it. The coarray pass walks outwards from
the declaration to the first enclosing Program or Module and
uses that unit’s initializer, so a saved coarray of a module procedure is
allocated by the module’s and one of an internal procedure by the program’s,
exactly as if it had been declared in that unit directly. Only a saved coarray
of an external procedure, which no program unit encloses, is left on the
TranslationUnit — which is why that one
case still needs the target’s startup hook.
Under separate compilation a saved coarray follows the same rule as an
ordinary declaration initializer. A module read from a .mod file was
compiled into an object file of its own, and that object file allocates the
module’s saved coarrays and binds each one to its companions. A translation
unit that only uses such a module therefore names that initializer without
defining a second one — defining one would clash at link time with the
definition already there — and leaves the coarrays themselves alone. Binding
them again here would point the module’s coarrays at companions this unit
allocated rather than at the storage every image agreed on, which is why the
two go together: skipping the definition without skipping the binding would
replace a good association with a private one.
Examples¶
An ASR text document that uses it:
(TranslationUnit
:symtab (SymbolTable
:id 0
:symbols {
"main" (Program
:symtab (SymbolTable
:id 1
:symbols {
"x" (Variable
:parent_symtab 1
:name "x"
:dependencies []
:intent :Local
:symbolic_value nil
:value nil
:storage :Default
:type (Integer
:kind 4
)
:type_declaration nil
:abi :Source
:access :Public
:presence :Required
:value_attr false
:target_attr false
:contiguous_attr false
:bindc_name nil
:is_volatile false
:is_protected false
:pass_attr :NotMethod
:self_argument nil
:codims []
:common_block nil
)
}
)
:name "main"
:dependencies []
:body [
(Assignment
:target (Var
:v (SymbolRef 1 "x")
)
:value (IntegerBinOp
:left (IntegerConstant
:n 2
:type (Integer
:kind 4
)
:intboz_type :Decimal
)
:op :Add
:right (IntegerConstant
:n 3
:type (Integer
:kind 4
)
:intboz_type :Decimal
)
:type (Integer
:kind 4
)
:value (IntegerConstant
:n 5
:type (Integer
:kind 4
)
:intboz_type :Decimal
)
)
:overloaded nil
:realloc_lhs false
:move_allocation false
)
]
:global_init nil
)
}
)
:items []
:global_init nil
)