From aa6b7924e2b6c47a2025f1e2af07d9419f7edcd6 Mon Sep 17 00:00:00 2001 From: Karina Date: Sun, 1 Feb 2026 18:03:08 +0400 Subject: [PATCH] feat: custom objc runtime, msgSend, and HOTObject experiment --- .clangd | 4 + CMakeLists.txt | 1 + userspace/CMakeLists.txt | 4 +- userspace/libobjc/CMakeLists.txt | 31 +++++ userspace/libobjc/inc/HOTObject.h | 21 +++ userspace/libobjc/src/HOTObject.m | 20 +++ userspace/libobjc/src/exceptions.c | 3 + userspace/libobjc/src/lookup.c | 3 + userspace/libobjc/src/msgSend.asm | 59 +++++++++ userspace/libobjc/src/runtime.c | 121 ++++++++++++++++++ userspace/libterm/CMakeLists.txt | 30 ++++- .../libterm/{linker.ld => linker/common.ld} | 0 userspace/libterm/linker/objc.ld | 28 ++++ userspace/libterm/src/ObjCRuntimeEntry.asm | 18 +++ userspace/testOBJC/CMakeLists.txt | 6 + userspace/testOBJC/src/main.m | 22 ++++ 16 files changed, 364 insertions(+), 7 deletions(-) create mode 100644 .clangd create mode 100644 userspace/libobjc/CMakeLists.txt create mode 100644 userspace/libobjc/inc/HOTObject.h create mode 100644 userspace/libobjc/src/HOTObject.m create mode 100644 userspace/libobjc/src/exceptions.c create mode 100644 userspace/libobjc/src/lookup.c create mode 100644 userspace/libobjc/src/msgSend.asm create mode 100644 userspace/libobjc/src/runtime.c rename userspace/libterm/{linker.ld => linker/common.ld} (100%) create mode 100644 userspace/libterm/linker/objc.ld create mode 100644 userspace/libterm/src/ObjCRuntimeEntry.asm create mode 100644 userspace/testOBJC/CMakeLists.txt create mode 100644 userspace/testOBJC/src/main.m diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..fb0bdc0 --- /dev/null +++ b/.clangd @@ -0,0 +1,4 @@ +If: + PathMatch: .*\.h +CompileFlags: + Add: [-x, objective-c] \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 943e9f2..3df7553 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ set(SYSTEM_SERVICES init debug termosh + testOBJC ) set(VOLUME_ROOT "${CMAKE_BINARY_DIR}/StartupVolume") diff --git a/userspace/CMakeLists.txt b/userspace/CMakeLists.txt index 1408b96..56ffa15 100644 --- a/userspace/CMakeLists.txt +++ b/userspace/CMakeLists.txt @@ -7,7 +7,9 @@ set(CMAKE_C_EXTENSIONS OFF) message(STATUS "Building termOS's userspace") +add_subdirectory(libobjc) add_subdirectory(libterm) add_subdirectory(init) add_subdirectory(debug) -add_subdirectory(termosh) \ No newline at end of file +add_subdirectory(testOBJC) +add_subdirectory(termosh) diff --git a/userspace/libobjc/CMakeLists.txt b/userspace/libobjc/CMakeLists.txt new file mode 100644 index 0000000..1e8ad41 --- /dev/null +++ b/userspace/libobjc/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.16) +project(libobjc LANGUAGES C ASM_NASM OBJC) + +set(USER_C_FLAGS + -ffreestanding + -fno-builtin + -nostdlib + -nostdinc + -fno-stack-protector + -fno-pic + -fno-pie + -m64 + -mno-red-zone + -mcmodel=small + -O2 +) + +set(LIBOBJC_SOURCES + src/runtime.c + src/lookup.c + src/msgSend.asm + src/exceptions.c + src/HOTObject.m +) + +add_library(objc STATIC ${LIBOBJC_SOURCES}) + +target_compile_options(objc PRIVATE $<$:${USER_C_FLAGS}>) + +target_include_directories(objc PUBLIC inc) +target_link_libraries(objc PRIVATE term) \ No newline at end of file diff --git a/userspace/libobjc/inc/HOTObject.h b/userspace/libobjc/inc/HOTObject.h new file mode 100644 index 0000000..9924263 --- /dev/null +++ b/userspace/libobjc/inc/HOTObject.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 0xKarinyash + +#pragma once +#include + +typedef struct OBJCClass OBJCClass; +typedef struct OBJCObject OBJCObject; + +typedef OBJCObject* id; +typedef OBJCClass* Class; + +@interface HOTObject { + Class classPointer; +} + ++ (id)alloc; +- (id)init; +- (id)free; + +@end \ No newline at end of file diff --git a/userspace/libobjc/src/HOTObject.m b/userspace/libobjc/src/HOTObject.m new file mode 100644 index 0000000..eeb2a89 --- /dev/null +++ b/userspace/libobjc/src/HOTObject.m @@ -0,0 +1,20 @@ +#import + +extern id OBJCAllocateInstance(Class class); +extern void MemoryFree(void* pointer); + +@implementation HOTObject ++ (id)alloc { + return OBJCAllocateInstance((Class)self); +} + +- (id)init { + return self; +} + +- (id)free { + MemoryFree(self); + return (id)0; +} + +@end \ No newline at end of file diff --git a/userspace/libobjc/src/exceptions.c b/userspace/libobjc/src/exceptions.c new file mode 100644 index 0000000..41242e3 --- /dev/null +++ b/userspace/libobjc/src/exceptions.c @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 0xKarinyash + diff --git a/userspace/libobjc/src/lookup.c b/userspace/libobjc/src/lookup.c new file mode 100644 index 0000000..41242e3 --- /dev/null +++ b/userspace/libobjc/src/lookup.c @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 0xKarinyash + diff --git a/userspace/libobjc/src/msgSend.asm b/userspace/libobjc/src/msgSend.asm new file mode 100644 index 0000000..095bc5f --- /dev/null +++ b/userspace/libobjc/src/msgSend.asm @@ -0,0 +1,59 @@ +; SPDX-License-Identifier: GPL-3.0-or-later +; Copyright (c) 2026 0xKarinyash + +[bits 64] +section .text +global objc_msgSend + +; id objc_msgSend(id self, SEL _cmd, ...) +; RDI = self, RSI = _cmd (pointer on OBJCSelector) + +objc_msgSend: + test rdi, rdi + jz .return_nil + mov rax, [rdi] + +.search_class: + ; OBJClass: + ; 0: metaClass, 8: parentClass, 16: name, 24: version, 32: info, 40: instanceSize, 48: methods + mov r10, [rax + 48] ; R10 = OBJCMethodList* + +.search_method_list: + test r10, r10 + jz .go_to_parent + ; OBJCMethodList: + ; 0: next, 8: methodCount, 12: padding, 16: methods[] + mov ecx, [r10 + 8] ; ECX = methodCount + test ecx, ecx + jz .next_list + + lea r11, [r10 + 16] ; R11 start of methods[] + +.loop_methods: + ; OBJCMethod: + ; 0: selector, 8: types, 16: functionPointer + mov r8, [r11] + cmp r8, rsi + je .found + add r11, 24 + loop .loop_methods + +.next_list: + mov r10, [r10] ; r10 = list->next + jmp .search_method_list + +.go_to_parent: + mov rax, [rax + 8] + test rax, rax + jnz .search_class + + jmp .return_nil + +.found: + mov rax, [r11 + 16] + jmp rax + +.return_nil: + xor rax, rax + xor rdx, rdx + ret diff --git a/userspace/libobjc/src/runtime.c b/userspace/libobjc/src/runtime.c new file mode 100644 index 0000000..1a2f96a --- /dev/null +++ b/userspace/libobjc/src/runtime.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 0xKarinyash + +#include + +typedef struct OBJCSelector OBJCSelector; +typedef struct OBJCMethod OBJCMethod; +typedef struct OBJCMethodList OBJCMethodList; +typedef struct OBJCClass OBJCClass; +typedef struct OBJCObject OBJCObject; +typedef struct OBJCSymbolTable OBJCSymbolTable; +typedef struct OBJCModule OBJCModule; + +struct OBJCSelector { + const char* name; + const char* types; +}; + +struct OBJCMethod { + struct OBJCSelector* selector; + const char* types; + void* functionPointer; // pointer to function +}; + +struct OBJCMethodList { + struct OBJCMethodList* next; + Int32 methodCount; + struct OBJCMethod methods[]; +}; + +struct OBJCClass { + OBJCClass* metaClass; // pointer to metaclass + OBJCClass* parentClass; // parent + const char* name; + Int64 version; + Int64 info; + Int64 instanceSize; + OBJCMethodList* methods; +}; + +struct OBJCObject { + OBJCClass* classPointer; +}; + +struct OBJCSymbolTable { + UInt32 selectorReferencesCount; + OBJCSelector* selectorReferences; + UInt16 classDefinitionCount; + UInt16 categoryDefinitionCount; + void* definitions[]; +}; + +struct OBJCModule { + UInt64 version; + UInt64 size; + const char* name; + OBJCSymbolTable* symbolTable; +}; + +enum { + kOBJCRuntimeMaxClasses = 256 +}; + +static OBJCClass* _classTable[kOBJCRuntimeMaxClasses]; +static UInt32 _classCount = 0; + +static OBJCClass* sOBJCGetClass(const char* name) { + for (UInt32 i = 0; i < _classCount; i++) { + if (StringCompare(_classTable[i]->name, name) == 0) return _classTable[i]; + } + return nullptr; +} + +static void sOBJCRegisterModule(OBJCModule* module) { + if (!module || !module->symbolTable) return; + OBJCSymbolTable* symbolTable = module->symbolTable; + for (UInt16 i = 0; i < symbolTable->classDefinitionCount; i++) { + OBJCClass* class = (OBJCClass*)symbolTable->definitions[i]; + if (_classCount < kOBJCRuntimeMaxClasses) { + _classTable[_classCount++] = class; + } + } +} + +// real + +extern void* __objc_module_list_start; +extern void* __objc_module_list_end; + +OBJCClass* objc_getClass(const char* name) { + return sOBJCGetClass(name); +} + +void __objc_exec_class(OBJCModule* module) { + sOBJCRegisterModule(module); +} + +OBJCClass* objc_lookup_class(const char* name) { + return sOBJCGetClass(name); +} + +void _objc_init_runtime() { + void** currentModule = &__objc_module_list_start; + while (currentModule < &__objc_module_list_end) { + if (*currentModule) __objc_exec_class((OBJCModule*)*currentModule); + currentModule++; + } +} + + +OBJCObject* OBJCAllocateInstance(OBJCClass* class) { + if (!class) return nullptr; + + OBJCObject* instance = (OBJCObject*)MemoryAllocate(class->instanceSize); + if (instance) { + MemorySet(instance, 0, class->instanceSize); + instance->classPointer = class; + } + + return instance; +} \ No newline at end of file diff --git a/userspace/libterm/CMakeLists.txt b/userspace/libterm/CMakeLists.txt index 50cd6f0..7e31827 100644 --- a/userspace/libterm/CMakeLists.txt +++ b/userspace/libterm/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(libterm LANGUAGES C ASM_NASM) +project(libterm LANGUAGES C ASM_NASM OBJC) set(USER_C_FLAGS -ffreestanding @@ -37,18 +37,36 @@ target_compile_options(term PRIVATE $<$:${USER_C_FLAGS}>) target_include_directories(term PUBLIC inc) add_library(RuntimeEntryObject OBJECT src/RuntimeEntry.asm) -target_compile_options(RuntimeEntryObject PRIVATE $<$:${USER_C_FLAGS}>) +add_library(ObjCRuntimeEntryObject OBJECT src/ObjCRuntimeEntry.asm) function(add_termos_executable NAME SOURCES) + set(HAS_OBJC FALSE) + foreach(SRC ${SOURCES}) + get_filename_component(EXT ${SRC} EXT) + if(EXT STREQUAL ".m") + set(HAS_OBJC TRUE) + break() + endif() + endforeach() + add_executable(${NAME} ${SOURCES}) - target_sources(${NAME} PRIVATE $) + if(HAS_OBJC) + target_sources(${NAME} PRIVATE $) + target_compile_options(${NAME} PRIVATE $<$:-fobjc-runtime=gnustep -fblocks>) + target_link_options(${NAME} PRIVATE -T ${libterm_SOURCE_DIR}/linker/objc.ld) + target_link_libraries(${NAME} PRIVATE objc) + else() + target_sources(${NAME} PRIVATE $) + endif() - target_compile_options(${NAME} PRIVATE $<$:${USER_C_FLAGS}>) + target_compile_options(${NAME} PRIVATE + $<$,$>:${USER_C_FLAGS}> + ) + target_link_libraries(${NAME} PRIVATE term) - target_link_options(${NAME} PRIVATE - -T ${libterm_SOURCE_DIR}/linker.ld + -T ${libterm_SOURCE_DIR}/linker/common.ld -nostdlib -static ) diff --git a/userspace/libterm/linker.ld b/userspace/libterm/linker/common.ld similarity index 100% rename from userspace/libterm/linker.ld rename to userspace/libterm/linker/common.ld diff --git a/userspace/libterm/linker/objc.ld b/userspace/libterm/linker/objc.ld new file mode 100644 index 0000000..ecb14a4 --- /dev/null +++ b/userspace/libterm/linker/objc.ld @@ -0,0 +1,28 @@ +SECTIONS +{ + .objc_metadata : + { + . = ALIGN(8); + __objc_runtime_start = .; + + KEEP(*(.objc_info)) + __objc_module_list_start = .; + KEEP(*(.objc_module_info)) + __objc_module_list_end = .; + KEEP(*(.objc_selectors)) + KEEP(*(.objc_classes)) + KEEP(*(.objc_category)) + KEEP(*(.objc_class_refs)) + KEEP(*(.objc_class_names)) + KEEP(*(.objc_method_names)) + KEEP(*(.objc_method_types)) + KEEP(*(.objc_protocol_list)) + KEEP(*(.objc_string_object)) + KEEP(*(.objc_constant_string)) + KEEP(*(.objc_data)) + KEEP(*(.objc_list.*)) + + __objc_runtime_end = .; + } +} +INSERT AFTER .data; \ No newline at end of file diff --git a/userspace/libterm/src/ObjCRuntimeEntry.asm b/userspace/libterm/src/ObjCRuntimeEntry.asm new file mode 100644 index 0000000..402f18e --- /dev/null +++ b/userspace/libterm/src/ObjCRuntimeEntry.asm @@ -0,0 +1,18 @@ +; SPDX-License-Identifier: GPL-3.0-or-later +; Copyright (c) 2026 0xKarinyash + +[bits 64] + +section .text +global _start +extern main +extern OSServiceProcessExit +extern _objc_init_runtime + +_start: + call _objc_init_runtime + + call main + + mov rdi, rax + call OSServiceProcessExit \ No newline at end of file diff --git a/userspace/testOBJC/CMakeLists.txt b/userspace/testOBJC/CMakeLists.txt new file mode 100644 index 0000000..818aef1 --- /dev/null +++ b/userspace/testOBJC/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.20) +project(termOSobjcdbg LANGUAGES OBJC) + +file(GLOB_RECURSE testOBJC_SOURCES "src/*.m") + +add_termos_executable(testOBJC "${testOBJC_SOURCES}") diff --git a/userspace/testOBJC/src/main.m b/userspace/testOBJC/src/main.m new file mode 100644 index 0000000..e2a94f8 --- /dev/null +++ b/userspace/testOBJC/src/main.m @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 0xKarinyash + +#import +#import + +@interface Test : HOTObject +- (void)sayHi; +@end + +@implementation Test +- (void)sayHi { + ConsolePrint("meow"); +} +@end + +int main() { + return 1; + Test *t = [[Test alloc] init]; + [t sayHi]; + return 0; +} \ No newline at end of file