Squash commits for public release

This commit is contained in:
2025-02-12 09:54:05 -05:00
commit 7118adc514
1108 changed files with 80873 additions and 0 deletions

29
libs/libobjc/BUILD.gn Normal file
View File

@@ -0,0 +1,29 @@
import("//build/libs/TEMPLATE.gni")
xOS_static_library("libobjc") {
sources = [
"src/NSObject.mm",
"src/class.mm",
"src/init.mm",
"src/memory.mm",
"src/msgsend_$target_arch.S",
"src/selector.mm",
]
include_dirs = [
"include/",
"//libs/libc/include/",
"//libs/",
]
deplibs = [ "libc" ]
configs = [ "//build/libs:libobjcc_flags" ]
# Currently, using Clang+LTO and arm32 as a traget, a weird issue
# happens, when linker can't resolve objc_load_function.
# Such functions contain `und` instruction and leads to a crash.
if (host == "llvm" && target_arch != "arm32") {
cflags = [ "-flto" ]
}
}

View File

@@ -0,0 +1,19 @@
#ifndef _LIBOBJC_NSOBJECT_H
#define _LIBOBJC_NSOBJECT_H
#include <libobjc/runtime.h>
@interface NSObject {
Class isa;
}
+ (id)init;
+ (id)new;
+ (id)alloc;
- (id)init;
- (Class)class;
@end
#endif // _LIBOBJC_NSOBJECT_H

View File

@@ -0,0 +1,17 @@
#ifndef _LIBOBJC_CLASS_H
#define _LIBOBJC_CLASS_H
#include <libobjc/module.h>
#define DISPATCH_TABLE_NOT_INITIALIZED (void*)0x0
bool class_resolve_links(Class cls);
void class_resolve_all_unresolved();
void class_table_init();
void class_add_from_module(struct objc_symtab* symtab);
IMP class_get_implementation(Class cls, SEL sel);
Class objc_getClass(const char* name);
#endif // _LIBOBJC_CLASS_H

View File

@@ -0,0 +1,18 @@
#ifndef _LIBOBJC_HELPERS_H
#define _LIBOBJC_HELPERS_H
#include <stdio.h>
#define OBJC_EXPORT extern "C"
#define OBJC_DEBUG
#ifdef OBJC_DEBUG
#define OBJC_DEBUGPRINT(...) \
printf(__VA_ARGS__); \
fflush(stdout)
#else
#define OBJC_DEBUGPRINT(...) (sizeof(int))
#endif
#endif // _LIBOBJC_HELPERS_H

View File

@@ -0,0 +1,6 @@
#ifndef _LIBOBJC_ISA_H
#define _LIBOBJC_ISA_H
#define ISA_MASK 0xfffffffc
#endif // _LIBOBJC_ISA_H

View File

@@ -0,0 +1,14 @@
#ifndef _LIBOBJC_MEMORY_H
#define _LIBOBJC_MEMORY_H
#include <libobjc/runtime.h>
#include <stdlib.h>
#define objc_malloc(...) malloc(__VA_ARGS__)
#define objc_realloc(...) realloc(__VA_ARGS__)
#define objc_calloc(...) calloc(__VA_ARGS__)
#define objc_free(...) free(__VA_ARGS__)
id alloc_instance(Class cls);
#endif // _LIBOBJC_MEMORY_H

View File

@@ -0,0 +1,6 @@
#ifndef _LIBOBJC_MODULE_H
#define _LIBOBJC_MODULE_H
#include <libobjc/v1/decls.h>
#endif // _LIBOBJC_MODULE_H

View File

@@ -0,0 +1,10 @@
#ifndef _LIBOBJC_OBJC_H
#define _LIBOBJC_OBJC_H
#include <libobjc/helpers.h>
#include <libobjc/runtime.h>
OBJC_EXPORT id _Nullable objc_alloc(Class _Nullable cls);
OBJC_EXPORT id _Nullable objc_alloc_init(Class _Nullable cls);
#endif // _LIBOBJC_OBJC_H

