- Add objects section to project.yml for linking precompiled .o/.obj files - Implement getProjectObjectFiles method to retrieve precompiled objects - Add c-flags and asm-flags support in CompilerBase - Extend GCC backend to handle cflags and nativeflags configuration - Update compiler to include project object files in linking process - Add freestanding C++ allocation ABI implementation using Zend MM - Include example TypePHP OS with kernel entry point and hardware stubs - Add assembly files for boot sequence and kernel entry point - Implement basic libc compatibility layer for Zend bootstrap - Add try/catch macro replacement in generated code indentation - Create bridge between C++ kernel and PHP code - Add linker script for 64-bit kernel binary generationmaster
parent
0fda2fe4ab
commit
1cad314678
36 changed files with 2665 additions and 9 deletions
@ -0,0 +1 @@ |
||||
/build/ |
||||
@ -0,0 +1,45 @@ |
||||
BUILD := build
|
||||
OBJECT_DIR := $(BUILD)/objects
|
||||
BOOTSTRAP_OBJECT := $(OBJECT_DIR)/bootstrap.o
|
||||
PAYLOAD_ELF := $(BUILD)/kernel64.elf
|
||||
PAYLOAD_BIN := $(BUILD)/kernel64.bin
|
||||
PAYLOAD_OBJECT := $(OBJECT_DIR)/kernel64-payload.o
|
||||
KERNEL := $(BUILD)/typephp-os.elf
|
||||
|
||||
BOOTFLAGS := -m32 -O2 -ffreestanding -fno-pic -fno-pie \
|
||||
-fno-stack-protector -Wall -Wextra
|
||||
|
||||
.PHONY: all payload objects run test clean |
||||
|
||||
all: $(KERNEL) |
||||
|
||||
payload: objects |
||||
cd ../.. && ./bin/tpc.php --nano examples/typephp-os/project.yml
|
||||
|
||||
# Only the 32-bit bootstrap requires flags incompatible with the 64-bit Nano
|
||||
# payload. Ordinary .c/.cc/.S files stay in project.yml sources.
|
||||
objects: $(BOOTSTRAP_OBJECT) |
||||
|
||||
$(OBJECT_DIR): |
||||
mkdir -p $@
|
||||
|
||||
$(BOOTSTRAP_OBJECT): freestanding/boot.S | $(OBJECT_DIR) |
||||
gcc $(BOOTFLAGS) -c $< -o $@
|
||||
|
||||
$(KERNEL): payload |
||||
objcopy -O binary $(PAYLOAD_ELF) $(PAYLOAD_BIN)
|
||||
objcopy -I binary -O elf32-i386 -B i386 \
|
||||
--rename-section .data=.payload,alloc,load,readonly,data,contents \
|
||||
$(PAYLOAD_BIN) $(PAYLOAD_OBJECT)
|
||||
ld -m elf_i386 -T freestanding/linker.ld -o $@ \
|
||||
$(BOOTSTRAP_OBJECT) $(PAYLOAD_OBJECT)
|
||||
|
||||
run: all |
||||
qemu-system-x86_64 -m 128M -kernel $(KERNEL) \
|
||||
-display none -serial stdio -monitor none -no-reboot -no-shutdown
|
||||
|
||||
test: all |
||||
bash tools/test-qemu.sh $(KERNEL) $(BUILD)/qemu.log
|
||||
|
||||
clean: |
||||
rm -rf $(BUILD)
|
||||
@ -0,0 +1,118 @@ |
||||
# TypePHP freestanding OS experiment |
||||
|
||||
This is a long-running experimental x86_64 TypePHP operating system. It boots |
||||
under QEMU and runs an ordinary TypePHP Nano program without linking hosted |
||||
PHP, libc, or libstdc++. TypePHP implements the startup self-check, a custom |
||||
Zend class, the prime-number demo, and the resident application loop. Small C |
||||
and assembly layers provide the machine bootstrap and current kernel services. |
||||
|
||||
The architectural rules and staged plan are maintained in |
||||
[ROADMAP.md](ROADMAP.md). |
||||
|
||||
## Architecture |
||||
|
||||
There is no kernel-specific tpc mode or reduced PHP Nano profile. The 64-bit |
||||
payload is compiled with the normal command: |
||||
|
||||
```shell |
||||
./bin/tpc.php --nano examples/typephp-os/project.yml |
||||
``` |
||||
|
||||
This composes the complete php-nano and PHPX source manifests. The TypePHP OS |
||||
project owns the freestanding boundary under `freestanding/abi`: implemented |
||||
C/POSIX and C++ ABI functions live there, while APIs required for linking but |
||||
not implemented by the kernel are exported as panic stubs. Consequently an |
||||
unsupported operation fails immediately with its ABI symbol instead of |
||||
silently returning fabricated data. Filesystem APIs are the next subsystem to |
||||
replace with real implementations; sockets are outside the current scope. |
||||
|
||||
Cross-project portability uses general feature switches only: |
||||
|
||||
- `PHP_NANO_NO_LIBC` asks the embedding host to provide Nano's clock, sleep, |
||||
and entropy hooks; |
||||
- `PHPX_NO_EXCEPTION` routes PHPX exception propagation to the host abort hook; |
||||
- `PHPX_NO_RTTI` selects PHPX's non-RTTI checked cast policy; |
||||
- `TYPEPHP_NO_MAIN` lets an embedding host provide the process/kernel entry. |
||||
|
||||
None of these switches refers to TypePHP OS or to a kernel build. Ordinary |
||||
Nano and PHPX builds preserve their hosted defaults. |
||||
|
||||
The payload uses PHP's original Zend allocator, GC, strings, HashTables, |
||||
objects, classes, exceptions, and built-in extension registration. Generated |
||||
projects and built-ins are registered through their ordinary MINIT paths; the |
||||
only excluded capability is dynamic PHP execution through ZendVM. |
||||
|
||||
## Build and run |
||||
|
||||
Required host tools are TypePHP's PHP/Composer dependencies, GCC/G++, GNU |
||||
binutils, GNU make, and `qemu-system-x86_64`. |
||||
|
||||
From this directory, build and boot with: |
||||
|
||||
```shell |
||||
make |
||||
make run |
||||
``` |
||||
|
||||
The build produces two useful files: |
||||
|
||||
- `build/kernel64.elf`: the 64-bit TypePHP + ordinary Nano payload produced by |
||||
tpc; |
||||
- `build/typephp-os.elf`: the final Multiboot kernel accepted by QEMU. |
||||
|
||||
Run the automated serial-output smoke test with: |
||||
|
||||
```shell |
||||
make test |
||||
``` |
||||
|
||||
The Makefile compiles only the 32-bit Multiboot bootstrap externally because |
||||
its `-m32` ABI cannot participate in the 64-bit payload link. All ordinary |
||||
64-bit `.c`, `.cc`, and `.S` files remain in `project.yml` and use tpc's generic |
||||
`c-flags`, `cxx-flags`, and `asm-flags`. Generic same-ABI prebuilt objects can |
||||
be supplied with `objects`; the architecture-changing bootstrap is instead |
||||
combined during the final packaging link. |
||||
|
||||
After tpc emits `kernel64.elf`, the Makefile uses `objcopy` to turn the payload |
||||
into a raw binary and then an ELF32 data object. GNU ld combines that object |
||||
with the 32-bit bootstrap. The bootstrap is loaded through Multiboot v1, |
||||
identity-maps the first GiB, enters x86_64 long mode, and transfers control to |
||||
the payload linked at 2 MiB. |
||||
|
||||
To invoke QEMU manually: |
||||
|
||||
```shell |
||||
qemu-system-x86_64 -m 128M \ |
||||
-kernel examples/typephp-os/build/typephp-os.elf \ |
||||
-display none -serial stdio -monitor none -no-reboot -no-shutdown |
||||
``` |
||||
|
||||
Press `Ctrl+A`, then `X`, to leave headless QEMU. |
||||
|
||||
## Current capabilities |
||||
|
||||
The bootstrap passes the Multiboot memory map to the 64-bit kernel. The |
||||
physical layer reserves the complete kernel image, selects mapped usable RAM, |
||||
and installs the remaining arena behind the project-owned `posix_memalign()`. |
||||
Upstream `zend_alloc` then obtains and subdivides aligned 2 MiB chunks. C++ |
||||
global `new` and `delete`, including `std::vector` allocations, use Zend MM. |
||||
|
||||
The QEMU smoke test currently verifies: |
||||
|
||||
- 64-bit TypePHP scalar and control-flow execution; |
||||
- real Zend allocation, GC, string, array, object, class, and exception data; |
||||
- PHPX `Variant`, `Str`, `Array`, custom classes, and a typed |
||||
`std::vector<int>`; |
||||
- built-in date handling and `sleep()` through Zend Bridge; |
||||
- a TypePHP prime calculation for 0–100; |
||||
- a resident loop that prints the RTC-derived UTC time and |
||||
`Hello TypePHP-OS!` every two seconds. |
||||
|
||||
The 64-bit payload currently occupies about 7 MiB, so the first 16 MiB is |
||||
reserved before physical memory is handed to Zend MM. Returning entire Zend |
||||
chunks to a future physical page allocator remains later work. |
||||
|
||||
Filesystem ABI entries already link as explicit panic stubs. The next |
||||
milestone will replace them with an in-kernel filesystem and make PHP's local |
||||
file stream operations real. Network sockets, dynamic module loading, |
||||
`include`/`require`/`eval`, and external process execution remain unavailable. |
||||
@ -0,0 +1,49 @@ |
||||
# TypePHP OS roadmap |
||||
|
||||
This directory is a long-running experiment. Every milestone must remain |
||||
bootable in QEMU and must not change ordinary TypePHP, PHPX, or PHP Nano |
||||
behavior. |
||||
|
||||
## Architecture rules |
||||
|
||||
- The project is an ordinary `tpc --nano` consumer. tpc has no kernel option, |
||||
and PHP Nano has no kernel source profile. |
||||
- PHP and Composer remain build-time tools; the kernel never links `libphp`. |
||||
- Keep copied php-src C/H files unchanged and compile the complete Nano source |
||||
manifest. TypePHP OS owns the required libc/POSIX and C++ ABI boundary. |
||||
- Missing ABI operations must be linkable panic stubs. Replace each stub with |
||||
a real kernel service before exposing the corresponding PHP capability. |
||||
- Cross-project changes must use generally useful portability contracts such |
||||
as `PHP_NANO_NO_LIBC`, `PHPX_NO_EXCEPTION`, and `PHPX_NO_RTTI`; product-specific |
||||
conditionals are not allowed in PHP Nano or PHPX. |
||||
- Ordinary same-ABI source files and flags belong in `project.yml`. Translation |
||||
units that require incompatible per-file options are built by Make and may |
||||
be linked through tpc's generic `objects` facility when their ABI matches. |
||||
Architecture packaging and ELF/binary conversion happen after tpc emits the |
||||
64-bit ELF. |
||||
- Every completed milestone must pass the serial-output QEMU smoke test and |
||||
leave the 64-bit payload with no undefined symbols. |
||||
|
||||
## Milestones |
||||
|
||||
1. **Boot and scalar AOT — complete.** Multiboot v1 bootstrap, x86_64 long |
||||
mode, VGA/COM1 output, TypePHP scalars, functions, and control flow. |
||||
2. **Memory and Zend containers — complete.** Physical arena, TypePHP OS ABI |
||||
layer, original `zend_alloc`, `zend_gc`, `zend_string`, `zend_hash`, zval |
||||
destruction, and generated TypePHP arrays/strings through PHPX. |
||||
3. **Zend object model — complete for the current demo.** Original object |
||||
store, class entries, inheritance, interfaces, handlers, PHP exception |
||||
classes, generated user classes, construction, properties, and destruction. |
||||
4. **Time service — complete.** CMOS wall clock, monotonic host hook, built-in |
||||
date formatting, and the standard extension's `sleep()` path. |
||||
5. **Filesystem — next.** Physical block device, a small filesystem, file |
||||
descriptors, directory/stat operations, and PHP's local file stream API. |
||||
6. **Native Class memory.** Exercise Wren GC through Zend MM and verify tracing |
||||
of PHPX fields under sustained allocation. |
||||
7. **Kernel services.** Interrupt-driven timer, keyboard, physical-page |
||||
reclamation, and a capability-oriented native API. |
||||
8. **Packaging and CI.** Automate the two-stage ELF32/ELF64 build and QEMU boot |
||||
smoke test in GitHub Actions. |
||||
|
||||
Later architectures may provide different bootstraps and host ABI adapters. |
||||
Generated TypePHP code and PHPX values remain 64-bit on every target. |
||||
@ -0,0 +1,203 @@ |
||||
/*
|
||||
+----------------------------------------------------------------------+ |
||||
| TypePHP OS | |
||||
+----------------------------------------------------------------------+ |
||||
| Freestanding C++ allocation ABI backed by Zend MM. | |
||||
| SPDX-License-Identifier: BSD-3-Clause | |
||||
+----------------------------------------------------------------------+ |
||||
*/ |
||||
|
||||
extern "C" { |
||||
#include <zend_alloc.h> |
||||
#include "typephp_os_abi.h" |
||||
} |
||||
|
||||
#include <cstddef> |
||||
#include <cstdint> |
||||
#include <new> |
||||
|
||||
extern "C" { |
||||
void *__dso_handle = &__dso_handle; |
||||
|
||||
int __cxa_atexit(void (*destructor)(void *), void *argument, void *dso) |
||||
{ |
||||
(void) destructor; |
||||
(void) argument; |
||||
(void) dso; |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
namespace { |
||||
|
||||
void *zend_allocate(std::size_t size) |
||||
{ |
||||
void *memory = emalloc(size == 0 ? 1 : size); |
||||
if (memory == nullptr) { |
||||
typephp_os_panic("C++ allocation failed"); |
||||
} |
||||
return memory; |
||||
} |
||||
|
||||
void *zend_allocate_aligned(std::size_t size, std::size_t alignment) |
||||
{ |
||||
if (alignment <= alignof(std::max_align_t)) { |
||||
return zend_allocate(size); |
||||
} |
||||
const std::size_t extra = alignment - 1 + sizeof(void *); |
||||
if (size > static_cast<std::size_t>(-1) - extra) { |
||||
typephp_os_panic("C++ aligned allocation overflow"); |
||||
} |
||||
void *base = zend_allocate(size + extra); |
||||
const auto start = reinterpret_cast<std::uintptr_t>(base) + sizeof(void *); |
||||
const auto aligned = (start + alignment - 1) & ~(static_cast<std::uintptr_t>(alignment) - 1); |
||||
void **result = reinterpret_cast<void **>(aligned); |
||||
result[-1] = base; |
||||
return result; |
||||
} |
||||
|
||||
void zend_free_aligned(void *memory, std::size_t alignment) noexcept |
||||
{ |
||||
if (memory == nullptr) { |
||||
return; |
||||
} |
||||
if (alignment <= alignof(std::max_align_t)) { |
||||
efree(memory); |
||||
return; |
||||
} |
||||
efree(reinterpret_cast<void **>(memory)[-1]); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
void *operator new(std::size_t size) |
||||
{ |
||||
return zend_allocate(size); |
||||
} |
||||
|
||||
void *operator new[](std::size_t size) |
||||
{ |
||||
return zend_allocate(size); |
||||
} |
||||
|
||||
void *operator new(std::size_t size, const std::nothrow_t &) noexcept |
||||
{ |
||||
return zend_allocate(size); |
||||
} |
||||
|
||||
void *operator new[](std::size_t size, const std::nothrow_t &) noexcept |
||||
{ |
||||
return zend_allocate(size); |
||||
} |
||||
|
||||
void operator delete(void *memory) noexcept |
||||
{ |
||||
if (memory != nullptr) { |
||||
efree(memory); |
||||
} |
||||
} |
||||
|
||||
void operator delete[](void *memory) noexcept |
||||
{ |
||||
if (memory != nullptr) { |
||||
efree(memory); |
||||
} |
||||
} |
||||
|
||||
void operator delete(void *memory, std::size_t) noexcept |
||||
{ |
||||
::operator delete(memory); |
||||
} |
||||
|
||||
void operator delete[](void *memory, std::size_t) noexcept |
||||
{ |
||||
::operator delete[](memory); |
||||
} |
||||
|
||||
void operator delete(void *memory, const std::nothrow_t &) noexcept |
||||
{ |
||||
::operator delete(memory); |
||||
} |
||||
|
||||
void operator delete[](void *memory, const std::nothrow_t &) noexcept |
||||
{ |
||||
::operator delete[](memory); |
||||
} |
||||
|
||||
void *operator new(std::size_t size, std::align_val_t alignment) |
||||
{ |
||||
return zend_allocate_aligned(size, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
void *operator new[](std::size_t size, std::align_val_t alignment) |
||||
{ |
||||
return zend_allocate_aligned(size, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
void *operator new( |
||||
std::size_t size, std::align_val_t alignment, const std::nothrow_t &) noexcept |
||||
{ |
||||
return zend_allocate_aligned(size, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
void *operator new[]( |
||||
std::size_t size, std::align_val_t alignment, const std::nothrow_t &) noexcept |
||||
{ |
||||
return zend_allocate_aligned(size, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
void operator delete(void *memory, std::align_val_t alignment) noexcept |
||||
{ |
||||
zend_free_aligned(memory, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
void operator delete[](void *memory, std::align_val_t alignment) noexcept |
||||
{ |
||||
zend_free_aligned(memory, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
void operator delete(void *memory, std::size_t, std::align_val_t alignment) noexcept |
||||
{ |
||||
zend_free_aligned(memory, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
void operator delete[](void *memory, std::size_t, std::align_val_t alignment) noexcept |
||||
{ |
||||
zend_free_aligned(memory, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
void operator delete( |
||||
void *memory, std::align_val_t alignment, const std::nothrow_t &) noexcept |
||||
{ |
||||
zend_free_aligned(memory, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
void operator delete[]( |
||||
void *memory, std::align_val_t alignment, const std::nothrow_t &) noexcept |
||||
{ |
||||
zend_free_aligned(memory, static_cast<std::size_t>(alignment)); |
||||
} |
||||
|
||||
/* libstdc++'s header-only containers call these ABI hooks on impossible or
|
||||
* allocation-failure paths. The kernel deliberately does not link the |
||||
* hosted libstdc++ runtime, so terminate through its panic boundary. */ |
||||
namespace std { |
||||
|
||||
const nothrow_t nothrow{}; |
||||
|
||||
[[noreturn]] void __throw_length_error(const char *) |
||||
{ |
||||
typephp_os_panic("C++ container length error"); |
||||
} |
||||
|
||||
[[noreturn]] void __throw_bad_alloc() |
||||
{ |
||||
typephp_os_panic("C++ allocation failed"); |
||||
} |
||||
|
||||
[[noreturn]] void __throw_bad_array_new_length() |
||||
{ |
||||
typephp_os_panic("C++ array allocation failed"); |
||||
} |
||||
|
||||
} // namespace std
|
||||
@ -0,0 +1,834 @@ |
||||
/*
|
||||
+----------------------------------------------------------------------+ |
||||
| TypePHP OS | |
||||
+----------------------------------------------------------------------+ |
||||
| Small freestanding C/POSIX compatibility layer for Zend bootstrap. | |
||||
| SPDX-License-Identifier: BSD-3-Clause | |
||||
+----------------------------------------------------------------------+ |
||||
|
||||
This is not a replacement for zend_alloc. It only supplies the host calls |
||||
used by zend_alloc to acquire its aligned 2 MiB chunks, plus the C memory |
||||
primitives emitted by C/C++ compilers. User allocations continue through |
||||
emalloc/efree and therefore retain the original Zend allocator semantics. |
||||
*/ |
||||
|
||||
#include "typephp_os_abi.h" |
||||
|
||||
#include <stdarg.h> |
||||
#include <locale.h> |
||||
#include <setjmp.h> |
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
#include <inttypes.h> |
||||
|
||||
typedef struct typephp_os_block { |
||||
size_t size; |
||||
} typephp_os_block; |
||||
|
||||
static uintptr_t arena_cursor; |
||||
static uintptr_t arena_end; |
||||
static int typephp_os_errno; |
||||
|
||||
/* 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; |
||||
|
||||
size_t strlen(const char *string); |
||||
void *malloc(size_t size); |
||||
int posix_memalign(void **result, size_t alignment, size_t size); |
||||
int vsnprintf(char *buffer, size_t size, const char *format, va_list args); |
||||
|
||||
int *__errno_location(void) |
||||
{ |
||||
return &typephp_os_errno; |
||||
} |
||||
|
||||
void __assert_fail( |
||||
const char *assertion, |
||||
const char *file, |
||||
unsigned int line, |
||||
const char *function) |
||||
{ |
||||
(void) file; |
||||
(void) line; |
||||
(void) function; |
||||
typephp_os_panic(assertion); |
||||
} |
||||
|
||||
static uintptr_t align_up(uintptr_t value, size_t alignment) |
||||
{ |
||||
return (value + alignment - 1u) & ~((uintptr_t) alignment - 1u); |
||||
} |
||||
|
||||
static int is_power_of_two(size_t value) |
||||
{ |
||||
return value != 0 && (value & (value - 1u)) == 0; |
||||
} |
||||
|
||||
void typephp_os_memory_init(void *address, size_t size) |
||||
{ |
||||
const uintptr_t begin = (uintptr_t) address; |
||||
if (size > UINTPTR_MAX - begin) { |
||||
arena_cursor = 0; |
||||
arena_end = 0; |
||||
return; |
||||
} |
||||
arena_cursor = begin; |
||||
arena_end = begin + size; |
||||
} |
||||
|
||||
size_t typephp_os_memory_available(void) |
||||
{ |
||||
return arena_end >= arena_cursor ? (size_t) (arena_end - arena_cursor) : 0; |
||||
} |
||||
|
||||
__attribute__((weak)) void typephp_os_write(const char *data, size_t size) |
||||
{ |
||||
(void) data; |
||||
(void) size; |
||||
} |
||||
|
||||
__attribute__((weak, noreturn)) void typephp_os_panic(const char *message) |
||||
{ |
||||
typephp_os_write(message, strlen(message)); |
||||
for (;;) { |
||||
__asm__ volatile("cli; hlt"); |
||||
} |
||||
} |
||||
|
||||
void phpx_no_exception_abort(const char *message) |
||||
{ |
||||
typephp_os_panic(message); |
||||
} |
||||
|
||||
int php_nano_host_random_bytes(void *bytes, size_t size) |
||||
{ |
||||
static uint64_t state = UINT64_C(0x9e3779b97f4a7c15); |
||||
unsigned char *output = (unsigned char *) bytes; |
||||
while (size-- != 0) { |
||||
state ^= state << 13; |
||||
state ^= state >> 7; |
||||
state ^= state << 17; |
||||
*output++ = (unsigned char) state; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
uint64_t php_nano_host_random_seed(void) |
||||
{ |
||||
uint64_t seed = 0; |
||||
(void) php_nano_host_random_bytes(&seed, sizeof(seed)); |
||||
return seed; |
||||
} |
||||
|
||||
void *memset(void *destination, int value, size_t size) |
||||
{ |
||||
unsigned char *output = (unsigned char *) destination; |
||||
while (size-- != 0) { |
||||
*output++ = (unsigned char) value; |
||||
} |
||||
return destination; |
||||
} |
||||
|
||||
void *memcpy(void *destination, const void *source, size_t size) |
||||
{ |
||||
unsigned char *output = (unsigned char *) destination; |
||||
const unsigned char *input = (const unsigned char *) source; |
||||
while (size-- != 0) { |
||||
*output++ = *input++; |
||||
} |
||||
return destination; |
||||
} |
||||
|
||||
void *memmove(void *destination, const void *source, size_t size) |
||||
{ |
||||
unsigned char *output = (unsigned char *) destination; |
||||
const unsigned char *input = (const unsigned char *) source; |
||||
if (output <= input || output >= input + size) { |
||||
return memcpy(destination, source, size); |
||||
} |
||||
output += size; |
||||
input += size; |
||||
while (size-- != 0) { |
||||
*--output = *--input; |
||||
} |
||||
return destination; |
||||
} |
||||
|
||||
int memcmp(const void *left, const void *right, size_t size) |
||||
{ |
||||
const unsigned char *a = (const unsigned char *) left; |
||||
const unsigned char *b = (const unsigned char *) right; |
||||
while (size-- != 0) { |
||||
if (*a != *b) { |
||||
return *a < *b ? -1 : 1; |
||||
} |
||||
++a; |
||||
++b; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
size_t strlen(const char *string) |
||||
{ |
||||
const char *end = string; |
||||
while (*end != '\0') { |
||||
++end; |
||||
} |
||||
return (size_t) (end - string); |
||||
} |
||||
|
||||
char *strdup(const char *string) |
||||
{ |
||||
const size_t size = strlen(string) + 1; |
||||
char *copy = (char *) malloc(size); |
||||
return copy ? (char *) memcpy(copy, string, size) : 0; |
||||
} |
||||
|
||||
int strcmp(const char *left, const char *right) |
||||
{ |
||||
while (*left != '\0' && *left == *right) { |
||||
++left; |
||||
++right; |
||||
} |
||||
return (unsigned char) *left - (unsigned char) *right; |
||||
} |
||||
|
||||
int strncmp(const char *left, const char *right, size_t size) |
||||
{ |
||||
while (size-- != 0) { |
||||
const unsigned char a = (unsigned char) *left++; |
||||
const unsigned char b = (unsigned char) *right++; |
||||
if (a != b) { |
||||
return (int) a - (int) b; |
||||
} |
||||
if (a == 0) { |
||||
return 0; |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
void *memchr(const void *memory, int character, size_t size) |
||||
{ |
||||
const unsigned char *cursor = (const unsigned char *) memory; |
||||
const unsigned char value = (unsigned char) character; |
||||
while (size-- != 0) { |
||||
if (*cursor == value) { |
||||
return (void *) cursor; |
||||
} |
||||
++cursor; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
char *strchr(const char *string, int character) |
||||
{ |
||||
do { |
||||
if (*string == (char) character) { |
||||
return (char *) string; |
||||
} |
||||
} while (*string++ != '\0'); |
||||
return 0; |
||||
} |
||||
|
||||
char *strrchr(const char *string, int character) |
||||
{ |
||||
const char *match = 0; |
||||
do { |
||||
if (*string == (char) character) { |
||||
match = string; |
||||
} |
||||
} while (*string++ != '\0'); |
||||
return (char *) match; |
||||
} |
||||
|
||||
char *strstr(const char *haystack, const char *needle) |
||||
{ |
||||
const size_t needle_size = strlen(needle); |
||||
if (needle_size == 0) { |
||||
return (char *) haystack; |
||||
} |
||||
while (*haystack != '\0') { |
||||
if (*haystack == *needle && strncmp(haystack, needle, needle_size) == 0) { |
||||
return (char *) haystack; |
||||
} |
||||
++haystack; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
static unsigned char ascii_lower(unsigned char character) |
||||
{ |
||||
return character >= 'A' && character <= 'Z' |
||||
? (unsigned char) (character + ('a' - 'A')) |
||||
: character; |
||||
} |
||||
|
||||
int strcasecmp(const char *left, const char *right) |
||||
{ |
||||
while (*left != '\0' && ascii_lower((unsigned char) *left) == ascii_lower((unsigned char) *right)) { |
||||
++left; |
||||
++right; |
||||
} |
||||
return (int) ascii_lower((unsigned char) *left) - (int) ascii_lower((unsigned char) *right); |
||||
} |
||||
|
||||
int strncasecmp(const char *left, const char *right, size_t size) |
||||
{ |
||||
while (size-- != 0) { |
||||
const unsigned char a = ascii_lower((unsigned char) *left++); |
||||
const unsigned char b = ascii_lower((unsigned char) *right++); |
||||
if (a != b) { |
||||
return (int) a - (int) b; |
||||
} |
||||
if (a == 0) { |
||||
return 0; |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
int isascii(int character) |
||||
{ |
||||
return (character & ~0x7f) == 0; |
||||
} |
||||
|
||||
int isdigit(int character) |
||||
{ |
||||
return character >= '0' && character <= '9'; |
||||
} |
||||
|
||||
int islower(int character) |
||||
{ |
||||
return character >= 'a' && character <= 'z'; |
||||
} |
||||
|
||||
int isupper(int character) |
||||
{ |
||||
return character >= 'A' && character <= 'Z'; |
||||
} |
||||
|
||||
int isalpha(int character) |
||||
{ |
||||
return islower(character) || isupper(character); |
||||
} |
||||
|
||||
int isalnum(int character) |
||||
{ |
||||
return isalpha(character) || isdigit(character); |
||||
} |
||||
|
||||
int isspace(int character) |
||||
{ |
||||
return character == ' ' || (character >= '\t' && character <= '\r'); |
||||
} |
||||
|
||||
int isblank(int character) |
||||
{ |
||||
return character == ' ' || character == '\t'; |
||||
} |
||||
|
||||
int iscntrl(int character) |
||||
{ |
||||
return (character >= 0 && character < 0x20) || character == 0x7f; |
||||
} |
||||
|
||||
int isgraph(int character) |
||||
{ |
||||
return character > 0x20 && character < 0x7f; |
||||
} |
||||
|
||||
int isprint(int character) |
||||
{ |
||||
return character >= 0x20 && character < 0x7f; |
||||
} |
||||
|
||||
int ispunct(int character) |
||||
{ |
||||
return isgraph(character) && !isalnum(character); |
||||
} |
||||
|
||||
size_t __ctype_get_mb_cur_max(void) |
||||
{ |
||||
return 1; |
||||
} |
||||
|
||||
int isxdigit(int character) |
||||
{ |
||||
return isdigit(character) |
||||
|| (character >= 'a' && character <= 'f') |
||||
|| (character >= 'A' && character <= 'F'); |
||||
} |
||||
|
||||
int tolower(int character) |
||||
{ |
||||
return isupper(character) ? character + ('a' - 'A') : character; |
||||
} |
||||
|
||||
int toupper(int character) |
||||
{ |
||||
return islower(character) ? character - ('a' - 'A') : character; |
||||
} |
||||
|
||||
struct lconv *localeconv(void) |
||||
{ |
||||
static struct lconv value = { |
||||
.decimal_point = ".", |
||||
.thousands_sep = "", |
||||
.grouping = "", |
||||
}; |
||||
return &value; |
||||
} |
||||
|
||||
char *setlocale(int category, const char *locale) |
||||
{ |
||||
static char c_locale[] = "C"; |
||||
(void) category; |
||||
if (locale == 0 || strcmp(locale, "C") == 0 || strcmp(locale, "POSIX") == 0) { |
||||
return c_locale; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
char *getcwd(char *buffer, size_t size) |
||||
{ |
||||
if (buffer == 0 || size < 2) { |
||||
typephp_os_errno = 34; /* ERANGE */ |
||||
return 0; |
||||
} |
||||
buffer[0] = '/'; |
||||
buffer[1] = '\0'; |
||||
return buffer; |
||||
} |
||||
|
||||
char *getenv(const char *name) |
||||
{ |
||||
/* The kernel process starts with an intentionally empty environment. */ |
||||
(void) name; |
||||
return 0; |
||||
} |
||||
|
||||
int strcoll(const char *left, const char *right) |
||||
{ |
||||
return strcmp(left, right); |
||||
} |
||||
|
||||
char *strcat(char *destination, const char *source) |
||||
{ |
||||
char *result = destination; |
||||
destination += strlen(destination); |
||||
while ((*destination++ = *source++) != '\0') { |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
char *strpbrk(const char *string, const char *characters) |
||||
{ |
||||
for (; *string != '\0'; ++string) { |
||||
if (strchr(characters, *string) != 0) { |
||||
return (char *) string; |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
static int digit_value(int character) |
||||
{ |
||||
if (character >= '0' && character <= '9') { |
||||
return character - '0'; |
||||
} |
||||
if (character >= 'a' && character <= 'z') { |
||||
return character - 'a' + 10; |
||||
} |
||||
if (character >= 'A' && character <= 'Z') { |
||||
return character - 'A' + 10; |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
unsigned long long strtoull(const char *string, char **end, int base) |
||||
{ |
||||
while (isspace((unsigned char) *string)) { |
||||
++string; |
||||
} |
||||
if (*string == '+') { |
||||
++string; |
||||
} |
||||
if ((base == 0 || base == 16) && string[0] == '0' |
||||
&& (string[1] == 'x' || string[1] == 'X')) { |
||||
base = 16; |
||||
string += 2; |
||||
} else if (base == 0) { |
||||
base = string[0] == '0' ? 8 : 10; |
||||
} |
||||
const char *cursor = string; |
||||
unsigned long long value = 0; |
||||
int digit; |
||||
while ((digit = digit_value((unsigned char) *cursor)) >= 0 && digit < base) { |
||||
value = value * (unsigned int) base + (unsigned int) digit; |
||||
++cursor; |
||||
} |
||||
if (end != 0) { |
||||
*end = (char *) cursor; |
||||
} |
||||
return value; |
||||
} |
||||
|
||||
long long strtoll(const char *string, char **end, int base) |
||||
{ |
||||
while (isspace((unsigned char) *string)) { |
||||
++string; |
||||
} |
||||
const int negative = *string == '-'; |
||||
if (*string == '-' || *string == '+') { |
||||
++string; |
||||
} |
||||
char *parsed_end; |
||||
const unsigned long long value = strtoull(string, &parsed_end, base); |
||||
if (end != 0) { |
||||
*end = parsed_end; |
||||
} |
||||
return negative ? -(long long) value : (long long) value; |
||||
} |
||||
|
||||
long strtol(const char *string, char **end, int base) |
||||
{ |
||||
return (long) strtoll(string, end, base); |
||||
} |
||||
|
||||
int atoi(const char *string) |
||||
{ |
||||
return (int) strtol(string, 0, 10); |
||||
} |
||||
|
||||
long long atoll(const char *string) |
||||
{ |
||||
return strtoll(string, 0, 10); |
||||
} |
||||
|
||||
intmax_t imaxabs(intmax_t value) |
||||
{ |
||||
return value < 0 ? -value : value; |
||||
} |
||||
|
||||
double strtod(const char *string, char **end) |
||||
{ |
||||
while (isspace((unsigned char) *string)) { |
||||
++string; |
||||
} |
||||
const int negative = *string == '-'; |
||||
if (*string == '-' || *string == '+') { |
||||
++string; |
||||
} |
||||
double value = 0.0; |
||||
while (isdigit((unsigned char) *string)) { |
||||
value = value * 10.0 + (double) (*string++ - '0'); |
||||
} |
||||
if (*string == '.') { |
||||
double place = 0.1; |
||||
++string; |
||||
while (isdigit((unsigned char) *string)) { |
||||
value += (double) (*string++ - '0') * place; |
||||
place *= 0.1; |
||||
} |
||||
} |
||||
if (end != 0) { |
||||
*end = (char *) string; |
||||
} |
||||
return negative ? -value : value; |
||||
} |
||||
|
||||
void qsort(void *base, size_t count, size_t width, int (*compare)(const void *, const void *)) |
||||
{ |
||||
unsigned char *bytes = (unsigned char *) base; |
||||
for (size_t index = 1; index < count; ++index) { |
||||
size_t current = index; |
||||
while (current != 0 |
||||
&& compare(bytes + (current - 1) * width, bytes + current * width) > 0) { |
||||
for (size_t byte = 0; byte < width; ++byte) { |
||||
unsigned char value = bytes[(current - 1) * width + byte]; |
||||
bytes[(current - 1) * width + byte] = bytes[current * width + byte]; |
||||
bytes[current * width + byte] = value; |
||||
} |
||||
--current; |
||||
} |
||||
} |
||||
} |
||||
|
||||
int fflush(void *stream) |
||||
{ |
||||
(void) stream; |
||||
return 0; |
||||
} |
||||
|
||||
int setvbuf(void *stream, char *buffer, int mode, size_t size) |
||||
{ |
||||
(void) stream; |
||||
(void) buffer; |
||||
(void) mode; |
||||
(void) size; |
||||
return 0; |
||||
} |
||||
|
||||
int vprintf(const char *format, va_list args) |
||||
{ |
||||
char buffer[1024]; |
||||
const int written = vsnprintf(buffer, sizeof(buffer), format, args); |
||||
if (written > 0) { |
||||
typephp_os_write(buffer, (size_t) written < sizeof(buffer) |
||||
? (size_t) written : sizeof(buffer) - 1); |
||||
} |
||||
return written; |
||||
} |
||||
|
||||
int printf(const char *format, ...) |
||||
{ |
||||
va_list args; |
||||
va_start(args, format); |
||||
const int written = vprintf(format, args); |
||||
va_end(args); |
||||
return written; |
||||
} |
||||
|
||||
int _setjmp(jmp_buf environment) |
||||
{ |
||||
(void) environment; |
||||
return 0; |
||||
} |
||||
|
||||
double pow(double base, double exponent) |
||||
{ |
||||
long power = (long) exponent; |
||||
if ((double) power != exponent) { |
||||
return 0.0; |
||||
} |
||||
double result = 1.0; |
||||
unsigned long magnitude = power < 0 ? (unsigned long) (-power) : (unsigned long) power; |
||||
while (magnitude != 0) { |
||||
if ((magnitude & 1u) != 0) { |
||||
result *= base; |
||||
} |
||||
base *= base; |
||||
magnitude >>= 1u; |
||||
} |
||||
return power < 0 ? 1.0 / result : result; |
||||
} |
||||
|
||||
double fmod(double value, double divisor) |
||||
{ |
||||
if (divisor == 0.0) { |
||||
return 0.0 / 0.0; |
||||
} |
||||
double quotient = value / divisor; |
||||
if (quotient >= 0.0) { |
||||
quotient = (double) (unsigned long) quotient; |
||||
} else { |
||||
quotient = (double) (long) quotient; |
||||
} |
||||
return value - quotient * divisor; |
||||
} |
||||
|
||||
double ceil(double value) |
||||
{ |
||||
long integral = (long) value; |
||||
return value > (double) integral ? (double) integral + 1.0 : (double) integral; |
||||
} |
||||
|
||||
double floor(double value) |
||||
{ |
||||
long integral = (long) value; |
||||
return value < (double) integral ? (double) integral - 1.0 : (double) integral; |
||||
} |
||||
|
||||
double trunc(double value) |
||||
{ |
||||
return (double) (long) value; |
||||
} |
||||
|
||||
double round(double value) |
||||
{ |
||||
return value < 0.0 ? ceil(value - 0.5) : floor(value + 0.5); |
||||
} |
||||
|
||||
double fabs(double value) |
||||
{ |
||||
return value < 0.0 ? -value : value; |
||||
} |
||||
|
||||
int abs(int value) |
||||
{ |
||||
return value < 0 ? -value : value; |
||||
} |
||||
|
||||
long long llabs(long long value) |
||||
{ |
||||
return value < 0 ? -value : value; |
||||
} |
||||
|
||||
int ap_php_vsnprintf(char *buffer, size_t size, const char *format, va_list args); |
||||
|
||||
int vsnprintf(char *buffer, size_t size, const char *format, va_list args) |
||||
{ |
||||
return ap_php_vsnprintf(buffer, size, format, args); |
||||
} |
||||
|
||||
int snprintf(char *buffer, size_t size, const char *format, ...) |
||||
{ |
||||
va_list args; |
||||
va_start(args, format); |
||||
const int result = ap_php_vsnprintf(buffer, size, format, args); |
||||
va_end(args); |
||||
return result; |
||||
} |
||||
|
||||
int __snprintf_chk(char *buffer, size_t size, int flag, size_t buffer_size, const char *format, ...) |
||||
{ |
||||
(void) flag; |
||||
(void) buffer_size; |
||||
va_list args; |
||||
va_start(args, format); |
||||
const int result = ap_php_vsnprintf(buffer, size, format, args); |
||||
va_end(args); |
||||
return result; |
||||
} |
||||
|
||||
__attribute__((noreturn)) void __longjmp_chk(void *environment, int value) |
||||
{ |
||||
(void) environment; |
||||
(void) value; |
||||
typephp_os_panic("zend_bailout"); |
||||
} |
||||
|
||||
__attribute__((noreturn)) void longjmp(jmp_buf environment, int value) |
||||
{ |
||||
(void) environment; |
||||
(void) value; |
||||
typephp_os_panic("zend_bailout"); |
||||
} |
||||
|
||||
int fputs(const char *string, void *stream) |
||||
{ |
||||
(void) stream; |
||||
typephp_os_write(string, strlen(string)); |
||||
return 0; |
||||
} |
||||
|
||||
int fputc(int character, void *stream) |
||||
{ |
||||
(void) stream; |
||||
const char byte = (char) character; |
||||
typephp_os_write(&byte, 1); |
||||
return (unsigned char) byte; |
||||
} |
||||
|
||||
size_t fwrite(const void *data, size_t size, size_t count, void *stream) |
||||
{ |
||||
(void) stream; |
||||
if (size != 0 && count > SIZE_MAX / size) { |
||||
return 0; |
||||
} |
||||
typephp_os_write((const char *) data, size * count); |
||||
return count; |
||||
} |
||||
|
||||
int fprintf(void *stream, const char *format, ...) |
||||
{ |
||||
(void) stream; |
||||
/* Zend's allocator only uses this on fatal paths. Keep the implementation
|
||||
* allocation-free; the complete formatter belongs to the standard layer. */ |
||||
typephp_os_write(format, strlen(format)); |
||||
return (int) strlen(format); |
||||
} |
||||
|
||||
int __fprintf_chk(void *stream, int flag, const char *format, ...) |
||||
{ |
||||
(void) flag; |
||||
return fprintf(stream, format); |
||||
} |
||||
|
||||
void *malloc(size_t size) |
||||
{ |
||||
void *result = 0; |
||||
if (posix_memalign(&result, 16, size) != 0) { |
||||
return 0; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
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; |
||||
} |
||||
|
||||
void *calloc(size_t count, size_t size) |
||||
{ |
||||
if (count != 0 && size > SIZE_MAX / count) { |
||||
return 0; |
||||
} |
||||
const size_t bytes = count * size; |
||||
void *result = malloc(bytes); |
||||
if (result != 0) { |
||||
memset(result, 0, bytes); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
void *realloc(void *pointer, size_t size) |
||||
{ |
||||
if (pointer == 0) { |
||||
return malloc(size); |
||||
} |
||||
if (size == 0) { |
||||
return 0; |
||||
} |
||||
typephp_os_block *old = (typephp_os_block *) pointer - 1; |
||||
void *replacement = malloc(size); |
||||
if (replacement != 0) { |
||||
memcpy(replacement, pointer, old->size < size ? old->size : size); |
||||
} |
||||
return replacement; |
||||
} |
||||
|
||||
int posix_memalign(void **result, size_t alignment, size_t size) |
||||
{ |
||||
if (result == 0 || !is_power_of_two(alignment) || alignment < sizeof(void *)) { |
||||
return 22; /* EINVAL */ |
||||
} |
||||
if (arena_cursor == 0 || size > SIZE_MAX - sizeof(typephp_os_block)) { |
||||
*result = 0; |
||||
return 12; /* ENOMEM */ |
||||
} |
||||
|
||||
const uintptr_t payload = align_up( |
||||
arena_cursor + sizeof(typephp_os_block), 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; |
||||
arena_cursor = payload + size; |
||||
*result = (void *) payload; |
||||
return 0; |
||||
} |
||||
|
||||
__attribute__((noreturn)) void abort(void) |
||||
{ |
||||
typephp_os_panic("abort"); |
||||
} |
||||
|
||||
__attribute__((noreturn)) void exit(int status) |
||||
{ |
||||
(void) status; |
||||
typephp_os_panic("exit"); |
||||
} |
||||
|
||||
__attribute__((noreturn)) void _Exit(int status) |
||||
{ |
||||
exit(status); |
||||
} |
||||
@ -0,0 +1,38 @@ |
||||
/*
|
||||
+----------------------------------------------------------------------+ |
||||
| TypePHP OS | |
||||
+----------------------------------------------------------------------+ |
||||
| Freestanding host contract owned by the TypePHP OS experiment. | |
||||
| SPDX-License-Identifier: BSD-3-Clause | |
||||
+----------------------------------------------------------------------+ |
||||
*/ |
||||
|
||||
#ifndef TYPEPHP_OS_ABI_H |
||||
#define TYPEPHP_OS_ABI_H |
||||
|
||||
#include <stddef.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/* Install the contiguous physical-memory arena used by the libc/POSIX shim.
|
||||
* The caller owns page-table setup and must keep the whole region mapped. */ |
||||
void typephp_os_memory_init(void *address, size_t size); |
||||
size_t typephp_os_memory_available(void); |
||||
|
||||
/* C++17 global new/delete are supplied by TypePHP OS and allocate
|
||||
* exclusively through Zend MM (emalloc/efree). */ |
||||
|
||||
/* A platform may override these weak hooks for diagnostics and shutdown. */ |
||||
void typephp_os_write(const char *data, size_t size); |
||||
void typephp_os_panic(const char *message); |
||||
|
||||
/* Generic PHPX no-exception policy hook. */ |
||||
void phpx_no_exception_abort(const char *message); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif |
||||
@ -0,0 +1,103 @@ |
||||
.section .text |
||||
.code64 |
||||
.extern typephp_os_panic
|
||||
|
||||
/* ABI coverage comes before subsystem implementation. Every placeholder is a |
||||
* real exported symbol so the complete php-nano/PHPX source set links, while |
||||
* any accidental runtime use fails loudly instead of returning fake data. */ |
||||
.macro TYPEPHP_OS_UNIMPLEMENTED symbol |
||||
.global \symbol |
||||
.type \symbol, @function
|
||||
\symbol: |
||||
lea .Lmessage\@(%rip), %rdi
|
||||
jmp typephp_os_panic |
||||
.pushsection .rodata |
||||
.Lmessage\@:
|
||||
.asciz "unimplemented libc/POSIX ABI: \symbol" |
||||
.popsection |
||||
.endm |
||||
|
||||
TYPEPHP_OS_UNIMPLEMENTED access |
||||
TYPEPHP_OS_UNIMPLEMENTED chdir |
||||
TYPEPHP_OS_UNIMPLEMENTED chmod |
||||
TYPEPHP_OS_UNIMPLEMENTED chown |
||||
TYPEPHP_OS_UNIMPLEMENTED close |
||||
TYPEPHP_OS_UNIMPLEMENTED closedir |
||||
TYPEPHP_OS_UNIMPLEMENTED fcntl |
||||
TYPEPHP_OS_UNIMPLEMENTED fdopen |
||||
TYPEPHP_OS_UNIMPLEMENTED feof |
||||
TYPEPHP_OS_UNIMPLEMENTED fileno |
||||
TYPEPHP_OS_UNIMPLEMENTED flock |
||||
TYPEPHP_OS_UNIMPLEMENTED fopen |
||||
TYPEPHP_OS_UNIMPLEMENTED fclose |
||||
TYPEPHP_OS_UNIMPLEMENTED fread |
||||
TYPEPHP_OS_UNIMPLEMENTED fseek |
||||
TYPEPHP_OS_UNIMPLEMENTED fstat |
||||
TYPEPHP_OS_UNIMPLEMENTED fsync |
||||
TYPEPHP_OS_UNIMPLEMENTED ftell |
||||
TYPEPHP_OS_UNIMPLEMENTED ftruncate |
||||
TYPEPHP_OS_UNIMPLEMENTED getgid |
||||
TYPEPHP_OS_UNIMPLEMENTED getgrnam |
||||
TYPEPHP_OS_UNIMPLEMENTED getgroups |
||||
TYPEPHP_OS_UNIMPLEMENTED getpwnam |
||||
TYPEPHP_OS_UNIMPLEMENTED getpwnam_r |
||||
TYPEPHP_OS_UNIMPLEMENTED getpwuid_r |
||||
TYPEPHP_OS_UNIMPLEMENTED getuid |
||||
TYPEPHP_OS_UNIMPLEMENTED gnu_dev_major |
||||
TYPEPHP_OS_UNIMPLEMENTED gnu_dev_minor |
||||
TYPEPHP_OS_UNIMPLEMENTED isatty |
||||
TYPEPHP_OS_UNIMPLEMENTED lseek |
||||
TYPEPHP_OS_UNIMPLEMENTED lstat |
||||
TYPEPHP_OS_UNIMPLEMENTED mkdir |
||||
TYPEPHP_OS_UNIMPLEMENTED mkstemp |
||||
TYPEPHP_OS_UNIMPLEMENTED open |
||||
TYPEPHP_OS_UNIMPLEMENTED opendir |
||||
TYPEPHP_OS_UNIMPLEMENTED read |
||||
TYPEPHP_OS_UNIMPLEMENTED readdir |
||||
TYPEPHP_OS_UNIMPLEMENTED readlink |
||||
TYPEPHP_OS_UNIMPLEMENTED rename |
||||
TYPEPHP_OS_UNIMPLEMENTED rewind |
||||
TYPEPHP_OS_UNIMPLEMENTED rewinddir |
||||
TYPEPHP_OS_UNIMPLEMENTED rmdir |
||||
TYPEPHP_OS_UNIMPLEMENTED stat |
||||
TYPEPHP_OS_UNIMPLEMENTED umask |
||||
TYPEPHP_OS_UNIMPLEMENTED unlink |
||||
TYPEPHP_OS_UNIMPLEMENTED utime |
||||
TYPEPHP_OS_UNIMPLEMENTED write |
||||
|
||||
TYPEPHP_OS_UNIMPLEMENTED acos |
||||
TYPEPHP_OS_UNIMPLEMENTED acosh |
||||
TYPEPHP_OS_UNIMPLEMENTED asin |
||||
TYPEPHP_OS_UNIMPLEMENTED asinh |
||||
TYPEPHP_OS_UNIMPLEMENTED atan |
||||
TYPEPHP_OS_UNIMPLEMENTED atan2 |
||||
TYPEPHP_OS_UNIMPLEMENTED atanh |
||||
TYPEPHP_OS_UNIMPLEMENTED copysign |
||||
TYPEPHP_OS_UNIMPLEMENTED cos |
||||
TYPEPHP_OS_UNIMPLEMENTED cosh |
||||
TYPEPHP_OS_UNIMPLEMENTED exp |
||||
TYPEPHP_OS_UNIMPLEMENTED expm1 |
||||
TYPEPHP_OS_UNIMPLEMENTED hypot |
||||
TYPEPHP_OS_UNIMPLEMENTED log |
||||
TYPEPHP_OS_UNIMPLEMENTED log10 |
||||
TYPEPHP_OS_UNIMPLEMENTED log1p |
||||
TYPEPHP_OS_UNIMPLEMENTED log2 |
||||
TYPEPHP_OS_UNIMPLEMENTED nextafter |
||||
TYPEPHP_OS_UNIMPLEMENTED sin |
||||
TYPEPHP_OS_UNIMPLEMENTED sinh |
||||
TYPEPHP_OS_UNIMPLEMENTED sqrt |
||||
TYPEPHP_OS_UNIMPLEMENTED tan |
||||
TYPEPHP_OS_UNIMPLEMENTED tanh |
||||
|
||||
TYPEPHP_OS_UNIMPLEMENTED mblen |
||||
TYPEPHP_OS_UNIMPLEMENTED strerror |
||||
TYPEPHP_OS_UNIMPLEMENTED strftime |
||||
|
||||
TYPEPHP_OS_UNIMPLEMENTED _Unwind_Resume |
||||
TYPEPHP_OS_UNIMPLEMENTED __cxa_begin_catch |
||||
TYPEPHP_OS_UNIMPLEMENTED __cxa_end_catch |
||||
TYPEPHP_OS_UNIMPLEMENTED __cxa_rethrow |
||||
TYPEPHP_OS_UNIMPLEMENTED __gxx_personality_v0 |
||||
TYPEPHP_OS_UNIMPLEMENTED _ZNSt15__exception_ptr13exception_ptr10_M_releaseEv |
||||
|
||||
.section .note.GNU-stack, "", @progbits
|
||||
@ -0,0 +1,118 @@ |
||||
.set MULTIBOOT_MAGIC, 0x1badb002 |
||||
.set MULTIBOOT_FLAGS, 0x00000003 |
||||
.set MULTIBOOT_CHECKSUM, -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS) |
||||
|
||||
.section .multiboot, "a" |
||||
.align 4
|
||||
.long MULTIBOOT_MAGIC
|
||||
.long MULTIBOOT_FLAGS
|
||||
.long MULTIBOOT_CHECKSUM
|
||||
|
||||
.section .bss |
||||
.align 4096
|
||||
pml4_table: |
||||
.skip 4096
|
||||
pdp_table: |
||||
.skip 4096
|
||||
pd_table: |
||||
.skip 4096
|
||||
|
||||
.align 16
|
||||
stack_bottom: |
||||
.skip 16384
|
||||
stack_top: |
||||
|
||||
.section .text |
||||
.code32 |
||||
.global _start
|
||||
.type _start, @function
|
||||
.extern kernel_entry
|
||||
_start: |
||||
cli |
||||
mov $stack_top, %esp |
||||
|
||||
# Preserve the Multiboot registers while page tables and MSRs are set up. |
||||
mov %eax, %ebp |
||||
|
||||
# Identity-map the first GiB with 2 MiB pages. |
||||
mov $pdp_table, %eax |
||||
or $0x3, %eax |
||||
mov %eax, pml4_table |
||||
|
||||
mov $pd_table, %eax |
||||
or $0x3, %eax |
||||
mov %eax, pdp_table |
||||
|
||||
mov $pd_table, %edi |
||||
mov $0x83, %eax |
||||
mov $512, %ecx |
||||
map_page: |
||||
mov %eax, (%edi) |
||||
add $0x200000, %eax |
||||
add $8, %edi |
||||
loop map_page |
||||
|
||||
# Enable PAE and prepare the x87/SSE state before entering long mode. |
||||
mov %cr0, %ecx |
||||
and $0xfffffffb, %ecx |
||||
or $0x2, %ecx |
||||
mov %ecx, %cr0 |
||||
mov %cr4, %ecx |
||||
or $0x620, %ecx |
||||
mov %ecx, %cr4 |
||||
fninit |
||||
|
||||
mov $pml4_table, %eax |
||||
mov %eax, %cr3 |
||||
|
||||
# Set EFER.LME, then enable paging. Protected mode is already active. |
||||
mov $0xc0000080, %ecx |
||||
rdmsr |
||||
or $0x100, %eax |
||||
wrmsr |
||||
|
||||
mov %cr0, %eax |
||||
or $0x80000000, %eax |
||||
mov %eax, %cr0 |
||||
|
||||
lgdt gdt64_pointer |
||||
|
||||
# The 64-bit System V ABI receives Multiboot magic/info in RDI/RSI. |
||||
mov %ebp, %edi |
||||
mov %ebx, %esi |
||||
ljmp $0x08, $long_mode_start |
||||
|
||||
.code64 |
||||
long_mode_start: |
||||
mov $0x10, %ax |
||||
mov %ax, %ds |
||||
mov %ax, %es |
||||
mov %ax, %ss |
||||
xor %ax, %ax |
||||
mov %ax, %fs |
||||
mov %ax, %gs |
||||
and $-16, %rsp |
||||
|
||||
# The embedded ELF64 payload is linked and loaded at exactly 2 MiB. |
||||
movabs $0x200000, %rax |
||||
call *%rax |
||||
|
||||
halt_forever: |
||||
cli |
||||
hlt |
||||
jmp halt_forever |
||||
.size _start, . - _start |
||||
|
||||
.section .rodata |
||||
.align 8
|
||||
gdt64: |
||||
.quad 0x0000000000000000
|
||||
.quad 0x00af9a000000ffff
|
||||
.quad 0x00cf92000000ffff
|
||||
gdt64_end: |
||||
|
||||
gdt64_pointer: |
||||
.word gdt64_end - gdt64 - 1 |
||||
.long gdt64
|
||||
|
||||
.section .note.GNU-stack, "", @progbits
|
||||
@ -0,0 +1,84 @@ |
||||
#include <typephp_os_abi.h> |
||||
#include <php_nano_extension.h> |
||||
#include <phpx.h> |
||||
#include <phpx_helper.h> |
||||
|
||||
void php_main(); |
||||
|
||||
extern "C" { |
||||
#include <zend_exceptions.h> |
||||
} |
||||
|
||||
extern "C" void kernel_clear_c(int color); |
||||
extern "C" void kernel_put_char_c(int ascii, int color); |
||||
extern "C" void kernel_halt_c(); |
||||
extern "C" unsigned long physical_memory_megabytes(); |
||||
extern "C" unsigned long physical_chunk_smoke_test(); |
||||
|
||||
static void exception_model_smoke_test() |
||||
{ |
||||
php::enableDebugInfo(true); |
||||
php::pushDebugFrame("kernel-smoke.php", 42, "Kernel::smoke"); |
||||
zend_throw_exception_ex(zend_ce_type_error, 0, "%s", "kernel exception"); |
||||
if (EG(exception) == nullptr || EG(exception)->ce != zend_ce_type_error) { |
||||
typephp_os_panic("Zend TypeError construction failed"); |
||||
} |
||||
|
||||
php::augmentException(); |
||||
zval value; |
||||
zval *file = zend_read_property_ex( |
||||
zend_get_exception_base(EG(exception)), EG(exception), ZSTR_KNOWN(ZEND_STR_FILE), true, &value); |
||||
if (Z_TYPE_P(file) != IS_STRING || !zend_string_equals_literal(Z_STR_P(file), "kernel-smoke.php")) { |
||||
typephp_os_panic("PHPX exception augmentation failed"); |
||||
} |
||||
|
||||
zend_clear_exception(); |
||||
php::popDebugFrame(); |
||||
php::enableDebugInfo(false); |
||||
} |
||||
|
||||
extern "C" void typephp_kernel_main() |
||||
{ |
||||
if (php_nano_startup_composer_extensions() != SUCCESS) { |
||||
typephp_os_panic("Unable to start Nano extensions"); |
||||
} |
||||
exception_model_smoke_test(); |
||||
php_main(); |
||||
} |
||||
|
||||
void php_kernel_clear(php::Int color) |
||||
{ |
||||
kernel_clear_c(static_cast<int>(color)); |
||||
} |
||||
|
||||
void php_kernel_put_char(php::Int ascii, php::Int color) |
||||
{ |
||||
kernel_put_char_c(static_cast<int>(ascii), static_cast<int>(color)); |
||||
} |
||||
|
||||
void php_kernel_write(php::Str text, php::Int color) |
||||
{ |
||||
for (size_t i = 0; i < text.length(); ++i) { |
||||
kernel_put_char_c(static_cast<unsigned char>(text.data()[i]), static_cast<int>(color)); |
||||
} |
||||
} |
||||
|
||||
php::Int php_kernel_word_bits() |
||||
{ |
||||
return static_cast<php::Int>(sizeof(php::Int) * 8); |
||||
} |
||||
|
||||
php::Int php_kernel_memory_megabytes() |
||||
{ |
||||
return static_cast<php::Int>(physical_memory_megabytes()); |
||||
} |
||||
|
||||
php::Int php_kernel_chunk_smoke_test() |
||||
{ |
||||
return static_cast<php::Int>(physical_chunk_smoke_test()); |
||||
} |
||||
|
||||
void php_kernel_halt() |
||||
{ |
||||
kernel_halt_c(); |
||||
} |
||||
@ -0,0 +1,135 @@ |
||||
typedef unsigned char uint8_t; |
||||
typedef unsigned short uint16_t; |
||||
typedef unsigned int uint32_t; |
||||
typedef unsigned long uint64_t; |
||||
|
||||
enum { |
||||
MULTIBOOT_BOOTLOADER_MAGIC = 0x2badb002u, |
||||
VGA_WIDTH = 80, |
||||
VGA_HEIGHT = 25, |
||||
COM1 = 0x3f8, |
||||
}; |
||||
|
||||
static volatile uint16_t *const vga = (volatile uint16_t *) 0xb8000; |
||||
static uint32_t row; |
||||
static uint32_t column; |
||||
|
||||
static inline void outb(uint16_t port, uint8_t value) |
||||
{ |
||||
__asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port)); |
||||
} |
||||
|
||||
static inline uint8_t inb(uint16_t port) |
||||
{ |
||||
uint8_t value; |
||||
__asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); |
||||
return value; |
||||
} |
||||
|
||||
static void serial_init(void) |
||||
{ |
||||
outb(COM1 + 1, 0x00); |
||||
outb(COM1 + 3, 0x80); |
||||
outb(COM1 + 0, 0x03); |
||||
outb(COM1 + 1, 0x00); |
||||
outb(COM1 + 3, 0x03); |
||||
outb(COM1 + 2, 0xc7); |
||||
outb(COM1 + 4, 0x0b); |
||||
} |
||||
|
||||
static void serial_put(uint8_t value) |
||||
{ |
||||
while ((inb(COM1 + 5) & 0x20u) == 0) { |
||||
} |
||||
outb(COM1, value); |
||||
} |
||||
|
||||
static void vga_scroll(void) |
||||
{ |
||||
if (row < VGA_HEIGHT) { |
||||
return; |
||||
} |
||||
for (uint32_t y = 1; y < VGA_HEIGHT; y++) { |
||||
for (uint32_t x = 0; x < VGA_WIDTH; x++) { |
||||
vga[(y - 1) * VGA_WIDTH + x] = vga[y * VGA_WIDTH + x]; |
||||
} |
||||
} |
||||
for (uint32_t x = 0; x < VGA_WIDTH; x++) { |
||||
vga[(VGA_HEIGHT - 1) * VGA_WIDTH + x] = (uint16_t) (0x0720u); |
||||
} |
||||
row = VGA_HEIGHT - 1; |
||||
} |
||||
|
||||
void kernel_clear_c(int color) |
||||
{ |
||||
const uint16_t blank = (uint16_t) ((((uint16_t) color) << 8u) | ' '); |
||||
for (uint32_t i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) { |
||||
vga[i] = blank; |
||||
} |
||||
row = 0; |
||||
column = 0; |
||||
} |
||||
|
||||
void kernel_put_char_c(int ascii, int color) |
||||
{ |
||||
const uint8_t character = (uint8_t) ascii; |
||||
if (character == '\n') { |
||||
serial_put('\r'); |
||||
serial_put('\n'); |
||||
column = 0; |
||||
row++; |
||||
vga_scroll(); |
||||
return; |
||||
} |
||||
if (character == '\t') { |
||||
do { |
||||
kernel_put_char_c(' ', color); |
||||
} while ((column & 7u) != 0); |
||||
return; |
||||
} |
||||
|
||||
serial_put(character); |
||||
vga[row * VGA_WIDTH + column] = |
||||
(uint16_t) ((((uint16_t) color) << 8u) | character); |
||||
column++; |
||||
if (column == VGA_WIDTH) { |
||||
column = 0; |
||||
row++; |
||||
vga_scroll(); |
||||
} |
||||
} |
||||
|
||||
void typephp_os_write(const char *data, unsigned long size) |
||||
{ |
||||
for (unsigned long index = 0; index < size; ++index) { |
||||
kernel_put_char_c((unsigned char) data[index], 0x0f); |
||||
} |
||||
} |
||||
|
||||
void kernel_halt_c(void) |
||||
{ |
||||
// QEMU's isa-debug-exit device turns this into process exit status 33.
|
||||
outb(0xf4, 0x10); |
||||
for (;;) { |
||||
__asm__ volatile("cli; hlt"); |
||||
} |
||||
} |
||||
|
||||
extern void typephp_kernel_main(void); |
||||
extern void physical_memory_init(uint64_t multiboot_info_address); |
||||
|
||||
void kernel_entry(uint64_t magic, uint64_t multiboot_info) |
||||
{ |
||||
(void) multiboot_info; |
||||
serial_init(); |
||||
if (magic != MULTIBOOT_BOOTLOADER_MAGIC) { |
||||
const char *message = "Invalid Multiboot boot magic\n"; |
||||
while (*message != '\0') { |
||||
serial_put((uint8_t) *message++); |
||||
} |
||||
kernel_halt_c(); |
||||
} |
||||
physical_memory_init(multiboot_info); |
||||
typephp_kernel_main(); |
||||
kernel_halt_c(); |
||||
} |
||||
@ -0,0 +1,31 @@ |
||||
.section .text.boot, "ax" |
||||
.code64 |
||||
.global kernel64_entry
|
||||
.type kernel64_entry, @function
|
||||
.extern kernel_entry
|
||||
.extern __bss_start
|
||||
.extern __bss_end
|
||||
|
||||
kernel64_entry: |
||||
cld |
||||
mov %rdi, %r12 |
||||
mov %rsi, %r13 |
||||
|
||||
# The raw payload does not carry ELF NOBITS data, so clear it explicitly. |
||||
lea __bss_start(%rip), %rdi |
||||
lea __bss_end(%rip), %rcx |
||||
sub %rdi, %rcx |
||||
xor %eax, %eax |
||||
rep stosb |
||||
|
||||
mov %r12, %rdi |
||||
mov %r13, %rsi |
||||
call kernel_entry |
||||
|
||||
halt64: |
||||
cli |
||||
hlt |
||||
jmp halt64 |
||||
.size kernel64_entry, . - kernel64_entry |
||||
|
||||
.section .note.GNU-stack, "", @progbits
|
||||
@ -0,0 +1,41 @@ |
||||
OUTPUT_FORMAT(elf64-x86-64) |
||||
OUTPUT_ARCH(i386:x86-64) |
||||
ENTRY(kernel64_entry) |
||||
|
||||
SECTIONS |
||||
{ |
||||
. = 2M; |
||||
|
||||
.text ALIGN(4K) : |
||||
{ |
||||
KEEP(*(.text.boot)) |
||||
*(.text .text.*) |
||||
} |
||||
|
||||
.rodata ALIGN(4K) : |
||||
{ |
||||
*(.rodata .rodata.*) |
||||
} |
||||
|
||||
.data ALIGN(4K) : |
||||
{ |
||||
*(.data .data.*) |
||||
} |
||||
|
||||
.bss ALIGN(4K) (NOLOAD) : |
||||
{ |
||||
__bss_start = .; |
||||
*(COMMON) |
||||
*(.bss .bss.*) |
||||
__bss_end = .; |
||||
} |
||||
|
||||
ASSERT(__bss_end <= 16M, "64-bit kernel exceeds its reserved boot memory") |
||||
|
||||
/DISCARD/ : |
||||
{ |
||||
*(.comment) |
||||
*(.eh_frame*) |
||||
*(.note*) |
||||
} |
||||
} |
||||
@ -0,0 +1,49 @@ |
||||
OUTPUT_FORMAT(elf32-i386) |
||||
OUTPUT_ARCH(i386) |
||||
ENTRY(_start) |
||||
|
||||
SECTIONS |
||||
{ |
||||
. = 1M; |
||||
|
||||
.multiboot ALIGN(4) : |
||||
{ |
||||
KEEP(*(.multiboot)) |
||||
} |
||||
|
||||
.text ALIGN(4K) : |
||||
{ |
||||
*(.text .text.*) |
||||
} |
||||
|
||||
.rodata ALIGN(4K) : |
||||
{ |
||||
*(.rodata .rodata.*) |
||||
} |
||||
|
||||
.data ALIGN(4K) : |
||||
{ |
||||
*(.data .data.*) |
||||
} |
||||
|
||||
.bss ALIGN(4K) (NOLOAD) : |
||||
{ |
||||
*(COMMON) |
||||
*(.bss .bss.*) |
||||
} |
||||
|
||||
ASSERT(. < 2M, "32-bit bootstrap overlaps the 64-bit payload") |
||||
|
||||
. = 2M; |
||||
.payload ALIGN(4K) : |
||||
{ |
||||
KEEP(*(.payload)) |
||||
} |
||||
|
||||
/DISCARD/ : |
||||
{ |
||||
*(.comment) |
||||
*(.eh_frame*) |
||||
*(.note*) |
||||
} |
||||
} |
||||
@ -0,0 +1,95 @@ |
||||
typedef unsigned int uint32_t; |
||||
typedef unsigned long uint64_t; |
||||
|
||||
extern void typephp_os_memory_init(void *address, unsigned long size); |
||||
|
||||
enum { |
||||
MULTIBOOT_INFO_MEMORY_MAP = 1u << 6, |
||||
}; |
||||
|
||||
static const uint64_t CHUNK_SIZE = 2ul * 1024ul * 1024ul; |
||||
/* Keep the complete TypePHP + PHP Nano payload away from the page allocator. */ |
||||
static const uint64_t KERNEL_RESERVED_END = 16ul * 1024ul * 1024ul; |
||||
static const uint64_t IDENTITY_MAP_END = 1024ul * 1024ul * 1024ul; |
||||
|
||||
typedef struct __attribute__((packed)) { |
||||
uint32_t size; |
||||
uint64_t address; |
||||
uint64_t length; |
||||
uint32_t type; |
||||
} multiboot_memory_entry; |
||||
|
||||
typedef struct __attribute__((packed)) { |
||||
uint32_t flags; |
||||
uint32_t memory_lower; |
||||
uint32_t memory_upper; |
||||
uint32_t boot_device; |
||||
uint32_t command_line; |
||||
uint32_t modules_count; |
||||
uint32_t modules_address; |
||||
uint32_t symbols[4]; |
||||
uint32_t memory_map_length; |
||||
uint32_t memory_map_address; |
||||
} multiboot_info; |
||||
|
||||
static uint64_t usable_memory_bytes; |
||||
static uint64_t chunk_cursor; |
||||
static uint64_t chunk_end; |
||||
|
||||
static uint64_t align_up(uint64_t value, uint64_t alignment) |
||||
{ |
||||
return (value + alignment - 1) & ~(alignment - 1); |
||||
} |
||||
|
||||
void physical_memory_init(uint64_t multiboot_info_address) |
||||
{ |
||||
const multiboot_info *info = |
||||
(const multiboot_info *) (unsigned long) multiboot_info_address; |
||||
usable_memory_bytes = 0; |
||||
chunk_cursor = 0; |
||||
chunk_end = 0; |
||||
|
||||
if ((info->flags & MULTIBOOT_INFO_MEMORY_MAP) == 0) { |
||||
return; |
||||
} |
||||
|
||||
uint64_t position = info->memory_map_address; |
||||
const uint64_t end = position + info->memory_map_length; |
||||
while (position + sizeof(multiboot_memory_entry) <= end) { |
||||
const multiboot_memory_entry *entry = |
||||
(const multiboot_memory_entry *) (unsigned long) position; |
||||
if (entry->size < sizeof(multiboot_memory_entry) - sizeof(uint32_t)) { |
||||
break; |
||||
} |
||||
|
||||
if (entry->type == 1) { |
||||
usable_memory_bytes += entry->length; |
||||
uint64_t region_end = entry->address + entry->length; |
||||
if (region_end > IDENTITY_MAP_END) { |
||||
region_end = IDENTITY_MAP_END; |
||||
} |
||||
uint64_t region_start = entry->address; |
||||
if (region_start < KERNEL_RESERVED_END) { |
||||
region_start = KERNEL_RESERVED_END; |
||||
} |
||||
region_start = align_up(region_start, CHUNK_SIZE); |
||||
if (region_start + CHUNK_SIZE <= region_end |
||||
&& region_end - region_start > chunk_end - chunk_cursor) { |
||||
chunk_cursor = region_start; |
||||
chunk_end = region_end; |
||||
} |
||||
} |
||||
position += (uint64_t) entry->size + sizeof(entry->size); |
||||
} |
||||
|
||||
if (chunk_cursor != 0 && chunk_end > chunk_cursor) { |
||||
typephp_os_memory_init( |
||||
(void *) (unsigned long) chunk_cursor, |
||||
(unsigned long) (chunk_end - chunk_cursor)); |
||||
} |
||||
} |
||||
|
||||
uint64_t physical_memory_megabytes(void) |
||||
{ |
||||
return usable_memory_bytes >> 20; |
||||
} |
||||
@ -0,0 +1,215 @@ |
||||
#include <php.h> |
||||
|
||||
enum { |
||||
CMOS_ADDRESS = 0x70, |
||||
CMOS_DATA = 0x71, |
||||
}; |
||||
|
||||
typedef struct kernel_datetime { |
||||
uint16_t year; |
||||
uint8_t month; |
||||
uint8_t day; |
||||
uint8_t hour; |
||||
uint8_t minute; |
||||
uint8_t second; |
||||
} kernel_datetime; |
||||
|
||||
static inline void outb(uint16_t port, uint8_t value) |
||||
{ |
||||
__asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port)); |
||||
} |
||||
|
||||
static inline uint8_t inb(uint16_t port) |
||||
{ |
||||
uint8_t value; |
||||
__asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); |
||||
return value; |
||||
} |
||||
|
||||
static uint8_t cmos_read(uint8_t index) |
||||
{ |
||||
/* Keep NMI disabled while selecting a CMOS register. The kernel does not
|
||||
* install an IDT yet, so accepting an NMI here would be unsafe. */ |
||||
outb(CMOS_ADDRESS, (uint8_t) (index | 0x80u)); |
||||
return inb(CMOS_DATA); |
||||
} |
||||
|
||||
static int rtc_update_in_progress(void) |
||||
{ |
||||
return (cmos_read(0x0a) & 0x80u) != 0; |
||||
} |
||||
|
||||
static uint8_t bcd_to_binary(uint8_t value) |
||||
{ |
||||
return (uint8_t) ((value & 0x0fu) + ((value >> 4u) * 10u)); |
||||
} |
||||
|
||||
static void rtc_snapshot(kernel_datetime *value) |
||||
{ |
||||
uint8_t second; |
||||
uint8_t minute; |
||||
uint8_t hour; |
||||
uint8_t day; |
||||
uint8_t month; |
||||
uint8_t year; |
||||
uint8_t century; |
||||
uint8_t status_b; |
||||
uint8_t previous_second; |
||||
uint8_t previous_minute; |
||||
uint8_t previous_hour; |
||||
uint8_t previous_day; |
||||
uint8_t previous_month; |
||||
uint8_t previous_year; |
||||
uint8_t previous_century; |
||||
|
||||
do { |
||||
while (rtc_update_in_progress()) { |
||||
__asm__ volatile("pause"); |
||||
} |
||||
previous_second = cmos_read(0x00); |
||||
previous_minute = cmos_read(0x02); |
||||
previous_hour = cmos_read(0x04); |
||||
previous_day = cmos_read(0x07); |
||||
previous_month = cmos_read(0x08); |
||||
previous_year = cmos_read(0x09); |
||||
previous_century = cmos_read(0x32); |
||||
|
||||
while (rtc_update_in_progress()) { |
||||
__asm__ volatile("pause"); |
||||
} |
||||
second = cmos_read(0x00); |
||||
minute = cmos_read(0x02); |
||||
hour = cmos_read(0x04); |
||||
day = cmos_read(0x07); |
||||
month = cmos_read(0x08); |
||||
year = cmos_read(0x09); |
||||
century = cmos_read(0x32); |
||||
} while (second != previous_second || minute != previous_minute |
||||
|| hour != previous_hour || day != previous_day |
||||
|| month != previous_month || year != previous_year |
||||
|| century != previous_century); |
||||
|
||||
status_b = cmos_read(0x0b); |
||||
if ((status_b & 0x04u) == 0) { |
||||
second = bcd_to_binary(second); |
||||
minute = bcd_to_binary(minute); |
||||
day = bcd_to_binary(day); |
||||
month = bcd_to_binary(month); |
||||
year = bcd_to_binary(year); |
||||
century = bcd_to_binary(century); |
||||
hour = (uint8_t) ((hour & 0x80u) | bcd_to_binary((uint8_t) (hour & 0x7fu))); |
||||
} |
||||
if ((status_b & 0x02u) == 0 && (hour & 0x80u) != 0) { |
||||
hour = (uint8_t) (((hour & 0x7fu) + 12u) % 24u); |
||||
} else { |
||||
hour &= 0x7fu; |
||||
} |
||||
|
||||
value->year = century >= 19 && century <= 99 |
||||
? (uint16_t) (century * 100u + year) |
||||
: (uint16_t) (2000u + year); |
||||
value->month = month; |
||||
value->day = day; |
||||
value->hour = hour; |
||||
value->minute = minute; |
||||
value->second = second; |
||||
} |
||||
|
||||
static int is_leap_year(uint64_t year) |
||||
{ |
||||
return (year % 4u == 0 && year % 100u != 0) || year % 400u == 0; |
||||
} |
||||
|
||||
static uint64_t rtc_seconds(const kernel_datetime *value) |
||||
{ |
||||
static const uint16_t days_before_month[] = { |
||||
0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, |
||||
}; |
||||
uint64_t year = value->year; |
||||
uint64_t previous_year = year - 1u; |
||||
uint64_t days = 365u * (year - 1970u) |
||||
+ previous_year / 4u - 1969u / 4u |
||||
- previous_year / 100u + 1969u / 100u |
||||
+ previous_year / 400u - 1969u / 400u; |
||||
days += days_before_month[value->month]; |
||||
if (value->month > 2 && is_leap_year(year)) { |
||||
++days; |
||||
} |
||||
days += value->day - 1u; |
||||
return (((days * 24u + value->hour) * 60u + value->minute) * 60u) |
||||
+ value->second; |
||||
} |
||||
|
||||
void kernel_datetime_c(kernel_datetime *value) |
||||
{ |
||||
rtc_snapshot(value); |
||||
} |
||||
|
||||
void kernel_sleep_c(uint64_t seconds) |
||||
{ |
||||
kernel_datetime now; |
||||
rtc_snapshot(&now); |
||||
const uint64_t deadline = rtc_seconds(&now) + seconds; |
||||
do { |
||||
__asm__ volatile("pause"); |
||||
rtc_snapshot(&now); |
||||
} while (rtc_seconds(&now) < deadline); |
||||
} |
||||
|
||||
long typephp_os_time_seconds(void) |
||||
{ |
||||
kernel_datetime now; |
||||
rtc_snapshot(&now); |
||||
return (long) rtc_seconds(&now); |
||||
} |
||||
|
||||
void php_nano_host_system_time(int64_t *seconds, int32_t *microseconds) |
||||
{ |
||||
*seconds = (int64_t) typephp_os_time_seconds(); |
||||
*microseconds = 0; |
||||
} |
||||
|
||||
uint64_t php_nano_host_monotonic_nanoseconds(void) |
||||
{ |
||||
return (uint64_t) typephp_os_time_seconds() * UINT64_C(1000000000); |
||||
} |
||||
|
||||
void php_nano_host_sleep(uint64_t seconds, uint32_t nanoseconds) |
||||
{ |
||||
if (nanoseconds != 0) { |
||||
/* RTC resolution is currently one second. Round sub-second sleeps up
|
||||
* until the timer interrupt implementation is available. */ |
||||
++seconds; |
||||
} |
||||
kernel_sleep_c(seconds); |
||||
} |
||||
|
||||
unsigned int sleep(unsigned int seconds) |
||||
{ |
||||
kernel_sleep_c(seconds); |
||||
return 0; |
||||
} |
||||
|
||||
time_t time(time_t *result) |
||||
{ |
||||
const time_t value = (time_t) typephp_os_time_seconds(); |
||||
if (result != 0) { |
||||
*result = value; |
||||
} |
||||
return value; |
||||
} |
||||
|
||||
int clock_gettime(clockid_t clock_id, struct timespec *value) |
||||
{ |
||||
(void) clock_id; |
||||
value->tv_sec = (time_t) typephp_os_time_seconds(); |
||||
value->tv_nsec = 0; |
||||
return 0; |
||||
} |
||||
|
||||
int nanosleep(const struct timespec *duration, struct timespec *remaining) |
||||
{ |
||||
(void) remaining; |
||||
php_nano_host_sleep((uint64_t) duration->tv_sec, (uint32_t) duration->tv_nsec); |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,57 @@ |
||||
#include <php.h> |
||||
#include <zend_gc.h> |
||||
|
||||
unsigned long physical_chunk_smoke_test(void) |
||||
{ |
||||
|
||||
unsigned char *small = (unsigned char *) emalloc(37); |
||||
unsigned char *large = (unsigned char *) emalloc(96 * 1024); |
||||
if (small == 0 || large == 0 || small == large) { |
||||
return 0; |
||||
} |
||||
|
||||
for (unsigned long index = 0; index < 37; ++index) { |
||||
small[index] = (unsigned char) (index + 1); |
||||
} |
||||
for (unsigned long index = 0; index < 37; ++index) { |
||||
if (small[index] != (unsigned char) (index + 1)) { |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
efree(large); |
||||
efree(small); |
||||
|
||||
if (!gc_enabled()) { |
||||
return 0; |
||||
} |
||||
zend_gc_status status; |
||||
zend_gc_get_status(&status); |
||||
/* A newly enabled collector has an allocated, empty root buffer. */ |
||||
if (status.runs != 0 || status.collected != 0 || status.num_roots != 0) { |
||||
return 0; |
||||
} |
||||
|
||||
zend_string *key = zend_string_init("answer", sizeof("answer") - 1, 0); |
||||
if (key == 0 || ZSTR_LEN(key) != sizeof("answer") - 1 |
||||
|| memcmp(ZSTR_VAL(key), "answer", sizeof("answer")) != 0) { |
||||
return 0; |
||||
} |
||||
|
||||
HashTable table; |
||||
zend_hash_init(&table, 4, 0, 0, 0); |
||||
zval value; |
||||
ZVAL_STRING(&value, "forty-two"); |
||||
if (zend_hash_add(&table, key, &value) == 0) { |
||||
return 0; |
||||
} |
||||
zval *found = zend_hash_find(&table, key); |
||||
if (found == 0 || Z_TYPE_P(found) != IS_STRING |
||||
|| !zend_string_equals_literal(Z_STR_P(found), "forty-two")) { |
||||
return 0; |
||||
} |
||||
zend_hash_destroy(&table); |
||||
zend_string_release(key); |
||||
|
||||
return zend_memory_usage(1) >= 2ul * 1024ul * 1024ul ? 2 : 0; |
||||
} |
||||
@ -0,0 +1,57 @@ |
||||
name: typephp_os |
||||
mode: bin |
||||
sources: |
||||
- src |
||||
- freestanding/kernel64-entry.S |
||||
- freestanding/kernel.c |
||||
- freestanding/memory.c |
||||
- freestanding/time.c |
||||
- freestanding/zend-allocator.c |
||||
- freestanding/bridge.cc |
||||
- freestanding/abi/libc.c |
||||
- freestanding/abi/cxx.cpp |
||||
- freestanding/abi/unimplemented.S |
||||
build-dir: build/generated |
||||
output: build/kernel64.elf |
||||
cxx-std: c++17 |
||||
include-paths: |
||||
- freestanding/abi |
||||
defines: |
||||
- TYPEPHP_NO_MAIN=1 |
||||
- PHPX_NO_EXCEPTION=1 |
||||
- PHPX_NO_RTTI=1 |
||||
- PHP_NANO_NO_LIBC=1 |
||||
- __NO_CTYPE=1 |
||||
- ZEND_MM_ERROR=0 |
||||
- ZEND_MM_CUSTOM=0 |
||||
cxx-flags: |
||||
- -ffreestanding |
||||
- -fno-builtin |
||||
- -fno-stack-protector |
||||
- -fno-pic |
||||
- -fno-pie |
||||
- -m64 |
||||
- -mno-red-zone |
||||
- -mcmodel=small |
||||
- -fno-rtti |
||||
- -fno-threadsafe-statics |
||||
c-flags: |
||||
- -ffreestanding |
||||
- -fno-builtin |
||||
- -fno-stack-protector |
||||
- -fno-pic |
||||
- -fno-pie |
||||
- -m64 |
||||
- -mno-red-zone |
||||
- -mcmodel=small |
||||
asm-flags: |
||||
- -m64 |
||||
- -mno-red-zone |
||||
- -mcmodel=small |
||||
ld-flags: |
||||
- -nostdlib |
||||
- -no-pie |
||||
- -Wl,-m,elf_x86_64 |
||||
- -Wl,--build-id=none |
||||
- -Wl,-T,examples/typephp-os/freestanding/kernel64.ld |
||||
- -lgcc |
||||
@ -0,0 +1,22 @@ |
||||
<?php |
||||
|
||||
#[NativeFunction] |
||||
function kernel_clear(int $color): void {} |
||||
|
||||
#[NativeFunction] |
||||
function kernel_put_char(int $ascii, int $color): void {} |
||||
|
||||
#[NativeFunction] |
||||
function kernel_write(string $text, int $color): void {} |
||||
|
||||
#[NativeFunction] |
||||
function kernel_word_bits(): int { return 0; } |
||||
|
||||
#[NativeFunction] |
||||
function kernel_memory_megabytes(): int { return 0; } |
||||
|
||||
#[NativeFunction] |
||||
function kernel_chunk_smoke_test(): int { return 0; } |
||||
|
||||
#[NativeFunction] |
||||
function kernel_halt(): void {} |
||||
@ -0,0 +1,110 @@ |
||||
<?php |
||||
|
||||
final class KernelGreeting |
||||
{ |
||||
public function render(string $now): string |
||||
{ |
||||
return $now . ' Hello TypePHP-OS!'; |
||||
} |
||||
} |
||||
|
||||
function writeLine(string $text, int $color): void |
||||
{ |
||||
kernel_write($text, $color); |
||||
kernel_put_char(10, $color); |
||||
} |
||||
|
||||
function writeNumber(int $number, int $color): void |
||||
{ |
||||
if ($number === 0) { |
||||
kernel_put_char(48, $color); |
||||
return; |
||||
} |
||||
|
||||
$digits = std::vector(Type::Int); |
||||
while ($number > 0) { |
||||
$digit = $number % 10; |
||||
$digits[] = $digit; |
||||
$number = (int) (($number - $digit) / 10); |
||||
} |
||||
|
||||
for ($index = count($digits) - 1; $index >= 0; $index--) { |
||||
kernel_put_char(48 + $digits[$index], $color); |
||||
} |
||||
} |
||||
|
||||
function isPrime(int $number): bool |
||||
{ |
||||
if ($number < 2) { |
||||
return false; |
||||
} |
||||
for ($divisor = 2; $divisor * $divisor <= $number; $divisor++) { |
||||
if ($number % $divisor === 0) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
function runFeatureSelfCheck(): void |
||||
{ |
||||
kernel_clear(0); |
||||
writeLine('TypePHP OS POC', 10); |
||||
|
||||
kernel_write('Int bits: ', 15); |
||||
writeNumber(kernel_word_bits(), 15); |
||||
kernel_put_char(10, 15); |
||||
|
||||
kernel_write('RAM MiB: ', 15); |
||||
writeNumber(kernel_memory_megabytes(), 15); |
||||
kernel_put_char(10, 15); |
||||
|
||||
kernel_write('Zend MiB: ', 15); |
||||
writeNumber(kernel_chunk_smoke_test(), 15); |
||||
kernel_put_char(10, 15); |
||||
|
||||
$checks = ['Zend string/array: OK', 'Kernel is!']; |
||||
foreach ($checks as $check) { |
||||
writeLine($check, 10); |
||||
} |
||||
} |
||||
|
||||
function runPrimeDemo(int $limit): void |
||||
{ |
||||
$primes = std::vector(Type::Int); |
||||
for ($candidate = 0; $candidate <= $limit; $candidate++) { |
||||
if (isPrime($candidate)) { |
||||
$primes[] = $candidate; |
||||
} |
||||
} |
||||
|
||||
kernel_write('Calculate primes: 0-', 11); |
||||
writeNumber($limit, 11); |
||||
kernel_put_char(10, 11); |
||||
|
||||
kernel_write('Prime count: ', 15); |
||||
writeNumber(count($primes), 15); |
||||
kernel_put_char(10, 15); |
||||
|
||||
kernel_write('Prime list: ', 15); |
||||
for ($index = 0; $index < count($primes); $index++) { |
||||
if ($index !== 0) { |
||||
kernel_write(', ', 15); |
||||
} |
||||
writeNumber($primes[$index], 15); |
||||
} |
||||
kernel_put_char(10, 15); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
runFeatureSelfCheck(); |
||||
runPrimeDemo(100); |
||||
|
||||
$greeting = new KernelGreeting(); |
||||
while (true) { |
||||
sleep(2); |
||||
kernel_write($greeting->render(date('Y-m-d H:i:s')), 15); |
||||
kernel_put_char(10, 15); |
||||
} |
||||
} |
||||
@ -0,0 +1,38 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -euo pipefail |
||||
|
||||
kernel=${1:?kernel ELF is required} |
||||
log=${2:?log path is required} |
||||
|
||||
if timeout 8 qemu-system-x86_64 \ |
||||
-kernel "${kernel}" \ |
||||
-display none \ |
||||
-serial stdio \ |
||||
-monitor none \ |
||||
-no-reboot \ |
||||
-no-shutdown \ |
||||
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \ |
||||
>"${log}" 2>&1; then |
||||
status=0 |
||||
else |
||||
status=$? |
||||
fi |
||||
|
||||
if [[ ${status} -ne 124 ]]; then |
||||
cat "${log}" |
||||
echo "QEMU exited with ${status}; expected the resident kernel loop to reach the timeout" >&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
grep -q "TypePHP OS POC" "${log}" |
||||
grep -q "Int bits: 64" "${log}" |
||||
grep -Eq '^RAM MiB: [1-9][0-9]*' "${log}" |
||||
grep -q "Zend MiB: 2" "${log}" |
||||
grep -q "Zend string/array: OK" "${log}" |
||||
grep -q "Kernel is!" "${log}" |
||||
grep -q "Calculate primes: 0-100" "${log}" |
||||
grep -q "Prime count: 25" "${log}" |
||||
grep -q "Prime list: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97" "${log}" |
||||
grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} Hello TypePHP-OS![[:space:]]*$' "${log}" |
||||
cat "${log}" |
||||
Loading…
Reference in new issue