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
master
韩天峰 4 weeks ago
parent fc826df3d7
commit 2d052429ca
  1. 89
      examples/typephp-os/Makefile
  2. 36
      examples/typephp-os/README.md
  3. 11
      examples/typephp-os/ROADMAP.md
  4. 13
      examples/typephp-os/boot/boot.S
  5. 86
      examples/typephp-os/kernel/core/abi/libc.c
  6. 5
      examples/typephp-os/kernel/core/abi/typephp_os_abi.h
  7. 1
      examples/typephp-os/kernel/core/abi/typephp_os_syscall.h
  8. 1
      examples/typephp-os/kernel/core/abi/unimplemented.S
  9. 7
      examples/typephp-os/kernel/core/memory.c
  10. 35
      examples/typephp-os/kernel/core/process.c
  11. 12
      examples/typephp-os/kernel/fat16.php
  12. 31
      examples/typephp-os/tools/test-qemu.sh
  13. 15
      examples/typephp-os/user/README.md
  14. 1
      examples/typephp-os/user/include/sys/syscall.h
  15. 20
      examples/typephp-os/user/include/sys/utsname.h
  16. 6
      examples/typephp-os/user/libc.c
  17. 2
      examples/typephp-os/user/nano/hello.php
  18. 17
      examples/typephp-os/user/nano/hello.yml
  19. 36
      examples/typephp-os/user/runtime/README.md
  20. 0
      examples/typephp-os/user/runtime/crt0.S
  21. 7
      examples/typephp-os/user/runtime/host.cc
  22. 0
      examples/typephp-os/user/runtime/linker.ld
  23. 16
      examples/typephp-os/user/runtime/posix.c
  24. 0
      examples/typephp-os/user/runtime/syscall.S
  25. 4
      examples/typephp-os/user/sh.c
  26. 76
      examples/typephp-os/user/uname.c
  27. 2
      examples/typephp-os/user/vmfault.c
  28. 19
      phpunit/src/CompilerBaseApiTest.php
  29. 6
      src/Translator.php

@ -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)

@ -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

@ -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

@ -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

@ -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;

@ -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. */

@ -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,

@ -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

@ -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;

@ -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;
}

@ -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

@ -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}"

@ -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

@ -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

@ -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

@ -7,6 +7,7 @@
#include <sys/syscall.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/utsname.h>
#include <time.h>
#include <unistd.h>
@ -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);

@ -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()));

@ -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

@ -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.

@ -7,10 +7,14 @@
#include <unistd.h>
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<void *>(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");

@ -13,6 +13,10 @@
#include <typephp_os_syscall.h>
/* 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)

@ -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;

@ -0,0 +1,76 @@
#include <stddef.h>
#include <string.h>
#include <sys/utsname.h>
#include <unistd.h>
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;
}

@ -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;

@ -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'

@ -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.
}
}

Loading…
Cancel
Save