feat(rtl): extract TPlatformLayout port for per-target struct/constants

Move the OS integer constants (O_*/S_*/SEEK_*/CLOCK_REALTIME/WNOHANG) and the
struct stat layout out of rtl.platform.posix and behind a new TPlatformLayout
port. struct stat is now an opaque sized buffer whose Size/Mtime/Mode fields are
read at target offsets via StatSize/StatMtime/StatMode, rather than a record
that bakes in one layout — FreeBSD's stat differs in both offsets and size.

The Linux adapter (rtl.platform.layout.linux, TPlatformLayoutLinuxX86_64) supplies
today's values verbatim; struct stat offsets verified against sys/stat.h
(st_mode=24, st_size=48, st_mtim=88, sizeof=144). struct tm is left shared — its
used fields are identical on Linux/FreeBSD amd64.

The POSIX unit's one per-target wire is its 'uses rtl.platform.layout.linux';
the FreeBSD RTL archive will compose its own layout. No conditional compilation.

Step 0b of docs/native-target-architecture.adoc. Behaviour-preserving:
FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK, WARMCACHE_FIXPOINT_OK;
full suite 3761 tests green; file I/O (FileExists/DirectoryExists/FileAge/
ReadFile) exercised end-to-end through the new accessors.
This commit is contained in:
Graeme Geldenhuys 2026-06-25 00:19:58 +01:00
parent e723e80063
commit de27984647
4 changed files with 211 additions and 57 deletions

View file

@ -55,6 +55,7 @@ PAS_OBJS = $(OBJ_DIR)/blaise_mem.o \
$(OBJ_DIR)/blaise_float.o \
$(OBJ_DIR)/blaise_thread.o \
$(OBJ_DIR)/blaise_exc.o \
$(OBJ_DIR)/rtl_platform_layout_linux.o \
$(OBJ_DIR)/rtl_platform_posix.o
OBJS = $(ASM_OBJS) $(PAS_OBJS)
@ -104,7 +105,13 @@ $(OBJ_DIR)/blaise_exc.o: $(PAS_SRC_DIR)/blaise_exc.pas
@mkdir -p $(OBJ_DIR)
$(BLAISE) $(BLAISE_FLAGS) --source $< --unit-path $(PAS_SRC_DIR) --output $@
$(OBJ_DIR)/rtl_platform_layout_linux.o: $(PAS_SRC_DIR)/rtl.platform.layout.linux.pas \
$(PAS_SRC_DIR)/rtl.platform.pas
@mkdir -p $(OBJ_DIR)
$(BLAISE) $(BLAISE_FLAGS) --source $< --unit-path $(PAS_SRC_DIR) --output $@
$(OBJ_DIR)/rtl_platform_posix.o: $(PAS_SRC_DIR)/rtl.platform.posix.pas \
$(PAS_SRC_DIR)/rtl.platform.layout.linux.pas \
$(PAS_SRC_DIR)/rtl.platform.pas
@mkdir -p $(OBJ_DIR)
$(BLAISE) $(BLAISE_FLAGS) --source $< --unit-path $(PAS_SRC_DIR) --output $@

View file