View File

@@ -0,0 +1,21 @@
#ifndef _LIBOBJC_RUNTIME_H
#define _LIBOBJC_RUNTIME_H
#include <libobjc/helpers.h>
#include <libobjc/isa.h>
#include <libobjc/selector.h>
#include <libobjc/v1/decls.h>
#define ROOT_CLASS "NSObject"
OBJC_EXPORT void objc_msgSend(id reciever, SEL sel, ...);
static inline Class object_getClass(id object)
{
if (!object) {
return Nil;
}
return object->get_isa();
}
#endif // _LIBOBJC_RUNTIME_H

View File

@@ -0,0 +1,23 @@
#ifndef _LIBOBJC_SELECTOR_H
#define _LIBOBJC_SELECTOR_H
#include <libobjc/v1/decls.h>
#include <stddef.h>
static inline bool sel_equal(SEL s1, SEL s2)
{
if (s1 == NULL || s2 == NULL) {
return s1 == s2;
}
return s1->id == s2->id;
}
void selector_table_init();
void selector_add_from_module(struct objc_selector*);
void selector_add_from_method_list(struct objc_method_list*);
void selector_add_from_class(Class);
bool selector_is_valid(SEL sel);
SEL sel_registerName(const char* name);
SEL sel_registerTypedName(const char* name, const char* types);
#endif // _LIBOBJC_SELECTOR_H

View File

@@ -0,0 +1,139 @@
#ifndef _LIBOBJC_V1_DECLS_H
#define _LIBOBJC_V1_DECLS_H
#include <stdint.h>
struct objc_class;
struct objc_object;
struct objc_selector;
struct objc_method;
typedef struct objc_class* Class;
typedef struct objc_object* id;
typedef struct objc_selector* SEL;
typedef struct objc_method* Method;
typedef void (*IMP)(id, SEL, ...);
#define nil_method (IMP) NULL
#define nil (id) NULL
#define Nil (Class) NULL
struct objc_symtab {
unsigned long sel_ref_cnt;
struct objc_selector* refs;
unsigned short cls_def_cnt;
unsigned short cat_def_cnt;
void* defs[1]; // Variable len
};
struct objc_module {
unsigned long version;
unsigned long size;
const char* name;
struct objc_symtab* symtab;
};
struct objc_object final {
Class isa;
inline void set_isa(Class nisa) { isa = nisa; }
inline Class get_isa() { return isa; }
};
struct objc_ivar {
const char* ivar_name;
const char* ivar_type;
int ivar_offset;
};
struct objc_ivar_list {
int ivar_count;
struct objc_ivar ivar_list[1]; // Variable len
};
struct objc_method {
SEL method_name;
const char* method_types;
IMP method_imp;
};
struct objc_method_description {
SEL name;
char* types;
};
struct objc_method_list {
struct objc_method_list* method_next;
int method_count;
struct objc_method method_list[1]; // Variable len
};
struct objc_method_description_list {
int count;
struct objc_method_description list[1]; // Variable len
};
struct objc_protocol {
struct objc_class* class_pointer;
char* protocol_name;
struct objc_protocol_list* protocol_list;
struct objc_method_description_list* instance_methods;
struct objc_method_description_list* class_methods;
};
struct objc_protocol_list {
struct objc_protocol_list* next;
uint32_t count;
struct objc_protocol* list[1]; // Variable len
};
#define CLS_CLASS 0x1
#define CLS_META 0x2
#define CLS_INITIALIZED 0x4
#define CLS_RESOLVED 0x8 // This means that it has had correct super and sublinks assigned
#define CLS_INCONSTRUCTION 0x10
#define CLS_NUMBER_OFFSET 16 // Long is 32bit long on our target, we use 16bit for number
struct objc_class final {
Class isa;
Class superclass;
const char* name;
long version;
unsigned long info;
long instance_size;
struct objc_ivar_list* ivars;
struct objc_method_list* methods;
void* disp_table;
struct objc_class* subclass_list; // TODO: Currently unresolved
struct objc_class* sibling_class; // TODO: Currently unresolved
struct objc_protocol_list* protocols;
void* gc_object_type;
inline void set_isa(Class nisa) { isa = nisa; }
inline Class get_isa() { return isa; }
inline void set_info(unsigned long mask) { info = mask; }
inline void add_info(unsigned long mask) { info |= mask; }
inline void rem_info(unsigned long mask) { info &= (~mask); }
inline bool has_info(unsigned long mask) { return ((info & mask) == mask); }
inline unsigned long get_info() { return info; }
inline bool is_class() { return has_info(CLS_CLASS); }
inline bool is_meta() { return has_info(CLS_META); }
inline bool is_resolved() { return has_info(CLS_RESOLVED); }
inline bool is_initialized() { return has_info(CLS_INITIALIZED); }
inline bool is_root() { return (bool)superclass; }
inline void set_number(int num) { info = (info & ((1 << CLS_NUMBER_OFFSET) - 1)) | (num << CLS_NUMBER_OFFSET); }
inline int number() { return get_info() >> CLS_NUMBER_OFFSET; }
inline long size() { return instance_size; }
inline long aligned_size() { return instance_size; } // FIXME
};
struct objc_selector {
void* id;
const char* types;
};
#endif // _LIBOBJC_V1_DECLS_H

