User:Martok/Portability Issues

From Free Pascal wiki
Jump to navigationJump to search

DRAFT VERSION

Free Pascal, especially in its TP and Delphi modes, is generally compatible with code written for the "Borland compilers" (Borland pascal, Turbo Pascal, and Delphi, including recent versions by CodeGear and Embarcadero). Where there are syntactic differences, they are usually documented. However, there are some differences where the same source code will produce different behaviour. Sometimes they lie in undefined behaviour that is interpreted differently, sometimes they are required because Free Pascal targets non-Intel platforms, sometimes they are because Free Pascal tends to default to ISO Pascal compatibility, sometimes they just are. This page attempts to collect these non-obvious differences.

Also see: Porting Turbo Pascal to Free Pascal


Range Checks

FPC is defined with mandatory Range Checks. They can be disabled for performance, but the programm will not be strictly defined then.

Borland FPC
Range Checking is purely optional. In fact, TP is defined as having no runtime range checks at all, except for Succ/Pred (and Inc/Dec by extension), which explicitly react to $R.

Code may do things the programmer has forgotten to take care of, but if an operation on ordinals is accepted by the compiler at all, its result fully defined.

Range Checks are mandatory. Many operations on ordinals are undefined in the (default) state {$R-}. Some (such as typecasts) do not generate range checks, so they are technically always undefined.

This is consistent with ISO Pascal, where they are treated as "dynamic violations".

Further Reading

  • TP 7 Language Guide, Chapter 4
  • TP 7 Language Guide, Chapter 21

Subrange Types

Subrange Types are interpreted more strictly, following the rules-as-written of ISO 7185. This causes several follow-up differences, such as in dead code elimination or range checking.

type Percentile = 0..99;
var I: Percentile;
{ Ex. 1}
 I := 100;
{ Ex. 2}
 I := 99;
 Inc(I);
Borland FPC
Ex.1 does not compile. Ex.2 compiles and (in state {$R-}) causes the value 100 to be stored in I and program execution continues or (in state {$R+}) causes a Range Check Error. If no RCE was raised, the program continues safely.

Since TP, if Range Checks are disabled and a value to be assigned to a Subrange variable is outside that Subrange Type's range, the operation is instead carried out in the Host Type, all the way up to the corresponding basic Storage Type. For example in Delphi, Percentile above is a Subrange of the Host Type ShortInt so it could contain any value of a signed 8bit type. Only operations that generate Range Check code (or constant assignments) will respect the subrange-ness, all others are carried out as if it was ShortInt.

Ex.1 compiles, but raises a warning. Ex.2 compiles and (in state {$R+}) causes a Range Check Error at runtime. In either example, the program is now in an undefined state.
{ Continued from above }
 if I > 99 then
   Writeln('This is not a Percentage');
Borland FPC
Does exactly what is written.

Note: If the expression was I > 127, a warning 1021 "Comparision is always false" is raised, but the statement is still not removed. It is considered the programmer's responsibility to clean up this probably unintended code.

Since in the strict type system, I can never be larger than 99, the statement is considered always-false and removed as dead code. It is therefore impossible to generate the equivalent of Range-Check code in user code: even modifying the expression to if Integer(I) > 99 then is not formally guaranteed to work, since as soon as I left the declared range, operations on it became undefined.
type MyEnum = (zero = 0, one = 1, five = 5);
var E: MyEnum;
{ Ex.1 }
  E:= one;
  inc(E);
{ Ex.2 }
  E:= MyEnum(3);
{ Ex.3 }
  E:= five;
  inc(E);
Borland FPC
The Storage Type is decided by the setting of the PackEnum directive. The Host Type is the Subrange 0..5. The explanations above apply, so all examples compile, with Ex.3 raising a Range Check Error if they are enabled. In any case, the program is in a well-defined state.

Note: Ex.1 and 2 do not raise a Range Check. The ordinals 2 to 4 do not have assigned names, but are members of the Host Type.

The Storage Type is decided by the setting of the PackEnum directive. The Host Type is the Subrange 0..5. All examples compile, but put the program in an undefined state.

Note: the compiler assumes that only declared values (here: the specific ordinals 0,1,5) are contained in the variable. Any other value, including those in the "gap", may lead to undefined behaviour.

Further Reading

Floating Point Literals & Constants

Floating-Point literals in Borland compilers are always of the largest available real type (Real or Extended). FPC uses the smallest exactly possible type.

const
  five = 5.0;
  nine = 9.0;
  fiveninths = five / nine;

Writeln(fiveninths:30:20);
Borland FPC
All constants are of type Extended. Evaluated at extended precision, 0.55555555555555556000 is printed. five and nine are representable exactly as Single, so they are of type Single. Because it involves only type Single, the type of fiveninths is also single. 0.55555558200000000000 is printed.

Further Reading

Managed variable lifetimes

FPC often uses more temporary variables during evaluation of complex expressions. If these are of managed types, this affects object lifetime.

Assume a managed variable is freed no sooner than at the end of the enclosing block scope, notwithstanding any explicit nil assignments or similar clearing statements. This is actually a good assumption in general, as there are corner cases where Delphi also does what FPC always does.

TODO: concrete examples.

Managed Variable Intialization

For DFA purposes, FPC treats managed variables as uninitialized, even in cases where intialization is guaranteed.

program Project2;
var
  a: TMyArray;
begin
  SetLength(a, 12);
end.
Borland FPC
Global variables are always initialized to nil/0. Therefore, a is considered initialized before the SetLength call. Global variables are always initialized to nil/0. Nevertheless, a warning 5092 "uninitialized managed variable" is printed.
  function fn: TMyArray;
  begin
  end;
begin
  a:= fn;
end.
Borland FPC
The Result Variable is unintialized. Random memory at [ebp+4]) is returned. No warning is emitted. The Result Variable is unintialized. The result register eax is left untouched. A warning 5033 "uninitialized result variable".
procedure Proc;
var
  a: TMyArray;
begin
  i:= Length(a);
end;
Borland FPC
Local managed variables are always fully initialized, as if passed to Initialize(). Nevertheless, a warning "uninitialized variable" (note: the generic one, not specific to managed vars) is printed. Local managed variables are always fully initialized, as if passed to Initialize(). Nevertheless, a warning 5092 "uninitialized managed variable" is printed.
procedure Proc;
var
  a: TMyArray;
begin
  SetLength(a, 12);
end;
Borland FPC
As above, but no message is printed - SetLength is a compilerproc with special semantics (maps to DynArraySetLength), and does not raise a warning. As above. SetLength is a a compilerproc with no special semantics (maps to fpc_dynarray_setlength), and therefore prints a warning 5092 "uninitialized managed variable" is printed.