@ -0,0 +1,114 @@
{
Blaise - An Object Pascal Compiler
Copyright (c) 2026 Graeme Geldenhuys
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
Licensed under the Apache License v2.0 with Runtime Library Exception.
See LICENSE file in the project root for full license terms.
}
unit rtl.platform.layout.linux;
// Linux x86_64 struct layouts and OS constants — the concrete TPlatformLayout
// adapter for Linux (docs/native-target-architecture.adoc, Step 0b).
//
// These are the values that previously lived as `const` declarations and the
// TStatBuf record inside rtl.platform.posix.pas. Moving them behind the
// TPlatformLayout port keeps the POSIX method bodies (which are shared with
// FreeBSD) free of any Linux-specific layout assumption; the FreeBSD adapter
// supplies its own offsets and flag values without conditional compilation.
//
// struct stat offsets below match the Linux x86_64 `struct stat`
// (sys/stat.h): st_mode at byte 24, st_size at byte 48, st_mtime at byte 88.
// The buffer is sized at 144 bytes (sizeof(struct stat) on Linux x86_64).
interface
uses
rtl.platform;
type
TPlatformLayoutLinuxX86_64 = class(TPlatformLayout)
public
function O_RDONLY: Integer; override;
function O_WRONLY: Integer; override;
function O_RDWR: Integer; override;
function O_CREAT: Integer; override;
function O_TRUNC: Integer; override;
function O_APPEND: Integer; override;
function S_IFMT: Integer; override;
function S_IFDIR: Integer; override;
function SEEK_SET: Integer; override;
function SEEK_CUR: Integer; override;
function SEEK_END: Integer; override;
function CLOCK_REALTIME: Integer; override;
function WNOHANG: Integer; override;
function StatBufSize: Integer; override;
function StatSize(Buf: Pointer): Int64; override;
function StatMtime(Buf: Pointer): Int64; override;
function StatMode(Buf: Pointer): Integer; override;
end;
implementation
const
{ Linux x86_64 struct stat field offsets (bytes). }
STAT_OFF_MODE = 24; { st_mode (Integer) }
STAT_OFF_SIZE = 48; { st_size (Int64) }
STAT_OFF_MTIME = 88; { st_mtim.tv_sec (Int64) }
STAT_SIZE = 144; { sizeof(struct stat) }
function TPlatformLayoutLinuxX86_64.O_RDONLY: Integer; begin Result := 0; end;
function TPlatformLayoutLinuxX86_64.O_WRONLY: Integer; begin Result := 1; end;
function TPlatformLayoutLinuxX86_64.O_RDWR: Integer; begin Result := 2; end;
function TPlatformLayoutLinuxX86_64.O_CREAT: Integer; begin Result := $40; end;
function TPlatformLayoutLinuxX86_64.O_TRUNC: Integer; begin Result := $200; end;
function TPlatformLayoutLinuxX86_64.O_APPEND: Integer; begin Result := $400; end;
function TPlatformLayoutLinuxX86_64.S_IFMT: Integer; begin Result := $F000; end;
function TPlatformLayoutLinuxX86_64.S_IFDIR: Integer; begin Result := $4000; end;
function TPlatformLayoutLinuxX86_64.SEEK_SET: Integer; begin Result := 0; end;
function TPlatformLayoutLinuxX86_64.SEEK_CUR: Integer; begin Result := 1; end;
function TPlatformLayoutLinuxX86_64.SEEK_END: Integer; begin Result := 2; end;
function TPlatformLayoutLinuxX86_64.CLOCK_REALTIME: Integer; begin Result := 0; end;
function TPlatformLayoutLinuxX86_64.WNOHANG: Integer; begin Result := 1; end;
function TPlatformLayoutLinuxX86_64.StatBufSize: Integer;
begin
Result := STAT_SIZE;
end;
function TPlatformLayoutLinuxX86_64.StatSize(Buf: Pointer): Int64;
var
P: ^Int64;
begin
P := Pointer(PChar(Buf) + STAT_OFF_SIZE);
Result := P^;
end;
function TPlatformLayoutLinuxX86_64.StatMtime(Buf: Pointer): Int64;
var
P: ^Int64;
begin
P := Pointer(PChar(Buf) + STAT_OFF_MTIME);
Result := P^;
end;
function TPlatformLayoutLinuxX86_64.StatMode(Buf: Pointer): Integer;
var
P: ^Integer;
begin
P := Pointer(PChar(Buf) + STAT_OFF_MODE);
Result := P^;
end;
initialization
if GPlatformLayout = nil then
GPlatformLayout := TPlatformLayoutLinuxX86_64.Create();
end.

View file

