feat: hosted libstdc++ for x86_64-montauk - full C++ on MontaukOS

The cross toolchain now builds real hosted C++: std::string, vector,
unique_ptr, iostreams and exceptions all work in a plain
x86_64-montauk-g++ invocation with no special flags. This is the
library that native cc1plus will link against for the GCC self-host.

crt1 now runs .preinit_array/.init_array before main and hooks
.fini_array through atexit (main returns via exit(), so destructors
run on both exit paths). Global constructors and libgcc's eh_frame
registration both ride this. GCC is reconfigured with
--enable-initfini-array (modern arrays instead of .ctors),
--with-newlib (satisfies libstdc++'s crossconfig for unknown hosts),
--disable-wchar_t and --disable-libstdcxx-pch; the montauk gcc patch
grows a hunk keeping os/generic ctype under --with-newlib (the
Montauk libc is not newlib).

libc additions harvested by the libstdc++ build: mbtowc/wctomb,
strxfrm, strtok/strtok_r, a real vfscanf/fscanf/scanf (character-
driven with widths and l/ll; also unblocks gprof later), fgetpos/
fsetpos/setbuf/fpos_t, modf plus the C99 float math variants
(acosf..tanhf, hypot/hypotf as wrappers), FP_* classification macros,
the full POSIX errno vocabulary, complete DT_* dirent types, and
mbstate_t.

The SDK ships the real libstdc++.a (was an empty stand-in), the C++
headers at 0:/sdk/include/c++, and cxx-test.elf (global ctor, string,
vector+sort, unique_ptr, throw/catch; exits 0 on 5/5). Ramdisk sits
at 1571 files, within the 2048 table.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-07-16 23:06:50 +02:00
co-authored by Claude Fable 5
parent eadc79708f
commit 03d1a1bc2f
19 changed files with 480 additions and 8 deletions
+3 -1
View File
@@ -401,8 +401,10 @@ ifneq ($(wildcard $(NATIVE_BIN)/as),)
cp lib/libc/liblibc.a $(BINDIR)/sdk/lib/libc.a
cp lib/libc/crt1.o lib/libc/crti.o lib/libc/crtn.o $(BINDIR)/sdk/lib/
ar rcs $(BINDIR)/sdk/lib/libm.a
ar rcs $(BINDIR)/sdk/lib/libstdc++.a
cp ../toolchain/local/x86_64-montauk/lib/libstdc++.a $(BINDIR)/sdk/lib/libstdc++.a
cp -r ../toolchain/local/x86_64-montauk/include/c++ $(BINDIR)/sdk/include/c++
../toolchain/local/bin/x86_64-montauk-gcc -O2 ../toolchain/files/tls-test.c -o $(BINDIR)/sdk/bin/tls-test.elf
../toolchain/local/bin/x86_64-montauk-g++ -O2 ../toolchain/files/cxx-test.cpp -o $(BINDIR)/sdk/bin/cxx-test.elf
else
@echo "devkit: toolchain/native/ not built; skipping native dev tools"
endif
+5
View File
@@ -9,8 +9,13 @@ extern "C" {
#define NAME_MAX 255
#define DT_UNKNOWN 0
#define DT_FIFO 1
#define DT_CHR 2
#define DT_DIR 4
#define DT_BLK 6
#define DT_REG 8
#define DT_LNK 10
#define DT_SOCK 12
struct dirent {
unsigned char d_type;
+39
View File
@@ -49,8 +49,47 @@ extern int errno;
#define ENOTEMPTY 39
#define ELOOP 40
#define EWOULDBLOCK EAGAIN
#define ETXTBSY 26
#define ENOMSG 42
#define EIDRM 43
#define ENOSTR 60
#define ENODATA 61
#define ETIME 62
#define ENOSR 63
#define ENOLINK 67
#define EPROTO 71
#define EMULTIHOP 72
#define EBADMSG 74
#define EOVERFLOW 75
#define EILSEQ 84
#define ENOTSOCK 88
#define EDESTADDRREQ 89
#define EMSGSIZE 90
#define EPROTOTYPE 91
#define ENOPROTOOPT 92
#define EPROTONOSUPPORT 93
#define EOPNOTSUPP 95 /* == ENOTSUP, Linux numbering */
#define EAFNOSUPPORT 97
#define EADDRINUSE 98
#define EADDRNOTAVAIL 99
#define ENETDOWN 100
#define ENETUNREACH 101
#define ENETRESET 102
#define ECONNABORTED 103
#define ECONNRESET 104
#define ENOBUFS 105
#define EISCONN 106
#define ENOTCONN 107
#define ETIMEDOUT 110
#define ECONNREFUSED 111
#define EHOSTUNREACH 113
#define EALREADY 114
#define EINPROGRESS 115
#define ESTALE 116
#define EDQUOT 122
#define ECANCELED 125
#define EOWNERDEAD 130
#define ENOTRECOVERABLE 131
#define ENOTSUP 95
#ifdef __cplusplus
+39
View File
@@ -11,8 +11,47 @@ extern "C" {
#define INFINITY __builtin_inff()
#define NAN __builtin_nanf("")
/* C99 floating-point classification. */
#define FP_NAN 0
#define FP_INFINITE 1
#define FP_ZERO 2
#define FP_SUBNORMAL 3
#define FP_NORMAL 4
#define fpclassify(x) \
__builtin_fpclassify(FP_NAN, FP_INFINITE, FP_NORMAL, FP_SUBNORMAL, \
FP_ZERO, x)
#define isnan(x) __builtin_isnan(x)
#define isinf(x) __builtin_isinf(x)
#define isfinite(x) __builtin_isfinite(x)
#define isnormal(x) __builtin_isnormal(x)
#define signbit(x) __builtin_signbit(x)
double fabs(double x);
double frexp(double x, int *exp);
/* C89 modf and the C99 float variants assumed by hosted libstdc++
(--with-newlib crossconfig). The float forms wrap the double
implementations. */
double modf(double x, double *iptr);
float modff(float x, float *iptr);
double hypot(double x, double y);
float hypotf(float x, float y);
float acosf(float x);
float asinf(float x);
float atanf(float x);
float atan2f(float y, float x);
float coshf(float x);
float expf(float x);
float fmodf(float x, float y);
float frexpf(float x, int *exp);
float ldexpf(float x, int exp);
float logf(float x);
float log10f(float x);
float powf(float x, float y);
float sinhf(float x);
float tanf(float x);
float tanhf(float x);
double ldexp(double x, int n);
long double ldexpl(long double x, int n);
double floor(double x);
+9
View File
@@ -39,6 +39,15 @@ extern FILE *stderr;
int putc(int c, FILE *stream);
void rewind(FILE *stream);
int getchar(void);
typedef long fpos_t;
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
void setbuf(FILE *stream, char *buf);
int fscanf(FILE *stream, const char *fmt, ...);
int scanf(const char *fmt, ...);
int vfscanf(FILE *stream, const char *fmt, va_list ap);
int fileno(FILE *stream);
FILE *fdopen(int fd, const char *mode);
+2
View File
@@ -63,6 +63,8 @@ char *mktemp(char *template_);
char *realpath(const char *path, char *resolved);
size_t mbstowcs(wchar_t *dst, const char *src, size_t n);
int mblen(const char *s, size_t n);
int mbtowc(wchar_t *pwc, const char *s, size_t n);
int wctomb(char *s, wchar_t wc);
double strtod(const char *nptr, char **endptr);
float strtof(const char *nptr, char **endptr);
long double strtold(const char *nptr, char **endptr);
+3
View File
@@ -31,6 +31,9 @@ char *strpbrk(const char *s, const char *accept);
int strcasecmp(const char *s1, const char *s2);
int strncasecmp(const char *s1, const char *s2, size_t n);
int strcoll(const char *s1, const char *s2);
size_t strxfrm(char *dest, const char *src, size_t n);
char *strtok(char *str, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr);
char *strstr(const char *haystack, const char *needle);
const char *strerror(int errnum);
+5
View File
@@ -11,6 +11,11 @@ extern "C" {
typedef unsigned int wint_t;
/* Opaque shift state for the (byte-oriented) C locale. */
typedef struct {
unsigned long __opaque;
} mbstate_t;
#define WEOF ((wint_t)-1)
/* Byte-oriented C locale only: one byte, one character. */
+40 -1
View File
@@ -36,6 +36,38 @@ static inline long _sys2(long nr, long a1, long a2) {
extern int main(int argc, char** argv);
/* Global constructor/destructor tables. The linker provides these
boundary symbols whenever .preinit_array/.init_array/.fini_array
sections exist; the weak references make them all optional so plain
C programs keep working unchanged. Frame registration for C++
exceptions (crtbegin's frame_dummy) also arrives via .init_array. */
extern void (*__preinit_array_start[])(void) __attribute__((weak));
extern void (*__preinit_array_end[])(void) __attribute__((weak));
extern void (*__init_array_start[])(void) __attribute__((weak));
extern void (*__init_array_end[])(void) __attribute__((weak));
extern void (*__fini_array_start[])(void) __attribute__((weak));
extern void (*__fini_array_end[])(void) __attribute__((weak));
extern int atexit(void (*fn)(void));
extern void exit(int status);
static void _run_init_arrays(void) {
for (void (**f)(void) = __preinit_array_start; f < __preinit_array_end; f++) {
(*f)();
}
for (void (**f)(void) = __init_array_start; f < __init_array_end; f++) {
(*f)();
}
}
/* Destructors run in reverse registration order, hooked through
atexit so they also run when the program calls exit() directly. */
static void _run_fini_array(void) {
for (void (**f)(void) = __fini_array_end; f > __fini_array_start; f--) {
(*(f - 1))();
}
}
void _start(void) {
/* Static: 4 KiB args + 256 argv slots would crowd a 32 KiB stack. */
static char argbuf[4096];
@@ -77,6 +109,13 @@ void _start(void) {
argv[argc] = 0;
_sys1(SYS_EXIT, (long)main(argc, argv));
if (__fini_array_start != __fini_array_end) {
atexit(_run_fini_array);
}
_run_init_arrays();
/* exit() (not raw SYS_EXIT) so the atexit chain - including the
fini-array walker registered above - runs on return from main. */
exit(main(argc, argv));
__builtin_unreachable();
}
Binary file not shown.
+271
View File
@@ -3148,6 +3148,48 @@ unsigned int sleep(unsigned int seconds) {
return 0;
}
/* ========================================================================
math.h: modf and C99 float variants (wrappers over the double
implementations; assumed present by hosted libstdc++)
======================================================================== */
double modf(double x, double *iptr) {
double i = (x < 0.0) ? ceil(x) : floor(x);
*iptr = i;
return x - i;
}
float modff(float x, float *iptr) {
double di;
double r = modf((double)x, &di);
*iptr = (float)di;
return (float)r;
}
double hypot(double x, double y) {
return sqrt(x * x + y * y);
}
float hypotf(float x, float y) {
return (float)hypot((double)x, (double)y);
}
float acosf(float x) { return (float)acos((double)x); }
float asinf(float x) { return (float)asin((double)x); }
float atanf(float x) { return (float)atan((double)x); }
float atan2f(float y, float x) { return (float)atan2((double)y, (double)x); }
float coshf(float x) { return (float)cosh((double)x); }
float expf(float x) { return (float)exp((double)x); }
float fmodf(float x, float y) { return (float)fmod((double)x, (double)y); }
float frexpf(float x, int *e) { return (float)frexp((double)x, e); }
float ldexpf(float x, int e) { return (float)ldexp((double)x, e); }
float logf(float x) { return (float)log((double)x); }
float log10f(float x) { return (float)log10((double)x); }
float powf(float x, float y) { return (float)pow((double)x, (double)y); }
float sinhf(float x) { return (float)sinh((double)x); }
float tanf(float x) { return (float)tan((double)x); }
float tanhf(float x) { return (float)tanh((double)x); }
/* ========================================================================
POSIX additions for hosted programs (native binutils/gcc ports)
======================================================================== */
@@ -3232,6 +3274,61 @@ size_t mbstowcs(wchar_t *dst, const char *src, size_t n) {
return i;
}
/* Byte-oriented C locale: one byte, one character. */
int mbtowc(wchar_t *pwc, const char *s, size_t n) {
if (s == NULL) return 0; /* no shift states */
if (n == 0) return -1;
if (*s == '\0') {
if (pwc != NULL) *pwc = 0;
return 0;
}
if (pwc != NULL) *pwc = (wchar_t)(unsigned char)*s;
return 1;
}
int wctomb(char *s, wchar_t wc) {
if (s == NULL) return 0;
*s = (char)wc;
return 1;
}
char *strtok_r(char *str, const char *delim, char **saveptr) {
char *s = (str != NULL) ? str : *saveptr;
if (s == NULL) {
return NULL;
}
s += strspn(s, delim);
if (*s == '\0') {
*saveptr = NULL;
return NULL;
}
char *tok = s;
s += strcspn(s, delim);
if (*s != '\0') {
*s++ = '\0';
*saveptr = s;
} else {
*saveptr = NULL;
}
return tok;
}
char *strtok(char *str, const char *delim) {
static char *_strtok_save;
return strtok_r(str, delim, &_strtok_save);
}
/* No collation beyond byte order in the C locale: transform = copy. */
size_t strxfrm(char *dest, const char *src, size_t n) {
size_t len = strlen(src);
if (n > 0) {
size_t c = (len < n - 1) ? len : n - 1;
memcpy(dest, src, c);
dest[c] = '\0';
}
return len;
}
int mblen(const char *s, size_t n) {
if (s == NULL) return 0;
if (n == 0) return -1;
@@ -3316,6 +3413,180 @@ char *realpath(const char *path, char *resolved) {
return resolved;
}
int fgetpos(FILE *stream, fpos_t *pos) {
if (stream == NULL || pos == NULL) {
errno = EINVAL;
return -1;
}
long p = ftell(stream);
if (p < 0) return -1;
*pos = (fpos_t)p;
return 0;
}
int fsetpos(FILE *stream, const fpos_t *pos) {
if (stream == NULL || pos == NULL) {
errno = EINVAL;
return -1;
}
return fseek(stream, (long)*pos, SEEK_SET);
}
/* Streams are unbuffered in this libc; buffering hints are no-ops. */
void setbuf(FILE *stream, char *buf) {
(void)stream; (void)buf;
}
/* Character-driven scanf core over FILE (fgetc/ungetc). Supports the
same conversions as sscanf: %d %i %u %x %s %c %% with field widths
and the l/ll length modifiers. */
int vfscanf(FILE *stream, const char *fmt, va_list ap) {
int matched = 0;
int c;
while (*fmt) {
if (isspace((unsigned char)*fmt)) {
/* Whitespace in the format skips any input whitespace. */
do {
c = fgetc(stream);
} while (c != EOF && isspace(c));
if (c != EOF) ungetc(c, stream);
fmt++;
continue;
}
if (*fmt != '%') {
c = fgetc(stream);
if (c == EOF) goto done;
if ((char)c != *fmt) {
ungetc(c, stream);
goto done;
}
fmt++;
continue;
}
fmt++;
if (*fmt == '%') {
c = fgetc(stream);
if (c != '%') {
if (c != EOF) ungetc(c, stream);
goto done;
}
fmt++;
continue;
}
int width = 0;
while (isdigit((unsigned char)*fmt)) {
width = width * 10 + (*fmt++ - '0');
}
int is_long = 0;
if (*fmt == 'l') { is_long = 1; fmt++; }
if (*fmt == 'l') { fmt++; } /* ll behaves as l on LP64 */
if (*fmt == 'd' || *fmt == 'i' || *fmt == 'u' || *fmt == 'x' ||
*fmt == 'X') {
int base = (*fmt == 'x' || *fmt == 'X') ? 16 : 10;
do {
c = fgetc(stream);
} while (c != EOF && isspace(c));
if (c == EOF) goto done;
int neg = 0;
int n = 0;
if ((*fmt == 'd' || *fmt == 'i') && (c == '-' || c == '+')) {
neg = (c == '-');
c = fgetc(stream);
}
if (base == 16 && c == '0') {
int c2 = fgetc(stream);
if (c2 == 'x' || c2 == 'X') {
c = fgetc(stream);
} else if (c2 != EOF) {
ungetc(c2, stream);
}
}
unsigned long val = 0;
int any = 0;
for (;;) {
int digit = -1;
if (c >= '0' && c <= '9') digit = c - '0';
else if (base == 16 && c >= 'a' && c <= 'f') digit = c - 'a' + 10;
else if (base == 16 && c >= 'A' && c <= 'F') digit = c - 'A' + 10;
if (digit < 0 || (width > 0 && n >= width)) break;
val = val * (unsigned long)base + (unsigned long)digit;
any = 1;
n++;
c = fgetc(stream);
if (c == EOF) break;
}
if (c != EOF) ungetc(c, stream);
if (!any) goto done;
if (*fmt == 'd' || *fmt == 'i') {
long sv = neg ? -(long)val : (long)val;
if (is_long) *va_arg(ap, long *) = sv;
else *va_arg(ap, int *) = (int)sv;
} else {
if (is_long) *va_arg(ap, unsigned long *) = val;
else *va_arg(ap, unsigned int *) = (unsigned int)val;
}
matched++;
} else if (*fmt == 's') {
do {
c = fgetc(stream);
} while (c != EOF && isspace(c));
if (c == EOF) goto done;
char *dst = va_arg(ap, char *);
int n = 0;
while (c != EOF && !isspace(c) && (width == 0 || n < width)) {
*dst++ = (char)c;
n++;
c = fgetc(stream);
}
*dst = '\0';
if (c != EOF) ungetc(c, stream);
if (n == 0) goto done;
matched++;
} else if (*fmt == 'c') {
int n = (width > 0) ? width : 1;
char *dst = va_arg(ap, char *);
int got = 0;
while (got < n) {
c = fgetc(stream);
if (c == EOF) break;
dst[got++] = (char)c;
}
if (got < n) goto done;
matched++;
} else {
goto done; /* unsupported conversion */
}
fmt++;
}
done:
return (matched == 0) ? EOF : matched;
}
int fscanf(FILE *stream, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int r = vfscanf(stream, fmt, ap);
va_end(ap);
return r;
}
int scanf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int r = vfscanf(stdin, fmt, ap);
va_end(ap);
return r;
}
int putc(int c, FILE *stream) {
return fputc(c, stream);
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.