blaise/compiler/src/main/pascal/runtime.libc.freebsd.pas
Graeme Geldenhuys 0014c157a0 feat(freebsd): drive static/freestanding link + per-target layout by --target (Step 6)
Complete the end-to-end FreeBSD cross-compile: `blaise --target freebsd-x86_64`
now emits a static, freestanding FreeBSD ET_EXEC on a Linux host with no
external tools.

Target selection through the linker:
  * TargetIsFreestanding(target) — FreeBSD (Strategy B: direct syscalls, no
    libc) is always a static ET_EXEC.  LinkViaInternalLinker sets dynamic mode
    to `not (AOpts.Static or freestanding)`, and EnsureRTLObjects selects the
    freestanding kernel-leaf unit list the same way — so a FreeBSD target links
    the syscall/libc-shim/thread leaves and no libc, regardless of --static.

Per-target platform layout (dependency injection off --target):
  * The concrete TPlatformLayout adapter (rtl.platform.layout.<os>) is selected
    and linked by the driver (BuildRTLUnitList), and the compiler injects a
    direct call to that unit's initialiser — PlatformLayoutInitSym(target) =
    rtl.platform.layout.<os>_init — into main, first, for the compile-time
    --target.  rtl.platform.posix no longer `uses` a concrete layout, so the
    shared POSIX code is OS-agnostic and a FreeBSD build carries no Linux layout.
  * Bootstrap fallback: each layout unit also defines a WEAK _BlaisePlatformInit
    trampoline that _SetArgs calls, so a binary built by an older codegen (which
    does not emit main's by-name call) still initialises GPlatformLayout.  Weak
    binding lets both layout units coexist in one link (e.g. the TestRunner,
    which imports the FreeBSD layout via cp.test.platformlayout.freebsd) without
    a duplicate-symbol error — the linker keeps the first-seen (host) copy.

FreeBSD libc-shaped leaf:
  * New runtime.libc.freebsd (sibling of runtime.libc.linux): getcwd/getenv/
    time/waitpid/execvp/sysconf on the FreeBSD syscall leaf.  time() reads
    CLOCK_REALTIME (no SYS_time on FreeBSD); sysconf(CPU count) uses
    sysctl(hw.ncpu) — new SYS___sysctl=202 leaf, MIB {CTL_HW=6, HW_NCPU=3}.

Tests:
  * TTargetToolkitTests.TestTargetIsFreestanding_{FreeBSD_True,Linux_False}.
  * TInternalLinkerE2ETests.TestCompile_FreeBSDTarget_EmitsStaticFreeBSDExe —
    a full compiler-CLI cross-compile asserting EI_OSABI=9, ET_EXEC, no
    PT_INTERP (the binary cannot run on the Linux host, so shape only).

Verified: all four fixpoints green; full suite passes under QBE-built and
native-built test runner (4029 tests); the previously latent TestRunner
GPlatformLayout crash is resolved.
2026-07-01 13:53:54 +01:00

189 lines
5.7 KiB
ObjectPascal

{
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 runtime.libc.freebsd;
// libc-shaped leaves that are MORE than a raw syscall - they need logic the
// kernel does not provide: env lookup, the libc-style return shapes, the
// hw.ncpu sysctl. Built on the raw syscalls in runtime.syscall.freebsd.
// Linked only in a static (libc-free) FreeBSD build
// (docs/freebsd-x86_64-backend-design.adoc). The FreeBSD sibling of
// runtime.libc.linux.
//
// These DEFINE the bare POSIX names (getcwd, getenv, time, waitpid, execvp,
// sysconf) that rtl.platform.posix / runtime.thread import via `external name`,
// so they resolve here instead of FreeBSD's libc.
//
// Deltas from the Linux sibling:
// * time() has no SYS_time on FreeBSD; it reads CLOCK_REALTIME via
// clock_gettime and returns tv_sec.
// * sysconf(CPU count) uses sysctl(hw.ncpu) — FreeBSD has no
// sched_getaffinity.
// getcwd / getenv / waitpid / execvp are structurally identical (the syscall
// leaf provides sys_getcwd / wait4 / execve / environ on both targets).
interface
uses
runtime.syscall.freebsd; { provides `environ`, captured by _start }
{ libc-shaped getcwd: returns Buf on success, nil on error (the raw syscall
returns 0 / -errno). }
function getcwd(Buf: PChar; Size: Int64): PChar;
{ getenv: linear scan of `environ` for "Name=" returns a pointer to the value
(just past '='), or nil if not present. }
function getenv(Name: PChar): PChar;
{ time(2): FreeBSD has no SYS_time — read CLOCK_REALTIME and return tv_sec. If
T is non-nil the same value is written there (libc's optional out-param). }
function time(T: Pointer): Int64;
{ waitpid via wait4 with a NULL rusage. }
function waitpid(Pid: Integer; Status: Pointer; Options: Integer): Integer;
{ execvp: execve the name with the current `environ` as envp. No $PATH search
yet (matches the Linux sibling — the RTL only execs explicit paths). }
function execvp(File_: PChar; Argv: Pointer): Integer;
{ sysconf for the few names the RTL queries - currently only the online CPU
count (_SC_NPROCESSORS_ONLN, passed as 84 by runtime.thread on every target),
via sysctl(hw.ncpu). }
function sysconf(Name: Integer): Int64;
implementation
type
PPointer = ^Pointer;
PInt64 = ^Int64;
const
{ The token runtime.thread passes for the CPU-count query. It is an internal
RTL contract value (runtime.thread hardcodes 84 on every target), NOT
FreeBSD's libc _SC_NPROCESSORS_ONLN (58) — we never reach FreeBSD's libc. }
_SC_NPROCESSORS_ONLN = 84;
CLOCK_REALTIME = 0;
{ sysctl MIB for hw.ncpu (FreeBSD sys/sysctl.h): CTL_HW=6, HW_NCPU=3. }
CTL_HW = 6;
HW_NCPU = 3;
{ --- small freestanding string helpers (no dependency on runtime.str) --- }
function CStrLen(S: PChar): Int64;
var I: Int64;
begin
I := 0;
while (S[I] and $FF) <> 0 do I := I + 1;
Result := I;
end;
{ Compare the "KEY=" prefix of an environ entry against Name (length NLen).
Returns True when Entry starts with Name immediately followed by '='. }
function EnvKeyMatches(Entry, Name: PChar; NLen: Int64): Boolean;
var I: Int64;
begin
I := 0;
while I < NLen do
begin
if (Entry[I] and $FF) <> (Name[I] and $FF) then Exit(False);
if (Entry[I] and $FF) = 0 then Exit(False);
I := I + 1;
end;
Result := (Entry[NLen] and $FF) = Ord('=');
end;
function getcwd(Buf: PChar; Size: Int64): PChar;
var Rc: Int64;
begin
Rc := sys_getcwd(Buf, Size);
if Rc < 0 then
Result := nil
else
Result := Buf;
end;
function getenv(Name: PChar): PChar;
var
Entry: PChar;
NLen, Off: Int64;
begin
Result := nil;
if environ = nil then Exit;
NLen := CStrLen(Name);
{ environ is a NULL-terminated array of PChar; read the I-th slot as a
Pointer at environ + I*8 (PPointer deref, the RTL idiom - no [] on a typed
pointer). }
Off := 0;
Entry := PChar(PPointer(Pointer(PChar(environ) + Off))^);
while Entry <> nil do
begin
if EnvKeyMatches(Entry, Name, NLen) then
begin
Result := PChar(Pointer(PChar(Entry) + NLen + 1)); { just past '=' }
Exit;
end;
Off := Off + 8;
Entry := PChar(PPointer(Pointer(PChar(environ) + Off))^);
end;
end;
function time(T: Pointer): Int64;
var
Ts: array[0..1] of Int64; { struct timespec: tv_sec (Int64), tv_nsec (long) }
Rc: Integer;
Out_: PInt64;
begin
Rc := clock_gettime(CLOCK_REALTIME, @Ts[0]);
if Rc <> 0 then
Result := -1
else
Result := Ts[0]; { tv_sec }
if (T <> nil) and (Result >= 0) then
begin
Out_ := PInt64(T);
Out_^ := Result; { libc's optional out-param: *T = tv_sec }
end;
end;
function waitpid(Pid: Integer; Status: Pointer; Options: Integer): Integer;
begin
Result := wait4(Pid, Status, Options, nil);
end;
function execvp(File_: PChar; Argv: Pointer): Integer;
begin
{ No PATH search yet: execve the name directly (matches runtime.libc.linux —
the RTL only execs explicit paths, e.g. system() builds "/bin/sh"). }
Result := execve(File_, Argv, environ);
end;
function sysconf(Name: Integer): Int64;
var
Mib: array[0..1] of Integer; { the two-element MIB: CTL_HW, HW_NCPU }
NCpu: Integer;
OldLen: Int64; { size_t }
Rc: Integer;
begin
if Name = _SC_NPROCESSORS_ONLN then
begin
Mib[0] := CTL_HW;
Mib[1] := HW_NCPU;
NCpu := 0;
OldLen := SizeOf(NCpu); { 4 }
Rc := sysctl(@Mib[0], 2, @NCpu, @OldLen, nil, 0);
if (Rc <> 0) or (NCpu < 1) then
Result := 1 { fall back to single CPU on error }
else
Result := NCpu;
end
else
Result := -1;
end;
end.