From 2d052429ca5f0a4d136569859f4864a1263d2655 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 11 Sep 2026 15:00:41 +0800 Subject: [PATCH] feat(kernel): implement user-space memory management and PHP Nano support - Configure kernel-only paging with separate user virtual regions - Add PHP Nano project compilation with link library support - Implement aligned memory block management for Zend allocator - Create reusable libtypephp-os.a platform archive for TypePHP userspace - Add uname system call support and update user-space syscalls - Increase QEMU memory allocation from 128M to 160M - Add TNHELLO.ELF as full TypePHP + PHPX --- examples/typephp-os/Makefile | 89 +++++++++++++++++-- examples/typephp-os/README.md | 36 ++++++-- examples/typephp-os/ROADMAP.md | 11 ++- examples/typephp-os/boot/boot.S | 13 +-- examples/typephp-os/kernel/core/abi/libc.c | 86 ++++++++++++++++-- .../kernel/core/abi/typephp_os_abi.h | 5 ++ .../kernel/core/abi/typephp_os_syscall.h | 1 + .../kernel/core/abi/unimplemented.S | 1 + examples/typephp-os/kernel/core/memory.c | 7 +- examples/typephp-os/kernel/core/process.c | 35 +++++++- examples/typephp-os/kernel/fat16.php | 12 +-- examples/typephp-os/tools/test-qemu.sh | 31 +++++-- examples/typephp-os/user/README.md | 15 +++- .../typephp-os/user/include/sys/syscall.h | 1 + .../typephp-os/user/include/sys/utsname.h | 20 +++++ examples/typephp-os/user/libc.c | 6 ++ examples/typephp-os/user/nano/hello.php | 2 + examples/typephp-os/user/nano/hello.yml | 17 ++-- examples/typephp-os/user/runtime/README.md | 36 ++++++++ .../typephp-os/user/{nano => runtime}/crt0.S | 0 .../typephp-os/user/{nano => runtime}/host.cc | 7 +- .../user/{nano => runtime}/linker.ld | 0 .../typephp-os/user/{nano => runtime}/posix.c | 16 ++-- .../user/{nano => runtime}/syscall.S | 0 examples/typephp-os/user/sh.c | 4 +- examples/typephp-os/user/uname.c | 76 ++++++++++++++++ examples/typephp-os/user/vmfault.c | 2 +- phpunit/src/CompilerBaseApiTest.php | 19 ++++ src/Translator.php | 6 +- 29 files changed, 474 insertions(+), 80 deletions(-) create mode 100644 examples/typephp-os/user/include/sys/utsname.h create mode 100644 examples/typephp-os/user/runtime/README.md rename examples/typephp-os/user/{nano => runtime}/crt0.S (100%) rename examples/typephp-os/user/{nano => runtime}/host.cc (86%) rename examples/typephp-os/user/{nano => runtime}/linker.ld (100%) rename examples/typephp-os/user/{nano => runtime}/posix.c (91%) rename examples/typephp-os/user/{nano => runtime}/syscall.S (100%) create mode 100644 examples/typephp-os/user/uname.c diff --git a/examples/typephp-os/Makefile b/examples/typephp-os/Makefile index 6bbcd71d..ccbcad8e 100644 --- a/examples/typephp-os/Makefile +++ b/examples/typephp-os/Makefile @@ -7,12 +7,30 @@ PAYLOAD_BIN := $(BUILD)/kernel64.bin PAYLOAD_OBJECT := $(OBJECT_DIR)/kernel64-payload.o KERNEL := $(BUILD)/typephp-os.elf DISK := $(BUILD)/typephp-os.img -USER_COMMANDS := ls cd date pwd cat echo write touch mkdir rm rmdir mv fault vmfault wrfault memtest +USER_COMMANDS := ls cd date uname pwd cat echo write touch mkdir rm rmdir mv fault vmfault wrfault memtest USER_PROGRAMS := sh $(USER_COMMANDS) USER_SH_ELF := $(BUILD)/sh.elf USER_COMMAND_ELFS := $(addprefix $(BUILD)/,$(addsuffix .elf,$(USER_COMMANDS))) USER_CRT0_OBJECT := $(OBJECT_DIR)/user-crt0.o USER_LIBC_OBJECTS := $(OBJECT_DIR)/user-libc.o $(OBJECT_DIR)/user-syscall.o +NANO_USER_ELF := $(BUILD)/tnhello.elf +TYPEPHP_OS_LIBRARY := $(BUILD)/libtypephp-os.a +TYPEPHP_OS_LIBRARY_OBJECT_DIR := $(BUILD)/libtypephp-os +PHP_NANO_DIR := $(or $(realpath ../../vendor/swoole/php-nano),$(realpath ../../../php-nano)) +PHPX_DIR := $(or $(realpath ../../vendor/swoole/phpx),$(realpath ../../../phpx)) +OPENLIBM_SOURCES := $(shell sed -n 's|^\(.*\.c\)$$|thirdparty/openlibm/\1|p' tools/openlibm-files.txt) +TYPEPHP_OS_LIBRARY_C_SOURCES := user/runtime/posix.c kernel/core/abi/libc.c $(OPENLIBM_SOURCES) +TYPEPHP_OS_LIBRARY_CXX_SOURCES := user/runtime/host.cc kernel/core/abi/cxx.cpp +TYPEPHP_OS_LIBRARY_ASM_SOURCES := user/runtime/crt0.S user/runtime/syscall.S \ + kernel/core/abi/unimplemented.S +TYPEPHP_OS_LIBRARY_OBJECTS := \ + $(patsubst %.c,$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o,$(TYPEPHP_OS_LIBRARY_C_SOURCES)) \ + $(patsubst %.cc,$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o,$(filter %.cc,$(TYPEPHP_OS_LIBRARY_CXX_SOURCES))) \ + $(patsubst %.cpp,$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o,$(filter %.cpp,$(TYPEPHP_OS_LIBRARY_CXX_SOURCES))) \ + $(patsubst %.S,$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o,$(TYPEPHP_OS_LIBRARY_ASM_SOURCES)) +TYPEPHP_OS_LIBRARY_DEPFILES := $(addsuffix .d,$(TYPEPHP_OS_LIBRARY_OBJECTS)) +NANO_USER_SOURCES := user/nano/hello.php user/nano/hello.yml user/runtime/linker.ld \ + $(TYPEPHP_OS_LIBRARY) BOOTFLAGS := -m32 -O2 -ffreestanding -fno-pic -fno-pie \ -fno-stack-protector -Wall -Wextra @@ -21,8 +39,31 @@ KERNEL64_BOOTFLAGS := -m64 -O2 -ffreestanding -fno-builtin -fno-pic -fno-pie \ USER_CFLAGS := -m64 -O2 -ffreestanding -fno-builtin -fno-pic -fno-pie \ -fno-stack-protector -mno-red-zone -Wall -Wextra -Iuser/include -Ikernel/core/abi USER_LDFLAGS := -m elf_x86_64 -nostdlib -static -T user/linker.ld - -.PHONY: all thirdparty payload objects disk run test clean +TYPEPHP_OS_RUNTIME_DEFINES := -DTYPEPHP_NATIVE=1 -DPHP_NANO=1 -DPHPX_NANO=1 \ + -D_POSIX_C_SOURCE=200809L -DTYPEPHP_NO_MAIN=1 -DPHPX_NO_EXCEPTION=1 \ + -DPHPX_NO_RTTI=1 -DPHP_NANO_NO_LIBC=1 -DHAVE_SYS_UTSNAME_H=1 \ + -D__NO_CTYPE=1 -DZEND_MM_ERROR=0 -DZEND_MM_CUSTOM=0 -DASSEMBLER=1 \ + -D__BSD_VISIBLE=1 +TYPEPHP_OS_RUNTIME_INCLUDES := -Ikernel/core/abi \ + -Ithirdparty/openlibm/include -Ithirdparty/openlibm/src \ + -Ithirdparty/openlibm/amd64 -I$(PHP_NANO_DIR) -I$(PHP_NANO_DIR)/Zend \ + -I$(PHP_NANO_DIR)/include -I$(PHP_NANO_DIR)/main -I$(PHP_NANO_DIR)/TSRM \ + -I$(PHP_NANO_DIR)/ext/date/lib -I$(PHP_NANO_DIR)/ext/pcre/pcre2lib \ + -I$(PHPX_DIR)/include -I$(PHPX_DIR)/thirdparty/wren-gc/include +TYPEPHP_OS_RUNTIME_CFLAGS := -m64 -O0 -std=c11 -ffreestanding -fno-builtin \ + -fno-pic -fno-pie -fno-stack-protector -mno-red-zone -mcmodel=small \ + -ffunction-sections -fdata-sections -Wall -Wextra \ + $(TYPEPHP_OS_RUNTIME_DEFINES) $(TYPEPHP_OS_RUNTIME_INCLUDES) +TYPEPHP_OS_RUNTIME_CXXFLAGS := -m64 -O0 -std=c++17 -ffreestanding -fno-builtin \ + -fno-pic -fno-pie -fno-stack-protector -mno-red-zone -mcmodel=small \ + -fno-rtti -fno-threadsafe-statics -ffunction-sections -fdata-sections \ + -Wall -Wextra \ + $(TYPEPHP_OS_RUNTIME_DEFINES) $(TYPEPHP_OS_RUNTIME_INCLUDES) +TYPEPHP_OS_RUNTIME_ASMFLAGS := -m64 -ffreestanding -fno-pic -fno-pie \ + -fno-stack-protector -mno-red-zone -mcmodel=small +ARFLAGS := rcs + +.PHONY: all thirdparty payload objects user-runtime nano-user disk run test clean all: $(KERNEL) $(DISK) @@ -37,15 +78,20 @@ payload: thirdparty objects # object participates only in the packaging link. objects: $(BOOTSTRAP_OBJECT) $(KERNEL64_ENTRY_OBJECT) +user-runtime: $(TYPEPHP_OS_LIBRARY) + +nano-user: $(TYPEPHP_OS_LIBRARY) $(NANO_USER_ELF) + disk: $(DISK) -$(DISK): $(USER_SH_ELF) $(USER_COMMAND_ELFS) | $(BUILD) +$(DISK): $(USER_SH_ELF) $(USER_COMMAND_ELFS) $(NANO_USER_ELF) | $(BUILD) truncate -s 32M $@ mkfs.fat -F 16 -n TYPEPHPOS $@ mmd -i $@ ::/BIN for program in $(USER_PROGRAMS); do \ mcopy -o -i $@ $(BUILD)/$$program.elf ::/BIN/$$program.elf; \ done + mcopy -o -i $@ $(NANO_USER_ELF) ::/BIN/TNHELLO.ELF $(BUILD): mkdir -p $@ @@ -71,10 +117,34 @@ $(OBJECT_DIR)/user-libc.o: user/libc.c kernel/core/abi/typephp_os_syscall.h | $( $(OBJECT_DIR)/user-syscall.o: user/syscall.S kernel/core/abi/typephp_os_syscall.h | $(OBJECT_DIR) gcc $(USER_CFLAGS) -c $< -o $@ -$(USER_SH_ELF): $(OBJECT_DIR)/sh.o $(USER_CRT0_OBJECT) $(USER_LIBC_OBJECTS) user/linker.ld | $(BUILD) +$(OPENLIBM_SOURCES): | thirdparty + +$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o: %.c Makefile + @mkdir -p $(@D) + gcc $(TYPEPHP_OS_RUNTIME_CFLAGS) -MMD -MP -MF $@.d -c $< -o $@ + +$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o: %.cc Makefile + @mkdir -p $(@D) + g++ $(TYPEPHP_OS_RUNTIME_CXXFLAGS) -MMD -MP -MF $@.d -c $< -o $@ + +$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o: %.cpp Makefile + @mkdir -p $(@D) + g++ $(TYPEPHP_OS_RUNTIME_CXXFLAGS) -MMD -MP -MF $@.d -c $< -o $@ + +$(TYPEPHP_OS_LIBRARY_OBJECT_DIR)/%.o: %.S Makefile + @mkdir -p $(@D) + gcc $(TYPEPHP_OS_RUNTIME_ASMFLAGS) -MMD -MP -MF $@.d -c $< -o $@ + +$(TYPEPHP_OS_LIBRARY): $(TYPEPHP_OS_LIBRARY_OBJECTS) | $(BUILD) + $(AR) $(ARFLAGS) $@ $^ + +$(NANO_USER_ELF): $(NANO_USER_SOURCES) | thirdparty $(BUILD) + cd ../.. && ./bin/tpc.php --nano examples/typephp-os/user/nano/hello.yml + +$(USER_SH_ELF): $(OBJECT_DIR)/sh.o $(USER_CRT0_OBJECT) $(USER_LIBC_OBJECTS) user/linker.ld Makefile | $(BUILD) ld $(USER_LDFLAGS) --defsym USER_BASE=0x40000000 -o $@ $(filter %.o,$^) -$(USER_COMMAND_ELFS): $(BUILD)/%.elf: $(OBJECT_DIR)/%.o $(USER_CRT0_OBJECT) $(USER_LIBC_OBJECTS) user/linker.ld | $(BUILD) +$(USER_COMMAND_ELFS): $(BUILD)/%.elf: $(OBJECT_DIR)/%.o $(USER_CRT0_OBJECT) $(USER_LIBC_OBJECTS) user/linker.ld Makefile | $(BUILD) ld $(USER_LDFLAGS) --defsym USER_BASE=0x41000000 -o $@ $(filter %.o,$^) $(KERNEL): payload @@ -86,17 +156,18 @@ $(KERNEL): payload $(BOOTSTRAP_OBJECT) $(PAYLOAD_OBJECT) run: all - qemu-system-x86_64 -m 128M -kernel $(KERNEL) \ + qemu-system-x86_64 -m 160M -kernel $(KERNEL) \ -drive file=$(DISK),format=raw,if=ide,index=0 \ -display none -serial stdio -monitor none -no-reboot -no-shutdown -test: $(KERNEL) $(USER_SH_ELF) $(USER_COMMAND_ELFS) +test: $(KERNEL) $(USER_SH_ELF) $(USER_COMMAND_ELFS) $(NANO_USER_ELF) truncate -s 32M $(DISK) mkfs.fat -F 16 -n TYPEPHPOS $(DISK) mmd -i $(DISK) ::/BIN for program in $(USER_PROGRAMS); do \ mcopy -o -i $(DISK) $(BUILD)/$$program.elf ::/BIN/$$program.elf; \ done + mcopy -o -i $(DISK) $(NANO_USER_ELF) ::/BIN/TNHELLO.ELF # An alias not known while linking the kernel verifies disk-based discovery. mcopy -o -i $(DISK) $(BUILD)/echo.elf ::/BIN/hello.elf mcopy -o -i $(DISK) tools/invalid-elf.txt ::/BIN/bad.elf @@ -110,3 +181,5 @@ test: $(KERNEL) $(USER_SH_ELF) $(USER_COMMAND_ELFS) clean: rm -rf $(BUILD) + +-include $(TYPEPHP_OS_LIBRARY_DEPFILES) diff --git a/examples/typephp-os/README.md b/examples/typephp-os/README.md index 0954b5bb..6569ff02 100644 --- a/examples/typephp-os/README.md +++ b/examples/typephp-os/README.md @@ -85,6 +85,10 @@ The build produces these useful files: independently built shell and command executables; - `build/sh.elf` and the command ELF files: independent ELF64 Ring-3 programs linked with the small userspace bootstrap libc rather than a hosted libc. +- `build/tnhello.elf`: a full TypePHP + PHPX + PHP Nano Ring-3 executable + generated by tpc from `user/nano/hello.php`. +- `build/libtypephp-os.a`: the reusable userspace platform archive linked by + every tpc-generated TypePHP program with `-ltypephp-os`. Run the automated serial-output smoke test with: @@ -113,7 +117,7 @@ the payload linked at 2 MiB. To invoke QEMU manually: ```shell -qemu-system-x86_64 -m 128M \ +qemu-system-x86_64 -m 160M \ -kernel examples/typephp-os/build/typephp-os.elf \ -drive file=examples/typephp-os/build/typephp-os.img,format=raw,if=ide,index=0 \ -display none -serial stdio -monitor none -no-reboot -no-shutdown @@ -148,7 +152,7 @@ The QEMU smoke test currently verifies: - normal PHP output through multi-argument `echo` and `PHPWRITE`; - a TypePHP prime calculation for 0–100; - disk-backed ELF64 loading, Linux-style `argc/argv` startup, and libc-shaped - userspace calls; + userspace calls, including a complete PHP Nano program generated by tpc; - a separate x86-64 address space for the resident shell and every command, 4 KiB user mappings, a guarded 64 KiB stack, NX/WP enforcement, and ELF `RX`/`RW` segment permissions; @@ -161,9 +165,9 @@ The QEMU smoke test currently verifies: nested directory creation/removal through the TypePHP FAT16 implementation. The 64-bit payload currently occupies about 7 MiB. The first 40 MiB is kept -away from both allocators, covering the kernel and the current 32–36 MiB user -virtual-address window. Entire Zend chunks are not yet returned to the -physical-page pool. +away from both allocators for the kernel payload, bootstrap state, and early +host arena. Userspace has a separate 0x40000000–0x50000000 virtual region. +Entire Zend chunks are not yet returned to the physical-page pool. The current filesystem deliberately supports only DOS 8.3 names. Reading, directory enumeration, `stat`, executable loading, and file/directory mutation @@ -181,7 +185,7 @@ APIs that execute host commands remain unavailable. After the TypePHP self-check, the kernel opens `/BIN/SH.ELF` from FAT16, validates and loads its `PT_LOAD` segments, installs a 64-bit TSS and an IDT gate, and enters Ring 3 with `iretq`. The resident shell and each transient -command receive an independent CR3. Their 32–36 MiB virtual window is composed +command receive an independent CR3. Their 1–1.25 GiB virtual window is composed from recyclable 4 KiB pages; the shared identity-mapped kernel remains supervisor-only. The CPU has write protection and no-execute enabled, and the loader applies the final ELF `PF_W` and `PF_X` permissions after copying each @@ -214,11 +218,31 @@ Because this is a deliberately single-task model, the working directory is a session-global property; a successful `cd` therefore remains visible after control returns to the shell. +`TNHELLO.ELF` demonstrates the intended TypePHP userspace path. It is built by +the ordinary tpc Nano pipeline, links the complete php-nano and PHPX manifests, +links the reusable `libtypephp-os.a` platform archive, and uses the same ELF +loader and Ring-3 syscall boundary as the C commands. +Its startup code converts the Linux-style initial stack into `argc` and +`argv`, and `php_uname()` calls the standard extension's unchanged `info.c` +implementation through a userspace `uname()` ABI that reports TypePHP-OS. +Run it from the shell with: + +```text +tnhello alpha beta +``` + +The current user ABI intentionally implements only the calls needed by the +demonstration. Missing libc/POSIX operations are linkable panic stubs and +terminate the command with an explicit `unsupported TypePHP-OS user ABI` +message; they do not silently emulate success. + Available commands are: ```text ls date +uname +uname -a pwd echo Hello TypePHP cat HELLO.TXT diff --git a/examples/typephp-os/ROADMAP.md b/examples/typephp-os/ROADMAP.md index 42ecfbfa..1fa3da54 100644 --- a/examples/typephp-os/ROADMAP.md +++ b/examples/typephp-os/ROADMAP.md @@ -46,7 +46,7 @@ behavior. read/write-through cache now avoids repeated ATA PIO reads while preserving synchronous persistence. A general VFS page cache, cross-directory rename, replacement semantics, and long filenames are next. -6. **Single-task userspace — third slice complete.** Disk-backed ELF64 +6. **Single-task userspace — fourth slice complete.** Disk-backed ELF64 validation/loading from FAT16, GDT/TSS, Ring-3 entry, synchronous `int 0x80` system calls, COM1 standard I/O, saved parent context, shared @@ -68,8 +68,13 @@ behavior. and `wrfault.elf` cover invalid-opcode, isolation, and write-protection recovery, while page accounting checks reclamation. The programs use standard C `main(argc, argv)` behind a shared crt0 and receive - a Linux-style initial stack; syscall evolution follows the glibc migration - rules in `user/README.md`. + a Linux-style initial stack. A full PHP Nano/PHPX executable is now produced + by the ordinary tpc pipeline and runs in Ring 3 with working TypePHP + `argc/argv`; standard `php_uname()` reaches the userspace `uname()` ABI and + reports TypePHP-OS. Shared TypePHP userspace support is compiled once into + `libtypephp-os.a`, allowing multiple tpc projects to link the same runtime + platform archive. Unavailable ABI functions remain explicit panic stubs. + Syscall evolution follows the glibc migration rules in `user/README.md`. 7. **Native Class memory.** Exercise Wren GC through Zend MM and verify tracing of PHPX fields under sustained allocation. 8. **Kernel services.** Interrupt-driven timer and keyboard, higher-level VM diff --git a/examples/typephp-os/boot/boot.S b/examples/typephp-os/boot/boot.S index 2ff095f6..cbcf4aca 100644 --- a/examples/typephp-os/boot/boot.S +++ b/examples/typephp-os/boot/boot.S @@ -36,13 +36,13 @@ _start: # Identity-map the first GiB with 2 MiB pages. mov $pdp_table, %eax - # Upper paging levels must permit user traversal. Individual PDEs still - # decide whether a 2 MiB page is supervisor-only or visible to Ring 3. - or $0x7, %eax + # The bootstrap map is kernel-only. Each process later receives a + # separate CR3 with a high userspace region. + or $0x3, %eax mov %eax, pml4_table mov $pd_table, %eax - or $0x7, %eax + or $0x3, %eax mov %eax, pdp_table mov $pd_table, %edi @@ -54,11 +54,6 @@ map_page: add $8, %edi loop map_page - # Reserve 32-36 MiB for the single user process image and stack. The - # remaining identity-mapped pages stay supervisor-only. - orl $0x4, pd_table + (16 * 8) - orl $0x4, pd_table + (17 * 8) - # Enable PAE and prepare the x87/SSE state before entering long mode. mov %cr0, %ecx and $0xfffffffb, %ecx diff --git a/examples/typephp-os/kernel/core/abi/libc.c b/examples/typephp-os/kernel/core/abi/libc.c index 251b8ce0..086c8d5c 100644 --- a/examples/typephp-os/kernel/core/abi/libc.c +++ b/examples/typephp-os/kernel/core/abi/libc.c @@ -25,15 +25,25 @@ typedef struct typephp_os_block { size_t size; } typephp_os_block; +typedef struct typephp_os_aligned_block { + void *pointer; + size_t size; + int allocated; +} typephp_os_aligned_block; + +enum { TYPEPHP_OS_ALIGNED_BLOCK_CAPACITY = 64 }; + static uintptr_t arena_cursor; static uintptr_t arena_end; static int typephp_os_errno; +static typephp_os_aligned_block aligned_blocks[TYPEPHP_OS_ALIGNED_BLOCK_CAPACITY]; /* TypePHP OS has no hosted stdio object. These opaque values only satisfy * upstream error paths; bytes are forwarded through the host write hook. */ void *stdin = (void *) 0; void *stdout = (void *) 1; void *stderr = (void *) 2; +char **environ; size_t strlen(const char *string); void *malloc(size_t size); @@ -70,6 +80,12 @@ static int is_power_of_two(size_t value) void typephp_os_memory_init(void *address, size_t size) { const uintptr_t begin = (uintptr_t) address; + size_t index; + for (index = 0; index < TYPEPHP_OS_ALIGNED_BLOCK_CAPACITY; ++index) { + aligned_blocks[index].pointer = 0; + aligned_blocks[index].size = 0; + aligned_blocks[index].allocated = 0; + } if (size > UINTPTR_MAX - begin) { arena_cursor = 0; arena_end = 0; @@ -696,9 +712,20 @@ void *malloc(size_t size) void free(void *pointer) { - /* The bootstrap arena is monotonic. Zend's own allocations are reclaimed - * by zend_mm; backing chunks remain reserved until the kernel exits. */ - (void) pointer; + size_t index; + if (pointer == 0) { + return; + } + /* Zend returns its large aligned backing blocks through free(). Retain + * their address-space reservation, but make the blocks reusable. */ + for (index = 0; index < TYPEPHP_OS_ALIGNED_BLOCK_CAPACITY; ++index) { + if (aligned_blocks[index].pointer == pointer) { + aligned_blocks[index].allocated = 0; + return; + } + } + /* Small bootstrap allocations are process-lifetime monotonic blocks. + * Request-local PHP values are reclaimed inside Zend MM itself. */ } void *calloc(size_t count, size_t size) @@ -716,12 +743,24 @@ void *calloc(size_t count, size_t size) void *realloc(void *pointer, size_t size) { + size_t index; if (pointer == 0) { return malloc(size); } if (size == 0) { return 0; } + for (index = 0; index < TYPEPHP_OS_ALIGNED_BLOCK_CAPACITY; ++index) { + if (aligned_blocks[index].pointer == pointer) { + const size_t old_size = aligned_blocks[index].size; + void *replacement = malloc(size); + if (replacement != 0) { + memcpy(replacement, pointer, old_size < size ? old_size : size); + aligned_blocks[index].allocated = 0; + } + return replacement; + } + } typephp_os_block *old = (typephp_os_block *) pointer - 1; void *replacement = malloc(size); if (replacement != 0) { @@ -732,6 +771,8 @@ void *realloc(void *pointer, size_t size) int posix_memalign(void **result, size_t alignment, size_t size) { + size_t index; + size_t free_slot = TYPEPHP_OS_ALIGNED_BLOCK_CAPACITY; if (result == 0 || !is_power_of_two(alignment) || alignment < sizeof(void *)) { return 22; /* EINVAL */ } @@ -740,14 +781,45 @@ int posix_memalign(void **result, size_t alignment, size_t size) return 12; /* ENOMEM */ } - const uintptr_t payload = align_up( - arena_cursor + sizeof(typephp_os_block), alignment); + /* Zend requests its backing chunks with 2 MiB alignment. Reuse released + * chunks before extending the monotonic host arena. */ + const int needs_header = alignment < 2u * 1024u * 1024u; + if (!needs_header) { + for (index = 0; index < TYPEPHP_OS_ALIGNED_BLOCK_CAPACITY; ++index) { + typephp_os_aligned_block *block = aligned_blocks + index; + if (block->pointer == 0) { + if (free_slot == TYPEPHP_OS_ALIGNED_BLOCK_CAPACITY) { + free_slot = index; + } + continue; + } + if (!block->allocated && block->size >= size + && ((uintptr_t) block->pointer & (alignment - 1u)) == 0) { + block->allocated = 1; + memset(block->pointer, 0, size); + *result = block->pointer; + return 0; + } + } + if (free_slot == TYPEPHP_OS_ALIGNED_BLOCK_CAPACITY) { + *result = 0; + return 12; + } + } + const uintptr_t payload = align_up(arena_cursor + + (needs_header ? sizeof(typephp_os_block) : 0u), alignment); if (payload > arena_end || size > arena_end - payload) { *result = 0; return 12; } - typephp_os_block *block = (typephp_os_block *) payload - 1; - block->size = size; + if (needs_header) { + typephp_os_block *block = (typephp_os_block *) payload - 1; + block->size = size; + } else { + aligned_blocks[free_slot].pointer = (void *) payload; + aligned_blocks[free_slot].size = size; + aligned_blocks[free_slot].allocated = 1; + } arena_cursor = payload + size; *result = (void *) payload; return 0; diff --git a/examples/typephp-os/kernel/core/abi/typephp_os_abi.h b/examples/typephp-os/kernel/core/abi/typephp_os_abi.h index 390e5957..4c15f814 100644 --- a/examples/typephp-os/kernel/core/abi/typephp_os_abi.h +++ b/examples/typephp-os/kernel/core/abi/typephp_os_abi.h @@ -26,6 +26,11 @@ size_t typephp_os_memory_available(void); /* A platform may override these weak hooks for diagnostics and shutdown. */ void typephp_os_write(const char *data, size_t size); +#ifdef __cplusplus +[[noreturn]] +#else +_Noreturn +#endif void typephp_os_panic(const char *message); /* Generic PHPX no-exception policy hook. */ diff --git a/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h b/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h index 776dd199..412a0970 100644 --- a/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h +++ b/examples/typephp-os/kernel/core/abi/typephp_os_syscall.h @@ -14,6 +14,7 @@ enum typephp_os_syscall_number { TYPEPHP_SYS_MUNMAP = 11, TYPEPHP_SYS_BRK = 12, TYPEPHP_SYS_EXIT = 60, + TYPEPHP_SYS_UNAME = 63, TYPEPHP_SYS_GETCWD = 79, TYPEPHP_SYS_CHDIR = 80, TYPEPHP_SYS_MKDIR = 83, diff --git a/examples/typephp-os/kernel/core/abi/unimplemented.S b/examples/typephp-os/kernel/core/abi/unimplemented.S index 08b47948..7de3a5d2 100644 --- a/examples/typephp-os/kernel/core/abi/unimplemented.S +++ b/examples/typephp-os/kernel/core/abi/unimplemented.S @@ -45,6 +45,7 @@ TYPEPHP_OS_UNIMPLEMENTED umask TYPEPHP_OS_UNIMPLEMENTED utime TYPEPHP_OS_UNIMPLEMENTED mblen +TYPEPHP_OS_UNIMPLEMENTED localtime_r TYPEPHP_OS_UNIMPLEMENTED strerror TYPEPHP_OS_UNIMPLEMENTED strftime diff --git a/examples/typephp-os/kernel/core/memory.c b/examples/typephp-os/kernel/core/memory.c index 798f57ef..51be7313 100644 --- a/examples/typephp-os/kernel/core/memory.c +++ b/examples/typephp-os/kernel/core/memory.c @@ -10,10 +10,9 @@ enum { static const uint64_t CHUNK_SIZE = 2ul * 1024ul * 1024ul; static const uint64_t PAGE_SIZE = 4096ul; -/* Keep the kernel payload and the 32-36 MiB user virtual window away from - * kernel physical allocations. Each user CR3 replaces that part of the - * shared identity map, so physical kernel data there would become hidden. - * A future high-half kernel map can remove this temporary reservation. */ +/* Keep the payload, bootstrap data, and early host arena away from the two + * physical allocators. User programs occupy a separate high virtual region + * in their own CR3, so they no longer overlap this identity-mapped range. */ static const uint64_t KERNEL_RESERVED_END = 40ul * 1024ul * 1024ul; static const uint64_t IDENTITY_MAP_END = 1024ul * 1024ul * 1024ul; diff --git a/examples/typephp-os/kernel/core/process.c b/examples/typephp-os/kernel/core/process.c index 3e1a07a1..63e12f9a 100644 --- a/examples/typephp-os/kernel/core/process.c +++ b/examples/typephp-os/kernel/core/process.c @@ -37,8 +37,18 @@ enum { IDT_SYSCALL = 0x80, MAX_USER_ARGUMENTS = 8, USER_PATH_MAX = 128, + UTSNAME_LENGTH = 65, }; +typedef struct { + char sysname[UTSNAME_LENGTH]; + char nodename[UTSNAME_LENGTH]; + char release[UTSNAME_LENGTH]; + char version[UTSNAME_LENGTH]; + char machine[UTSNAME_LENGTH]; + char domainname[UTSNAME_LENGTH]; +} user_utsname; + enum { ELF_PT_LOAD = 1, ELF_PF_X = 1, @@ -304,7 +314,11 @@ static int read_exact(int fd, void *buffer, size_t size) { unsigned char *output = (unsigned char *) buffer; while (size != 0) { - ssize_t result = read(fd, output, size); + /* The TypePHP filesystem bridge returns each read as a PHP string. + * Bound the temporary allocation while loading multi-megabyte Nano + * executables instead of requesting an entire ELF segment at once. */ + const size_t request = size > 16u * 1024u ? 16u * 1024u : size; + ssize_t result = read(fd, output, request); if (result < 0) { return -errno; } @@ -630,6 +644,23 @@ static long syscall_time(long *result) return seconds; } +static long syscall_uname(user_utsname *result) +{ + static const user_utsname identity = { + "TypePHP-OS", + "typephp-os", + "0.1", + "TypePHP Nano user mode", + "x86_64", + "localdomain", + }; + if (!user_buffer(result, sizeof(*result))) { + return -EFAULT; + } + memcpy(result, &identity, sizeof(*result)); + return 0; +} + static unsigned int vm_protection(int protection) { unsigned int flags = 0; @@ -1051,6 +1082,8 @@ long typephp_os_syscall_dispatch(syscall_frame *frame) (int) frame->rdx, (int) frame->r10); case TYPEPHP_SYS_EXIT: return syscall_exit(frame, (long) frame->rdi); + case TYPEPHP_SYS_UNAME: + return syscall_uname((user_utsname *) frame->rdi); default: return -ENOSYS; } diff --git a/examples/typephp-os/kernel/fat16.php b/examples/typephp-os/kernel/fat16.php index 29ad6ae2..b894fb7c 100644 --- a/examples/typephp-os/kernel/fat16.php +++ b/examples/typephp-os/kernel/fat16.php @@ -830,7 +830,7 @@ final class Fat16Volume } $remaining = $this->readU32($entry, 28); $cluster = $this->readU16($entry, 26); - $result = ''; + $parts = []; $visited = 0; while ($remaining > 0 && $cluster >= 2 && $cluster < 0xfff8 && $visited < $this->clusterCount) { @@ -841,13 +841,13 @@ final class Fat16Volume return ''; } $length = $remaining < 512 ? $remaining : 512; - $result .= substr($sector, 0, $length); + $parts[] = substr($sector, 0, $length); $remaining -= $length; } $cluster = $this->readFat($cluster); $visited++; } - return $remaining === 0 ? $result : ''; + return $remaining === 0 ? implode('', $parts) : ''; } public function readPathFile(string $path): string @@ -862,7 +862,7 @@ final class Fat16Volume } $remaining = $this->readU32($entry, 28); $cluster = $this->readU16($entry, 26); - $result = ''; + $parts = []; $visited = 0; while ($remaining > 0 && $cluster >= 2 && $cluster < 0xfff8 && $visited < $this->clusterCount) { @@ -873,13 +873,13 @@ final class Fat16Volume return ''; } $partLength = $remaining < 512 ? $remaining : 512; - $result .= substr($sector, 0, $partLength); + $parts[] = substr($sector, 0, $partLength); $remaining -= $partLength; } $cluster = $this->readFat($cluster); $visited++; } - return $remaining === 0 ? $result : ''; + return $remaining === 0 ? implode('', $parts) : ''; } public function removeRootFile(string $name): bool diff --git a/examples/typephp-os/tools/test-qemu.sh b/examples/typephp-os/tools/test-qemu.sh index 98655449..db0cf07f 100755 --- a/examples/typephp-os/tools/test-qemu.sh +++ b/examples/typephp-os/tools/test-qemu.sh @@ -6,9 +6,14 @@ kernel=${1:?kernel ELF is required} disk=${2:?FAT16 disk image is required} log=${3:?log path is required} -if timeout 20 qemu-system-x86_64 \ +qemu_disk=$(mktemp /tmp/typephp-os-qemu.XXXXXX.img) +trap 'rm -f "${qemu_disk}"' EXIT +cp "${disk}" "${qemu_disk}" + +if timeout 35 qemu-system-x86_64 \ + -m 160M \ -kernel "${kernel}" \ - -drive file="${disk}",format=raw,if=ide,index=0 \ + -drive file="${qemu_disk}",format=raw,if=ide,index=0 \ -display none \ -serial stdio \ -monitor none \ @@ -16,7 +21,7 @@ if timeout 20 qemu-system-x86_64 \ -no-shutdown \ -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ >"${log}" 2>&1 \ - <<< $'\ndate\necho Hello TypePHP userspace\nhello Dynamically loaded\nmissing\nbad\nls /BIN\ncat HELLO.TXT\ntouch /EXPAND/F62.TXT\nls /EXPAND\nwrite NOTE.TXT Hello from Ring 3\ncat NOTE.TXT\ntouch EMPTY.TXT\nmkdir TMP\nls\nrm NOTE.TXT\ncat NOTE.TXT\nrmdir TMP\ncd TMP\nmkdir WORK\ncd WORK\nwrite NOTE.TXT Nested directory write\ncat NOTE.TXT\nmkdir SUB\ncd SUB\nwrite DEEP.TXT Deep directory write\nmv DEEP.TXT MOVED.TXT\ncat MOVED.TXT\ncat DEEP.TXT\npwd\ncd ..\nrmdir SUB\nrm SUB/MOVED.TXT\nrmdir SUB\nls\nrm NOTE.TXT\ncd ..\nrmdir WORK\ncd WORK\nmemtest\nfault\nvmfault\nwrfault\ndate\npwd\ncd BIN\npwd\nls\ncd ..\ncd DOCS\npwd\nls\ncd ..\nls\n'; then + <<< $'\ndate\nuname\nuname -a\necho Hello TypePHP userspace\nhello Dynamically loaded\ntnhello alpha beta\nmissing\nbad\nls /BIN\ncat HELLO.TXT\ntouch /EXPAND/F62.TXT\nls /EXPAND\nwrite NOTE.TXT Hello from Ring 3\ncat NOTE.TXT\ntouch EMPTY.TXT\nmkdir TMP\nls\nrm NOTE.TXT\ncat NOTE.TXT\nrmdir TMP\ncd TMP\nmkdir WORK\ncd WORK\nwrite NOTE.TXT Nested directory write\ncat NOTE.TXT\nmkdir SUB\ncd SUB\nwrite DEEP.TXT Deep directory write\nmv DEEP.TXT MOVED.TXT\ncat MOVED.TXT\ncat DEEP.TXT\npwd\ncd ..\nrmdir SUB\nrm SUB/MOVED.TXT\nrmdir SUB\nls\nrm NOTE.TXT\ncd ..\nrmdir WORK\ncd WORK\nmemtest\nfault\nvmfault\nwrfault\ndate\npwd\ncd BIN\npwd\nls\ncd ..\ncd DOCS\npwd\nls\ncd ..\nls\n'; then status=0 else status=$? @@ -46,10 +51,24 @@ grep -q "Prime list: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, grep -q "Process 1: sh.elf (Ring 3)" "${log}" grep -q "TypePHP-OS user shell" "${log}" grep -q "Ring 3 confirmed" "${log}" -grep -q "Commands: ls, cd, pwd, date, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, fault, vmfault, wrfault" "${log}" +grep -q "Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, fault, vmfault, wrfault" "${log}" grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} UTC' "${log}" +grep -q '^TypePHP-OS$' "${log}" +grep -q '^TypePHP-OS typephp-os 0.1 TypePHP Nano user mode x86_64$' "${log}" grep -q '^Hello TypePHP userspace' "${log}" grep -q '^Dynamically loaded' "${log}" +grep -Fq 'Hello World!string(6) "8.4.14"' "${log}" +grep -Fq 'string(4) "nano"' "${log}" +grep -q '^bool(false)' "${log}" +grep -q '^int(3)' "${log}" +grep -Fq 'string(7) "tnhello"' "${log}" +grep -Fq 'string(5) "alpha"' "${log}" +grep -Fq 'string(4) "beta"' "${log}" +grep -Fq 'string(55) "TypePHP-OS typephp-os 0.1 TypePHP Nano user mode x86_64"' "${log}" +if grep -q 'unsupported TypePHP-OS user ABI' "${log}"; then + echo "the Nano smoke program reached an unsupported userspace ABI" >&2 + exit 1 +fi grep -q '^sh: missing: No such file or directory' "${log}" grep -q '^sh: bad: Exec format error' "${log}" grep -q '^/BIN' "${log}" @@ -69,9 +88,9 @@ grep -Fq 'typephp-os:/WORK$ ' "${log}" grep -q '^brk/mmap: OK' "${log}" grep -q 'User process 2 fault: invalid opcode (#6)' "${log}" grep -q 'sh: fault: Input/output error' "${log}" -grep -q 'User process 2 fault: page fault (#14) .*address 0x0000000002000000' "${log}" +grep -q 'User process 2 fault: page fault (#14) .*address 0x0000000040000000' "${log}" grep -q 'sh: vmfault: Input/output error' "${log}" -grep -q 'User process 2 fault: page fault (#14) .*address 0x00000000022ef000' "${log}" +grep -q 'User process 2 fault: page fault (#14) .*address 0x000000004feff000' "${log}" grep -q 'sh: wrfault: Input/output error' "${log}" grep -Fq 'typephp-os:/$ ' "${log}" grep -Fq 'typephp-os:/DOCS$ ' "${log}" diff --git a/examples/typephp-os/user/README.md b/examples/typephp-os/user/README.md index 4bcf6925..98002134 100644 --- a/examples/typephp-os/user/README.md +++ b/examples/typephp-os/user/README.md @@ -27,12 +27,25 @@ The following rules are normative for this directory: The current bootstrap libc provides `syscall`, `read`, `write`, `openat`, `open`, `close`, `lseek`, `getcwd`, `chdir`, `mkdir`, `rmdir`, `unlink`, -`rename`, `time`, `brk`, `sbrk`, `mmap`, `mprotect`, `munmap`, `strlen`, +`rename`, `time`, `uname`, `brk`, `sbrk`, `mmap`, `mprotect`, `munmap`, `strlen`, `strerror`, `perror`, and `_exit` with libc-compatible C signatures. It also translates kernel `-errno` results into `-1` plus the single-task userspace `errno`. This list is a migration layer, not a reason to create project-specific variants of standard functions. +`nano/` contains the first tpc-generated userspace project. Unlike the small C +commands, it composes the complete PHP Nano and PHPX source manifests into an +independent ELF64 executable. Shared startup, host, POSIX, libc/C++ ABI, and +math support live under `runtime/` and are built once as `libtypephp-os.a`. +Each TypePHP userspace project links that archive with `-ltypephp-os` instead +of recompiling the platform layer. The shared crt0 accepts the same Linux-style +initial stack, so the TypePHP entry receives the real command-line `argc` and +`argv`. The standard extension's `php_uname()` remains implemented in the +upstream `info.c`; the local `uname()` ABI reports `TypePHP-OS`. APIs whose +required system call is not available terminate through an explicit panic +stub. This keeps all Nano symbols linkable without pretending the incomplete +OS ABI is implemented. + The resident shell and each launched command have independent x86-64 address spaces backed by recyclable 4 KiB physical pages. Current ELF files use page-separated `RX` text/rodata and `RW` data/bss segments; the 64 KiB user diff --git a/examples/typephp-os/user/include/sys/syscall.h b/examples/typephp-os/user/include/sys/syscall.h index 447ec636..2b07e5c9 100644 --- a/examples/typephp-os/user/include/sys/syscall.h +++ b/examples/typephp-os/user/include/sys/syscall.h @@ -12,6 +12,7 @@ #define SYS_munmap TYPEPHP_SYS_MUNMAP #define SYS_brk TYPEPHP_SYS_BRK #define SYS_exit TYPEPHP_SYS_EXIT +#define SYS_uname TYPEPHP_SYS_UNAME #define SYS_getcwd TYPEPHP_SYS_GETCWD #define SYS_chdir TYPEPHP_SYS_CHDIR #define SYS_mkdir TYPEPHP_SYS_MKDIR diff --git a/examples/typephp-os/user/include/sys/utsname.h b/examples/typephp-os/user/include/sys/utsname.h new file mode 100644 index 00000000..b55e8b28 --- /dev/null +++ b/examples/typephp-os/user/include/sys/utsname.h @@ -0,0 +1,20 @@ +#ifndef TYPEPHP_OS_USER_SYS_UTSNAME_H +#define TYPEPHP_OS_USER_SYS_UTSNAME_H + +#define _UTSNAME_LENGTH 65 + +/* Keep the Linux new_utsname layout so the syscall remains compatible with + * the interface expected by a future glibc userspace. POSIX exposes the first + * five fields; domainname is the Linux extension at the end of the ABI. */ +struct utsname { + char sysname[_UTSNAME_LENGTH]; + char nodename[_UTSNAME_LENGTH]; + char release[_UTSNAME_LENGTH]; + char version[_UTSNAME_LENGTH]; + char machine[_UTSNAME_LENGTH]; + char domainname[_UTSNAME_LENGTH]; +}; + +int uname(struct utsname *value); + +#endif diff --git a/examples/typephp-os/user/libc.c b/examples/typephp-os/user/libc.c index c93ba215..576d336c 100644 --- a/examples/typephp-os/user/libc.c +++ b/examples/typephp-os/user/libc.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -179,6 +180,11 @@ time_t time(time_t *result) return (time_t) syscall(SYS_time, result); } +int uname(struct utsname *value) +{ + return (int) syscall(SYS_uname, value); +} + void _exit(int status) { (void) syscall(SYS_exit, status); diff --git a/examples/typephp-os/user/nano/hello.php b/examples/typephp-os/user/nano/hello.php index 98c38b53..6cb80d1b 100644 --- a/examples/typephp-os/user/nano/hello.php +++ b/examples/typephp-os/user/nano/hello.php @@ -4,6 +4,8 @@ function main(int $argc, array $argv): void { echo "Hello World!"; var_dump(PHP_VERSION); + var_dump(php_sapi_name()); + var_dump(php_ini_loaded_file()); var_dump($argc); var_dump($argv); var_dump(date('Y-m-d H:i:s', time())); diff --git a/examples/typephp-os/user/nano/hello.yml b/examples/typephp-os/user/nano/hello.yml index 26dc62c7..605cc6b8 100644 --- a/examples/typephp-os/user/nano/hello.yml +++ b/examples/typephp-os/user/nano/hello.yml @@ -2,15 +2,6 @@ name: typephp_os_user_hello mode: bin sources: - hello.php - - host.cc - - posix.c - - ../../kernel/core/abi/libc.c - - ../../kernel/core/abi/cxx.cpp - - ../../kernel/core/abi/unimplemented.S - - ../../thirdparty/openlibm/src -objects: - - ../../build/objects/nano-crt0.o - - ../../build/objects/nano-syscall.o build-dir: ../../build/generated-user-hello output: ../../build/tnhello.elf cxx-std: c++17 @@ -59,6 +50,10 @@ ld-flags: - -no-pie - -Wl,-m,elf_x86_64 - -Wl,--build-id=none - - -Wl,-T,examples/typephp-os/user/nano/linker.ld + - -Wl,-T,examples/typephp-os/user/runtime/linker.ld - -Wl,--defsym,USER_BASE=0x41000000 - - -lgcc +link-paths: + - ../../build +link-libs: + - typephp-os + - gcc diff --git a/examples/typephp-os/user/runtime/README.md b/examples/typephp-os/user/runtime/README.md new file mode 100644 index 00000000..d43c1d93 --- /dev/null +++ b/examples/typephp-os/user/runtime/README.md @@ -0,0 +1,36 @@ +# TypePHP userspace runtime + +This directory contains the TypePHP-OS platform layer shared by all +tpc-generated userspace programs. The top-level Makefile compiles it, the +kernel ABI shims, and OpenLibm into: + +```text +build/libtypephp-os.a +``` + +Build the archive once with: + +```shell +make user-runtime +``` + +A TypePHP project should contain only its own application sources and link the +shared archive through its project file: + +```yaml +link-paths: + - ../../build +link-libs: + - typephp-os + - gcc +``` + +The archive owns the userspace `_start`, PHP Nano host entry, syscall bridge, +POSIX/libc and C++ ABI shims, and math implementation. Application projects +must not compile private copies of these files. This allows any number of +projects under `user/` to reuse one platform archive and keeps their generated +object directories independent. + +The archive and each consumer must use the same freestanding ABI flags and +PHP Nano/PHPX feature definitions. Those shared settings are maintained in the +top-level Makefile and the consumer project file respectively. diff --git a/examples/typephp-os/user/nano/crt0.S b/examples/typephp-os/user/runtime/crt0.S similarity index 100% rename from examples/typephp-os/user/nano/crt0.S rename to examples/typephp-os/user/runtime/crt0.S diff --git a/examples/typephp-os/user/nano/host.cc b/examples/typephp-os/user/runtime/host.cc similarity index 86% rename from examples/typephp-os/user/nano/host.cc rename to examples/typephp-os/user/runtime/host.cc index a02a1426..252cfb1a 100644 --- a/examples/typephp-os/user/nano/host.cc +++ b/examples/typephp-os/user/runtime/host.cc @@ -7,10 +7,14 @@ #include extern "C" int typephp_nano_project_main(); +extern "C" char **environ; namespace { -constexpr std::uintptr_t arena_size = 24u * 1024u * 1024u; +/* Zend MM acquires aligned 2 MiB chunks while PHPX's native object GC also + * keeps process-lifetime metadata. A 48 MiB arena leaves enough headroom for + * the complete Nano module startup in the current 160 MiB QEMU machine. */ +constexpr std::uintptr_t arena_size = 48u * 1024u * 1024u; [[noreturn]] void raw_exit(int status) { @@ -74,6 +78,7 @@ int main(int argc, char **argv) typephp_os_panic("unable to allocate the Nano runtime arena"); } typephp_os_memory_init(reinterpret_cast(begin), arena_size); + environ = argv + argc + 1; php_nano_set_cli_arguments(argc, argv); if (php_nano_startup_composer_extensions() != SUCCESS) { typephp_os_panic("unable to start PHP Nano extensions"); diff --git a/examples/typephp-os/user/nano/linker.ld b/examples/typephp-os/user/runtime/linker.ld similarity index 100% rename from examples/typephp-os/user/nano/linker.ld rename to examples/typephp-os/user/runtime/linker.ld diff --git a/examples/typephp-os/user/nano/posix.c b/examples/typephp-os/user/runtime/posix.c similarity index 91% rename from examples/typephp-os/user/nano/posix.c rename to examples/typephp-os/user/runtime/posix.c index 2a260cb7..d0d9b5cc 100644 --- a/examples/typephp-os/user/nano/posix.c +++ b/examples/typephp-os/user/runtime/posix.c @@ -13,6 +13,10 @@ #include +/* glibc hides this GNU extension when php-nano requests POSIX.1-2008 only. */ +long syscall(long number, ...); +extern char **environ; + static __attribute__((noreturn)) void unsupported(const char *name) { static const char prefix[] = "unsupported TypePHP-OS user ABI: "; @@ -148,17 +152,7 @@ int nanosleep(const struct timespec *duration, struct timespec *remaining) int uname(struct utsname *value) { - if (value == NULL) { - errno = EFAULT; - return -1; - } - memset(value, 0, sizeof(*value)); - memcpy(value->sysname, "TypePHP-OS", sizeof("TypePHP-OS")); - memcpy(value->nodename, "typephp-os", sizeof("typephp-os")); - memcpy(value->release, "0.1", sizeof("0.1")); - memcpy(value->version, "TypePHP Nano user mode", sizeof("TypePHP Nano user mode")); - memcpy(value->machine, "x86_64", sizeof("x86_64")); - return 0; + return (int) syscall(TYPEPHP_SYS_UNAME, value); } int isatty(int fd) diff --git a/examples/typephp-os/user/nano/syscall.S b/examples/typephp-os/user/runtime/syscall.S similarity index 100% rename from examples/typephp-os/user/nano/syscall.S rename to examples/typephp-os/user/runtime/syscall.S diff --git a/examples/typephp-os/user/sh.c b/examples/typephp-os/user/sh.c index 4388d55b..02133518 100644 --- a/examples/typephp-os/user/sh.c +++ b/examples/typephp-os/user/sh.c @@ -61,8 +61,8 @@ int main(int argc, char **argv) (void) write(STDOUT_FILENO, "Ring 3 confirmed\n", sizeof("Ring 3 confirmed\n") - 1); (void) write(STDOUT_FILENO, - "Commands: ls, cd, pwd, date, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, fault, vmfault, wrfault\n", - sizeof("Commands: ls, cd, pwd, date, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, fault, vmfault, wrfault\n") - 1); + "Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, fault, vmfault, wrfault\n", + sizeof("Commands: ls, cd, pwd, date, uname, cat, echo, write, touch, mkdir, rm, rmdir, mv, memtest, fault, vmfault, wrfault\n") - 1); for (;;) { char *arguments[9]; int argument_count = 0; diff --git a/examples/typephp-os/user/uname.c b/examples/typephp-os/user/uname.c new file mode 100644 index 00000000..38c9ef98 --- /dev/null +++ b/examples/typephp-os/user/uname.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +enum uname_field { + FIELD_SYSNAME = 1u << 0, + FIELD_NODENAME = 1u << 1, + FIELD_RELEASE = 1u << 2, + FIELD_VERSION = 1u << 3, + FIELD_MACHINE = 1u << 4, + FIELD_ALL = FIELD_SYSNAME | FIELD_NODENAME | FIELD_RELEASE + | FIELD_VERSION | FIELD_MACHINE, +}; + +static int select_option(char option, unsigned int *fields) +{ + switch (option) { + case 'a': *fields |= FIELD_ALL; return 1; + case 's': *fields |= FIELD_SYSNAME; return 1; + case 'n': *fields |= FIELD_NODENAME; return 1; + case 'r': *fields |= FIELD_RELEASE; return 1; + case 'v': *fields |= FIELD_VERSION; return 1; + case 'm': *fields |= FIELD_MACHINE; return 1; + default: return 0; + } +} + +static void print_field(const char *value, int *first) +{ + if (!*first) { + (void) write(STDOUT_FILENO, " ", 1); + } + (void) write(STDOUT_FILENO, value, strlen(value)); + *first = 0; +} + +int main(int argc, char **argv) +{ + struct utsname identity; + unsigned int fields = 0; + int argument; + int first = 1; + + for (argument = 1; argument < argc; ++argument) { + const char *option = argv[argument]; + size_t index; + if (option[0] != '-' || option[1] == '\0') { + (void) write(STDERR_FILENO, "uname: invalid argument\n", + sizeof("uname: invalid argument\n") - 1); + return 1; + } + for (index = 1; option[index] != '\0'; ++index) { + if (!select_option(option[index], &fields)) { + (void) write(STDERR_FILENO, "uname: invalid option\n", + sizeof("uname: invalid option\n") - 1); + return 1; + } + } + } + if (fields == 0) { + fields = FIELD_SYSNAME; + } + if (uname(&identity) < 0) { + (void) write(STDERR_FILENO, "uname: system information unavailable\n", + sizeof("uname: system information unavailable\n") - 1); + return 1; + } + if ((fields & FIELD_SYSNAME) != 0) print_field(identity.sysname, &first); + if ((fields & FIELD_NODENAME) != 0) print_field(identity.nodename, &first); + if ((fields & FIELD_RELEASE) != 0) print_field(identity.release, &first); + if ((fields & FIELD_VERSION) != 0) print_field(identity.version, &first); + if ((fields & FIELD_MACHINE) != 0) print_field(identity.machine, &first); + (void) write(STDOUT_FILENO, "\n", 1); + return 0; +} diff --git a/examples/typephp-os/user/vmfault.c b/examples/typephp-os/user/vmfault.c index f1b1225c..06814395 100644 --- a/examples/typephp-os/user/vmfault.c +++ b/examples/typephp-os/user/vmfault.c @@ -2,7 +2,7 @@ int main(void) { - /* The resident shell is linked at 32 MiB. A command has a distinct CR3, + /* The resident shell is linked at 1 GiB. A command has a distinct CR3, * so this address must be unmapped even though the shell remains alive. */ volatile const uint8_t *shell_memory = (const uint8_t *) UINT64_C(0x40000000); return *shell_memory; diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index b5087351..e06e64cd 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -865,6 +865,25 @@ YAML); $this->assertSame(['/yaml/lib'], $this->compiler->getLinkPaths()); } + public function testNanoModePermitsTargetStaticLibraries(): void + { + $projectFile = $this->createProjectFile(<<<'YAML' +sources: + - main.php +link-libs: + - typephp-os +link-paths: + - build +YAML); + + $this->invokeMethod('parseProjectYaml', $projectFile); + $this->setPropertyValue('nanoMode', true); + $this->invokeMethod('applyCommandLineArguments'); + + $this->assertSame(['typephp-os'], $this->compiler->getLinkLibs()); + $this->assertSame([dirname($projectFile) . '/build'], $this->compiler->getLinkPaths()); + } + public function testParseProjectYamlFiltersIgnoredFilesFromReturnedSources(): void { $projectFile = $this->createProjectFile(<<<'YAML' diff --git a/src/Translator.php b/src/Translator.php index 16f0fc48..d7ed3168 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -597,9 +597,9 @@ class Translator extends Preprocessor if ($this->fullStatic) { $this->error('--nano already composes its runtime sources; --full-static is not applicable'); } - if ($this->linkLibs !== [] || $this->linkPaths !== []) { - $this->error('--nano does not permit external link libraries or library search paths'); - } + // Nano applications may consume target-owned static libraries. + // The final executable dependency audit remains the authority on + // whether those archives introduced an unsupported host import. } }