@ -96,8 +96,61 @@ type
procedure ProcessFree(Proc: Pointer); virtual; abstract;
end;
{ Per-target struct layouts and OS constant values.
(docs/native-target-architecture.adoc)
Two things diverge across POSIX targets and both live here so the platform
methods stay layout-agnostic:
* OS integer constants — the open()/lseek() flag bits, clock id, and
waitpid option whose numeric values differ between Linux and FreeBSD.
* struct stat — its field OFFSETS differ (FreeBSD's stat is a different
shape and size than Linux's). Rather than a shared TStatBuf record that
bakes in one layout, the buffer is an opaque byte block sized by
StatBufSize, and the three fields the RTL reads are pulled out by
StatSize / StatMtime / StatMode at the target's offsets.
struct tm is intentionally NOT abstracted: the fields the RTL uses
(tm_year/mon/mday/hour/min/sec/gmtoff) share an identical layout on Linux
and FreeBSD amd64, so TRtlPlatformPosix keeps a shared TTm record.
The concrete layout is selected at startup alongside GRtlPlatform; nothing
here uses conditional compilation. }
TPlatformLayout = class
public
{ ---- open() / file flags ---- }
function O_RDONLY: Integer; virtual; abstract;
function O_WRONLY: Integer; virtual; abstract;
function O_RDWR: Integer; virtual; abstract;
function O_CREAT: Integer; virtual; abstract;
function O_TRUNC: Integer; virtual; abstract;
function O_APPEND: Integer; virtual; abstract;
{ ---- stat mode bits ---- }
function S_IFMT: Integer; virtual; abstract;
function S_IFDIR: Integer; virtual; abstract;
{ ---- lseek whence ---- }
function SEEK_SET: Integer; virtual; abstract;
function SEEK_CUR: Integer; virtual; abstract;
function SEEK_END: Integer; virtual; abstract;
{ ---- clock_gettime / waitpid ---- }
function CLOCK_REALTIME: Integer; virtual; abstract;
function WNOHANG: Integer; virtual; abstract;
{ ---- struct stat: opaque buffer sized for the target, fields pulled at
the target's offsets ---- }
function StatBufSize: Integer; virtual; abstract;
function StatSize(Buf: Pointer): Int64; virtual; abstract;
function StatMtime(Buf: Pointer): Int64; virtual; abstract;
function StatMode(Buf: Pointer): Integer; virtual; abstract;
end;
var
GRtlPlatform: TRtlPlatform;
GPlatformLayout: TPlatformLayout;
implementation

View file

@ -28,7 +28,12 @@ unit rtl.platform.posix;
interface
uses
rtl.platform;
rtl.platform,
{ The concrete TPlatformLayout for this archive's target. This is the ONE
per-target wire in the otherwise OS-agnostic POSIX unit: the Linux RTL
archive composes the Linux layout, the FreeBSD archive its own. Its
initialization assigns GPlatformLayout. }
rtl.platform.layout.linux;
type
TRtlPlatformPosix = class(TRtlPlatform)
@ -109,26 +114,12 @@ type
{ ------------------------------------------------------------------ }
type
TStatBuf = record
Dev: Int64;
Ino: Int64;
Nlink: Int64;
Mode: Integer;
Uid: Integer;
Gid: Integer;
Pad0: Integer;
Rdev: Int64;
Size: Int64;
Blksize: Int64;
Blocks: Int64;
Atime: Int64;
AtimeNs: Int64;
Mtime: Int64;
MtimeNs: Int64;
Ctime: Int64;
CtimeNs: Int64;
Unused: array[0..2] of Int64;
end;
{ Opaque struct stat buffer. The kernel fills it; the field offsets and the
true size are target-specific (Linux 144 bytes, FreeBSD larger), so the RTL
never names the fields directly — it sizes the buffer generously and reads
Size/Mtime/Mode through GPlatformLayout.StatSize/StatMtime/StatMode at the
target's offsets. 256 bytes covers every supported target's struct stat. }
TStatBuf = array[0..255] of Byte;
PStatBuf = ^TStatBuf;
TTimeSpec = record
@ -173,8 +164,8 @@ function libc_read(Fd: Integer; Buf: Pointer; Count: Int64): Int64; exte
function libc_write(Fd: Integer; Buf: Pointer; Count: Int64): Int64; external name 'write';
function libc_lseek(Fd: Integer; Offset: Int64; Whence: Integer): Int64; external name 'lseek';
function libc_close(Fd: Integer): Integer; external name 'close';
function libc_fstat(Fd: Integer; Buf: PStatBuf): Integer; external name 'fstat';
function libc_stat(Path: PChar; Buf: PStatBuf): Integer; external name 'stat';
function libc_fstat(Fd: Integer; Buf: Pointer): Integer; external name 'fstat';
function libc_stat(Path: PChar; Buf: Pointer): Integer; external name 'stat';
function libc_mkdir(Path: PChar; Mode: Integer): Integer; external name 'mkdir';
function libc_rmdir(Path: PChar): Integer; external name 'rmdir';
function libc_unlink(Path: PChar): Integer; external name 'unlink';
@ -288,24 +279,11 @@ implementation
const
BLAISE_STR_HDR = 12;
O_RDONLY = 0;
O_WRONLY = 1;
O_RDWR = 2;
O_CREAT = $40;
O_TRUNC = $200;
O_APPEND = $400;
NS_PER_SEC = 1000000000;
S_IFMT = $F000;
S_IFDIR = $4000;
SEEK_SET = 0;
SEEK_CUR = 1;
SEEK_END = 2;
NS_PER_SEC = 1000000000;
CLOCK_REALTIME = 0;
WNOHANG = 1;
{ O_* / S_* / SEEK_* / CLOCK_REALTIME / WNOHANG and the struct stat layout
live in GPlatformLayout (rtl.platform.layout.<os>) — their values/offsets
differ across POSIX targets. See docs/native-target-architecture.adoc. }
{ ------------------------------------------------------------------ }
{ Shared string helpers }
@ -435,7 +413,7 @@ function TRtlPlatformPosix.FileExists(const APath: string): Boolean;
var
Fd: Integer;
begin
Fd := libc_open2(StrData(Pointer(APath)), O_RDONLY);
Fd := libc_open2(StrData(Pointer(APath)), GPlatformLayout.O_RDONLY());
if Fd < 0 then begin Result := False; Exit end;
libc_close(Fd);
Result := True;
@ -460,10 +438,10 @@ var
Got: Int64;
LPtr: ^Integer;
begin
Fd := libc_open2(StrData(Pointer(APath)), O_RDONLY);
Fd := libc_open2(StrData(Pointer(APath)), GPlatformLayout.O_RDONLY());
if Fd < 0 then begin Result := string(PChar(StrAlloc(0))); Exit end;
if libc_fstat(Fd, @St) < 0 then begin libc_close(Fd); Result := string(PChar(StrAlloc(0))); Exit end;
Sz := St.Size;
Sz := GPlatformLayout.StatSize(@St);
if Sz < 0 then begin libc_close(Fd); Result := string(PChar(StrAlloc(0))); Exit end;
R := StrAlloc(Integer(Sz));
if R = nil then begin libc_close(Fd); Result := ''; Exit end;
@ -480,7 +458,7 @@ var
Fd: Integer;
Len: Integer;
begin
Fd := libc_open(StrData(Pointer(APath)), O_WRONLY or O_CREAT or O_TRUNC, 420);
Fd := libc_open(StrData(Pointer(APath)), GPlatformLayout.O_WRONLY() or GPlatformLayout.O_CREAT() or GPlatformLayout.O_TRUNC(), 420);
if Fd < 0 then Exit;
Len := StrLen(Pointer(AContent));
if Len > 0 then
@ -493,7 +471,7 @@ var
Fd: Integer;
Len: Integer;
begin
Fd := libc_open(StrData(Pointer(APath)), O_WRONLY or O_CREAT or O_APPEND, 420);
Fd := libc_open(StrData(Pointer(APath)), GPlatformLayout.O_WRONLY() or GPlatformLayout.O_CREAT() or GPlatformLayout.O_APPEND(), 420);
if Fd < 0 then Exit;
Len := StrLen(Pointer(AContent));
if Len > 0 then
@ -506,7 +484,7 @@ var
St: TStatBuf;
begin
if libc_stat(StrData(Pointer(APath)), @St) <> 0 then begin Result := -1; Exit end;
Result := St.Mtime;
Result := GPlatformLayout.StatMtime(@St);
end;
{ ================================================================== }
@ -518,7 +496,7 @@ var
St: TStatBuf;
begin
if libc_stat(StrData(Pointer(APath)), @St) <> 0 then begin Result := False; Exit end;
Result := (St.Mode and S_IFMT) = S_IFDIR;
Result := (GPlatformLayout.StatMode(@St) and GPlatformLayout.S_IFMT()) = GPlatformLayout.S_IFDIR();
end;
function TRtlPlatformPosix.ForceDirectories(const APath: string): Boolean;
@ -550,7 +528,7 @@ begin
begin
Result := False; Exit;
end;
end else if (St.Mode and S_IFMT) <> S_IFDIR then
end else if (GPlatformLayout.StatMode(@St) and GPlatformLayout.S_IFMT()) <> GPlatformLayout.S_IFDIR() then
begin
Result := False; Exit;
end;
@ -836,17 +814,17 @@ end;
function TRtlPlatformPosix.FdOpenRead(Path: Pointer): Integer;
begin
Result := libc_open2(StrData(Path), O_RDONLY);
Result := libc_open2(StrData(Path), GPlatformLayout.O_RDONLY());
end;
function TRtlPlatformPosix.FdOpenWrite(Path: Pointer): Integer;
begin
Result := libc_open(StrData(Path), O_WRONLY or O_CREAT or O_TRUNC, 420);
Result := libc_open(StrData(Path), GPlatformLayout.O_WRONLY() or GPlatformLayout.O_CREAT() or GPlatformLayout.O_TRUNC(), 420);
end;
function TRtlPlatformPosix.FdOpenAppend(Path: Pointer): Integer;
begin
Result := libc_open(StrData(Path), O_WRONLY or O_CREAT or O_APPEND, 420);
Result := libc_open(StrData(Path), GPlatformLayout.O_WRONLY() or GPlatformLayout.O_CREAT() or GPlatformLayout.O_APPEND(), 420);
end;
function TRtlPlatformPosix.FdRead(Fd: Integer; Buf: Pointer; Count: Integer): Integer;
@ -866,10 +844,10 @@ var
Whence: Integer;
begin
case Origin of
1: Whence := SEEK_CUR;
2: Whence := SEEK_END;
1: Whence := GPlatformLayout.SEEK_CUR();
2: Whence := GPlatformLayout.SEEK_END();
else
Whence := SEEK_SET;
Whence := GPlatformLayout.SEEK_SET();
end;
Result := libc_lseek(Fd, Offset, Whence);
end;
@ -879,7 +857,7 @@ var
St: TStatBuf;
begin
if libc_fstat(Fd, @St) <> 0 then begin Result := -1; Exit end;
Result := St.Size;
Result := GPlatformLayout.StatSize(@St);
end;
procedure TRtlPlatformPosix.FdClose(Fd: Integer);
@ -895,7 +873,7 @@ function TRtlPlatformPosix.TimeNow: Int64;
var
Ts: TTimeSpec;
begin
libc_clock_gettime(CLOCK_REALTIME, @Ts);
libc_clock_gettime(GPlatformLayout.CLOCK_REALTIME(), @Ts);
Result := Ts.Sec * NS_PER_SEC + Ts.NSec;
end;
@ -1082,7 +1060,7 @@ begin
P := Proc;
if (P^.Waited <> 0) or (P^.Pid = 0) then begin Result := 0; Exit end;
Status := 0;
R := libc_waitpid(P^.Pid, @Status, WNOHANG);
R := libc_waitpid(P^.Pid, @Status, GPlatformLayout.WNOHANG());
if R = P^.Pid then
begin
if (Status and $7F) = 0 then
@ -1171,6 +1149,8 @@ procedure _SetArgs(Argc: Integer; Argv: Pointer);
begin
GArgC := Argc;
GArgV := TPCharArray(Argv);
if GPlatformLayout = nil then
GPlatformLayout := TPlatformLayoutLinuxX86_64.Create();
if GRtlPlatform = nil then
GRtlPlatform := TRtlPlatformPosix.Create();
end;