View File

@@ -0,0 +1,61 @@
#include <libfoundation/NSObject.h>
#include <libobjc/class.h>
#include <libobjc/memory.h>
#include <libobjc/objc.h>
#include <libobjc/runtime.h>
OBJC_EXPORT IMP objc_msg_lookup(id receiver, SEL sel)
{
IMP impl = class_get_implementation(receiver->get_isa(), sel);
return impl;
}
static inline id call_alloc(Class cls, bool checkNil, bool allocWithZone = false)
{
if (allocWithZone) {
return ((id(*)(id, SEL, void*))objc_msgSend)(cls, @selector(allocWithZone:), NULL);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
// Called with [Class alloc]
OBJC_EXPORT id objc_alloc(Class cls)
{
return call_alloc(cls, true, false);
}
// Called with [[Class alloc] init]
OBJC_EXPORT id objc_alloc_init(Class cls)
{
return [call_alloc(cls, true, false) init];
}
@implementation NSObject
+ (id)init
{
return (id)self;
}
- (id)init
{
// Init root classes
return (id)self;
}
+ (id)alloc
{
return alloc_instance(self);
}
+ (id)new
{
return [call_alloc(self, false) init];
}
- (Class)class
{
return object_getClass(self);
}
@end

295
libs/libobjc/src/class.mm Normal file
View File

@@ -0,0 +1,295 @@
#include <assert.h>
#include <libobjc/class.h>
#include <libobjc/memory.h>
#include <libobjc/module.h>
#include <libobjc/objc.h>
#include <libobjc/runtime.h>
#include <libobjc/selector.h>
#include <string.h>
struct class_node {
const char* name;
int length;
Class cls;
};
// FIXME: Allocate it dynamically.
static class_node class_tabel_storage[512];
static int class_table_next_free = 0;
static Class unresolved_classes[128];
static int unresolved_classes_next = 0;
void class_table_init()
{
}
Class class_table_find(const char* name)
{
int len = strlen(name);
for (int i = 0; i < class_table_next_free; i++) {
if (len == class_tabel_storage[i].length) {
if (strcmp(name, class_tabel_storage[i].name) == 0) {
return class_tabel_storage[i].cls;
}
}
}
return Nil;
}
static void class_table_add(const char* name, Class cls)
{
int place = class_table_next_free++;
class_tabel_storage[place].name = name;
class_tabel_storage[place].length = strlen(name);
class_tabel_storage[place].cls = cls;
}
bool class_add(Class cls)
{
Class fnd = class_table_find(cls->name);
if (fnd) {
return false;
} else {
static int next_class_num = 1;
cls->set_number(next_class_num);
cls->get_isa()->set_number(next_class_num);
class_table_add(cls->name, cls);
next_class_num++;
return true;
}
}
static inline void class_disp_table_preinit(Class cls)
{
cls->disp_table = DISPATCH_TABLE_NOT_INITIALIZED;
}
static Method class_lookup_method_in_list(struct objc_method_list* objc_method_list, SEL sel)
{
if (!objc_method_list) {
return (Method)NULL;
}
if (!selector_is_valid(sel)) {
sel = sel_registerTypedName((char*)sel->id, sel->types);
}
while (objc_method_list) {
for (int i = 0; i < objc_method_list->method_count; i++) {
SEL method_name = objc_method_list->method_list[i].method_name;
if (method_name && method_name->id == sel->id) {
return &objc_method_list->method_list[i];
}
}
objc_method_list = objc_method_list->method_next;
}
return (Method)NULL;
}
static Method class_lookup_method_in_hierarchy(Class cls, SEL sel)
{
if (!selector_is_valid(sel)) {
sel = sel_registerTypedName((char*)sel->id, sel->types);
}
Method method;
for (Class cli = cls; cli; cli = cli->superclass) {
method = class_lookup_method_in_list(cli->methods, sel);
if (method) {
return method;
}
}
return (Method)NULL;
}
// Add instance methods only to root class.
static void class_root_add_instance_methods(Class cls)
{
int max_methods_allocated = 8;
struct objc_method_list* new_list = (struct objc_method_list*)objc_malloc(sizeof(struct objc_method_list) + sizeof(struct objc_method) * max_methods_allocated);
struct objc_method_list* objc_method_list = cls->methods;
new_list->method_count = 0;
while (objc_method_list) {
for (int i = 0; i < objc_method_list->method_count; i++) {
SEL method_name = objc_method_list->method_list[i].method_name;
if (method_name) {
// The instance method isn't a class method yet, so add it.
if (!class_lookup_method_in_list(cls->get_isa()->methods, method_name)) {
new_list->method_list[new_list->method_count++] = objc_method_list->method_list[i];
if (new_list->method_count == max_methods_allocated) {
max_methods_allocated += 8;
new_list = (struct objc_method_list*)objc_realloc(new_list, sizeof(struct objc_method_list) + sizeof(struct objc_method) * max_methods_allocated);
}
}
}
}
objc_method_list = objc_method_list->method_next;
}
if (new_list->method_count) {
new_list->method_next = cls->get_isa()->methods;
cls->get_isa()->methods = new_list;
} else {
objc_free(new_list);
}
// TODO: Update dispatch table.
}
static void class_send_initialize(Class cls)
{
assert(cls->is_class() && !cls->is_meta());
if (!cls->is_initialized()) {
cls->add_info(CLS_INITIALIZED);
if (cls->superclass) {
class_send_initialize(cls->superclass);
}
SEL sel = sel_registerName("initialize");
Method method = class_lookup_method_in_hierarchy(cls->get_isa(), sel);
if (method) {
OBJC_DEBUGPRINT("start [%s +initialize]\n", cls->name);
(*method->method_imp)((id)cls, sel);
OBJC_DEBUGPRINT("end [%s +initialize]\n", cls->name);
} else {
OBJC_DEBUGPRINT("class %s has no +initialize\n", cls->name);
}
}
}
bool class_can_resolve(Class cls)
{
if (cls->is_resolved()) {
return true;
}
if (!objc_getClass(ROOT_CLASS)) {
return false;
}
const char* superclass = (char*)cls->superclass;
return !superclass || (superclass && objc_getClass(superclass));
}
bool class_resolve_links(Class cls)
{
assert(cls->is_class());
if (cls->is_resolved()) {
return true;
}
Class object_class = objc_getClass(ROOT_CLASS);
if (!object_class) {
return false;
}
cls->get_isa()->set_isa(object_class);
if (!cls->superclass) {
cls->superclass = nil;
cls->get_isa()->superclass = nil;
cls->set_info(CLS_RESOLVED);
cls->get_isa()->set_info(CLS_RESOLVED);
return true;
}
Class supcls = objc_getClass((char*)cls->superclass);
if (supcls) {
// TODO: Fill subclass list
cls->superclass = supcls;
cls->get_isa()->superclass = supcls->get_isa();
cls->set_info(CLS_RESOLVED);
cls->get_isa()->set_info(CLS_RESOLVED);
return true;
}
return false;
}
void class_resolve_all_unresolved()
{
for (int i = 0; i < unresolved_classes_next; i++) {
class_resolve_links(unresolved_classes[i]);
}
}
bool class_init(Class cls)
{
if (class_add(cls)) {
selector_add_from_class(cls);
selector_add_from_class(cls->get_isa());
class_disp_table_preinit(cls);
class_disp_table_preinit(cls->get_isa());
// TODO: Init methods and dispatch tables.
if (cls->is_root()) {
class_root_add_instance_methods(cls);
}
class_resolve_links(cls);
return true;
}
return false;
}
Class objc_getClass(const char* name)
{
Class cls = class_table_find(name);
return cls;
}
void class_add_from_module(struct objc_symtab* symtab)
{
for (int i = 0; i < symtab->cls_def_cnt; i++) {
Class cls = (Class)symtab->defs[i];
// Fix clang flags
if (cls->is_class()) {
cls->set_info(CLS_CLASS);
} else {
cls->set_info(CLS_META);
}
OBJC_DEBUGPRINT("Installing classes %x (%d of %d): %s :: %d\n", (uintptr_t)cls, i + 1, symtab->cls_def_cnt, cls->name, cls->get_info());
if (class_init(cls)) {
if (!class_can_resolve(cls)) {
unresolved_classes[unresolved_classes_next++] = cls;
}
}
}
}
OBJC_EXPORT Class objc_lookup_class(const char* name)
{
return objc_getClass(name);
}
IMP class_get_implementation(Class cls, SEL sel)
{
// TODO: Can't init it here, since meta classes are passed here.
// if (!cls->is_initialized()) {
// class_send_initialize(cls);
// }
sel = sel_registerTypedName((char*)sel->id, sel->types);
Method method = class_lookup_method_in_hierarchy(cls, sel);
if (!method) {
return nil_method;
}
// TODO: Message forwarding
return method->method_imp;
}

31
libs/libobjc/src/init.mm Normal file
View File

@@ -0,0 +1,31 @@
#include <libobjc/class.h>
#include <libobjc/module.h>
#include <libobjc/objc.h>
#include <libobjc/runtime.h>
// The function is called by a constructor of each module.
OBJC_EXPORT void __objc_exec_class(struct objc_module* module)
{
static bool prepared_data_structures = false;
OBJC_DEBUGPRINT("Called __objc_exec_class, starting to init module\n");
if (!prepared_data_structures) {
selector_table_init();
class_table_init();
prepared_data_structures = true;
}
struct objc_symtab* symtab = module->symtab;
struct objc_selector* selectors = symtab->refs;
if (selectors) {
selector_add_from_module(selectors);
}
class_add_from_module(symtab);
// TODO: Many things to init here.
class_resolve_all_unresolved();
}

View File

@@ -0,0 +1,9 @@
#include <libobjc/memory.h>
id alloc_instance(Class cls)
{
size_t sz = cls->size();
id obj = (id)objc_malloc(sz);
obj->set_isa(cls);
return obj;
}

View File

@@ -0,0 +1,11 @@
.extern objc_msg_lookup
.global objc_msgSend
objc_msgSend:
push {r0-r3}
push {lr}
bl objc_msg_lookup
pop {lr}
mov r12, r0 // imp pointer
pop {r0-r3}
bx r12

View File

@@ -0,0 +1,19 @@
.extern objc_msg_lookup
.global objc_msgSend
objc_msgSend:
sub sp, sp, #0x50
stp x0, x1, [sp]
stp x2, x3, [sp, #0x10]
stp x4, x5, [sp, #0x20]
stp x6, x7, [sp, #0x30]
stp x29, x30, [sp, #0x40]
bl objc_msg_lookup
mov x9, x0 // imp pointer
ldp x0, x1, [sp]
ldp x2, x3, [sp, #0x10]
ldp x4, x5, [sp, #0x20]
ldp x6, x7, [sp, #0x30]
ldp x29, x30, [sp, #0x40]
add sp, sp, #0x50
br x9

View File

@@ -0,0 +1,5 @@
.extern objc_msg_lookup
.global objc_msgSend
objc_msgSend:

View File

@@ -0,0 +1,6 @@
extern objc_msg_lookup
global objc_msgSend
objc_msgSend:
call objc_msg_lookup
jmp [eax]

View File

@@ -0,0 +1,18 @@
extern objc_msg_lookup
global objc_msgSend
objc_msgSend:
push rdi
push rsi
push rdx
push rcx
push r8
push r9
call objc_msg_lookup
pop r9
pop r8
pop rcx
pop rdx
pop rsi
pop rdi
jmp [rax]

View File

@@ -0,0 +1,109 @@
#include <libobjc/memory.h>
#include <libobjc/objc.h>
#include <libobjc/runtime.h>
#include <libobjc/selector.h>
#include <string.h>
static struct objc_selector* selector_pool_start;
static struct objc_selector* selector_pool_next;
#define CONST_DATA true
#define VOLATILE_DATA false
static SEL selector_table_add(const char* name, const char* types, bool const_data)
{
// Checking if we have this selector
for (struct objc_selector* cur_sel = selector_pool_start; cur_sel != selector_pool_next; cur_sel++) {
if (strcmp(name, (char*)cur_sel->id) == 0) {
if (cur_sel->types == 0 || types == 0) {
if (cur_sel->types == types) {
return (SEL)cur_sel;
}
} else {
if (strcmp(types, cur_sel->types) == 0) {
return (SEL)cur_sel;
}
}
}
}
SEL sel = (SEL)selector_pool_next++;
if (const_data) {
sel->id = (char*)name;
sel->types = types;
} else {
int name_len = strlen(name);
char* name_data = (char*)objc_malloc(name_len + 1);
memcpy(name_data, name, name_len);
name_data[name_len] = '\0';
sel->id = name_data;
sel->types = 0;
if (types) {
int types_len = strlen(types);
char* types_data = (char*)objc_malloc(types_len + 1);
memcpy(types_data, types, types_len);
types_data[types_len] = '\0';
sel->types = types_data;
}
}
return sel;
}
bool selector_is_valid(SEL sel)
{
return (uintptr_t)selector_pool_start < (uintptr_t)sel && (uintptr_t)sel < (uintptr_t)selector_pool_next;
}
// TODO: We currently do it really stupid
void selector_table_init()
{
selector_pool_start = selector_pool_next = (struct objc_selector*)objc_malloc(1024);
}
void selector_add_from_module(struct objc_selector* selectors)
{
for (int i = 0; selectors[i].id; i++) {
char* name = (char*)selectors[i].id;
const char* types = selectors[i].types;
SEL sel = selector_table_add(name, types, CONST_DATA);
}
}
void selector_add_from_method_list(struct objc_method_list* method_list)
{
for (int i = 0; i < method_list->method_count; i++) {
Method method = &method_list->method_list[i];
if (method->method_name) {
char* name = (char*)method->method_name;
const char* types = method->method_types;
method->method_name = selector_table_add(name, types, CONST_DATA);
}
}
}
void selector_add_from_class(Class cls)
{
for (struct objc_method_list* ml = cls->methods; ml; ml = ml->method_next) {
selector_add_from_method_list(ml);
}
}
SEL sel_registerName(const char* name)
{
if (!name) {
return (SEL)NULL;
}
return selector_table_add(name, 0, VOLATILE_DATA);
}
SEL sel_registerTypedName(const char* name, const char* types)
{
if (!name || !types) {
return (SEL)NULL;
}
return selector_table_add(name, types, VOLATILE_DATA);
}