From 9cd5486484653dd8cc42b43bcde45379982c3177 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Sun, 26 Jan 2025 12:53:37 -0700 Subject: Add libobj, a replacement for libmisc/vcall.h that internally is more like Go --- CMakeLists.txt | 1 + build-aux/stack.c.gen | 18 +++--- libobj/CMakeLists.txt | 14 ++++ libobj/include/libobj/obj.h | 154 ++++++++++++++++++++++++++++++++++++++++++++ libobj/tests/test.h | 1 + libobj/tests/test_nest.c | 73 +++++++++++++++++++++ libobj/tests/test_obj.c | 61 ++++++++++++++++++ 7 files changed, 314 insertions(+), 8 deletions(-) create mode 100644 libobj/CMakeLists.txt create mode 100644 libobj/include/libobj/obj.h create mode 120000 libobj/tests/test.h create mode 100644 libobj/tests/test_nest.c create mode 100644 libobj/tests/test_obj.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ce1a942..b90d000 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ function(apply_matrix _m_arg_action _m_arg_matrix) endfunction() add_subdirectory(libmisc) +add_subdirectory(libobj) add_subdirectory(libcr) add_subdirectory(libcr_ipc) add_subdirectory(libhw_generic) diff --git a/build-aux/stack.c.gen b/build-aux/stack.c.gen index 2a23565..3ef5628 100755 --- a/build-aux/stack.c.gen +++ b/build-aux/stack.c.gen @@ -353,7 +353,7 @@ def main( # The sbc-harness codebase ####################################### - vcalls: dict[str, set[str]] = {} + objcalls: dict[str, set[str]] = {} re_vtable_start = re.compile(r"_vtable\s*=\s*\{") re_vtable_entry = re.compile(r"^\s+\.(?P\S+)\s*=\s*(?P\S+),.*") for fname in c_fnames: @@ -367,9 +367,9 @@ def main( impl = m.group("impl") if impl == "NULL": continue - if m.group("meth") not in vcalls: - vcalls[meth] = set() - vcalls[meth].add(impl) + if m.group("meth") not in objcalls: + objcalls[meth] = set() + objcalls[meth].add(impl) if "}" in line: in_vtable = False elif re_vtable_start.search(line): @@ -401,14 +401,16 @@ def main( typ = m.group("typ") lib9p_msgs.add(typ) - re_call_vcall = re.compile(r"VCALL\((?P[^,]+), (?P[^,)]+)[,)].*") + re_call_objcall = re.compile( + r"(?:VCALL|LO_CALL)\((?P[^,]+), (?P[^,)]+)[,)].*" + ) def sbc_indirect_callees(loc: str, line: str) -> list[str] | None: if "/3rd-party/" in loc: return None - if m := re_call_vcall.fullmatch(line): - if m.group("meth") in vcalls: - return sorted(vcalls[m.group("meth")]) + if m := re_call_objcall.fullmatch(line): + if m.group("meth") in objcalls: + return sorted(objcalls[m.group("meth")]) return [f"__indirect_call:{m.group('obj')}->vtable->{m.group('meth')}"] if "trigger->cb(trigger->cb_arg)" in line: return [ diff --git a/libobj/CMakeLists.txt b/libobj/CMakeLists.txt new file mode 100644 index 0000000..1cc552c --- /dev/null +++ b/libobj/CMakeLists.txt @@ -0,0 +1,14 @@ +# libobj/CMakeLists.txt - A simple Go-ish object system built on GCC -fplan9-extensions +# +# Copyright (C) 2024-2025 Luke T. Shumaker +# SPDX-License-Identifier: AGPL-3.0-or-later + +add_library(libobj INTERFACE) +target_include_directories(libobj SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(libobj INTERFACE + libmisc +) +target_compile_options(libobj INTERFACE "$<$:-fplan9-extensions>") + +add_lib_test(libobj test_obj) +add_lib_test(libobj test_nest) diff --git a/libobj/include/libobj/obj.h b/libobj/include/libobj/obj.h new file mode 100644 index 0000000..d8a528a --- /dev/null +++ b/libobj/include/libobj/obj.h @@ -0,0 +1,154 @@ +/* libobj/obj.h - A simple Go-ish object system + * + * Copyright (C) 2024-2025 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBOBJ_OBJ_H_ +#define _LIBOBJ_OBJ_H_ + +#include + +/** + * Use `lo_interface` similarly to how you would use + * `struct`/`enum`/`union` when writing the type of an interface + * value. + */ +#define lo_interface struct + +/** + * Use `LO_INTERFACE` in a .h file to define an interface. + * + * First define a macro named `{iface_name}_LO_IFACE` consisting of a + * series of calls to LO_NEST and/or LO_FUNC, then call + * `LO_INTERFACE({iface_name})`: + * + * #define myiface_LO_IFACE \ + * LO_NEST(wrapped_iface_name) \ + * LO_FUNC(ret_type, func_name, args...) + * LO_INTERFACE(myiface) + * + * Use `lo_interface {iface_name}` as the type of this interface; it + * should not be a pointer type. + * + * If there are any LO_NEST interfaces, this will define a + * `lo_box_{iface_name}_as_{wrapped_iface_name}(obj)` function for + * each. + */ +#define LO_NEST(_ARG_child_iface_name) \ + (lo_nest, _ARG_child_iface_name) +#define LO_FUNC(_ARG_ret_type, _ARG_func_name, ...) \ + (lo_func, _ARG_ret_type, _ARG_func_name __VA_OPT__(,) __VA_ARGS__) +#define LO_INTERFACE(_ARG_iface_name) \ + typedef struct { \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IFACE_VTABLE) \ + } _lo_##_ARG_iface_name##_vtable; \ + struct _ARG_iface_name { \ + void *self; \ + const _lo_##_ARG_iface_name##_vtable *vtable; \ + }; \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IFACE_PROTO, _ARG_iface_name) +#define _LO_IFACE_VTABLE(_tuple_typ, ...) \ + _LO_IFACE_VTABLE_##_tuple_typ(__VA_ARGS__) +#define _LO_IFACE_VTABLE_lo_nest(_ARG_child_iface_name) \ + _lo_##_ARG_child_iface_name##_vtable; +#define _LO_IFACE_VTABLE_lo_func(_ARG_ret_type, _ARG_func_name, ...) \ + _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); +#define _LO_IFACE_PROTO(_ARG_iface_name, _tuple_typ, ...) \ + _LO_IFACE_PROTO_##_tuple_typ(_ARG_iface_name, __VA_ARGS__) +#define _LO_IFACE_PROTO_lo_nest(_ARG_iface_name, _ARG_child_iface_name) \ + LM_ALWAYS_INLINE static lo_interface _ARG_child_iface_name \ + box_##_ARG_iface_name##_as_##_ARG_child_iface_name(lo_interface _ARG_iface_name obj) { \ + return (lo_interface _ARG_child_iface_name){ \ + .self = obj.self, \ + .vtable = &obj.vtable->_lo_##_ARG_child_iface_name##_vtable, \ + }; \ + } +#define _LO_IFACE_PROTO_lo_func(_ARG_iface_name, _ARG_ret_type, _ARG_func_name, ...) \ + /* empty */ + +/** + * `LO_NULL(iface_name)` is the null/nil/zero value for `lo_interface {iface_name}`. + */ +#define LO_NULL(_ARG_iface_name) ((lo_interface _ARG_iface_name){0}) + +/** + * `LO_IS_NULL(iface_val)` returns whether `iface_val` is LO_NULL. + */ +#define LO_IS_NULL(_ARG_iface_val) ((_ARG_iface_val).vtable == NULL) + +/** + * `LO_IFACE_EQ(a, b)` returns whether the interface values `a` and + * `b` are the same object. + */ +#define LO_EQ(_ARG_iface_val_a, _ARG_iface_val_b) \ + ((_ARG_iface_val_a).self == (_ARG_iface_val_b).self) + +/** + * Use LO_CALL(obj, method_name, args...) to call a method on an `lo_interface`. + */ +#define LO_CALL(_ARG_obj, _ARG_meth, ...) \ + (_ARG_obj).vtable->_ARG_meth((_ARG_obj).self __VA_OPT__(,) __VA_ARGS__) + +/** + * Use `LO_IMPLEMENTATION_H(iface_name, impl_type, impl_name)` in a .h + * file to declare that `{impl_type}` implements the `{iface_name}` + * interface with functions named `{impl_name}_{method_name}`. + * + * This will also define a `lo_box_{impl_name}_as_{iface_name}(obj)` + * function. + * + * You must also call the LO_IMPLEMENTATION_C in a single .c file. + */ +#define LO_IMPLEMENTATION_H(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \ + /* Vtable. */ \ + extern const _lo_##_ARG_iface_name##_vtable \ + _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable; \ + /* Boxing. */ \ + LM_ALWAYS_INLINE static lo_interface _ARG_iface_name \ + lo_box_##_ARG_impl_name##_as_##_ARG_iface_name(_ARG_impl_type *self) { \ + return (lo_interface _ARG_iface_name){ \ + .self = self, \ + .vtable = &_lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable, \ + }; \ + } + +/** + * Use `LO_IMPLEMENTATION_C(iface_name, impl_type, impl_name[, static])` in a .c + * file to declare that `{impl_type}` implements the `{iface_name}` interface + * with functions named `{impl_name}_{method_name}`. + * + * You must also call the LO_IMPLEMENTATION_H in the corresponding .h file. + * + * If `iface_name` contains a nested interface, then the + * implementation of the nested interfaces must be declared with + * `LO_IMPLEMENTATION_C` first. + */ +#define LO_IMPLEMENTATION_C(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name, ...) \ + /* Method prototypes. */ \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IMPL_PROTO, _ARG_impl_type, _ARG_impl_name, __VA_ARGS__) \ + /* Vtable. */ \ + const _lo_##_ARG_iface_name##_vtable \ + _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable = { \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IMPL_VTABLE, _ARG_impl_name) \ + }; \ + +#define _LO_IMPL_PROTO(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _tuple_typ, ...) \ + _LO_IMPL_PROTO_##_tuple_typ(_ARG_impl_type, _ARG_impl_name, _ARG_quals, __VA_ARGS__) +#define _LO_IMPL_PROTO_lo_nest(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_child_iface_name) \ + /* empty */ +#define _LO_IMPL_PROTO_lo_func(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_ret_type, _ARG_func_name, ...) \ + _ARG_quals _ARG_ret_type _ARG_impl_name##_##_ARG_func_name(_ARG_impl_type * __VA_OPT__(,) __VA_ARGS__); + +#define _LO_IMPL_VTABLE(_ARG_impl_name, _tuple_typ, ...) \ + _LO_IMPL_VTABLE_##_tuple_typ(_ARG_impl_name, __VA_ARGS__) +#define _LO_IMPL_VTABLE_lo_nest(_ARG_impl_name, _ARG_child_iface_name) \ + ._lo_##_ARG_child_iface_name##_vtable = _lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, +#define _LO_IMPL_VTABLE_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) \ + ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name, + +#endif /* _LIBOBJ_OBJ_H_ */ diff --git a/libobj/tests/test.h b/libobj/tests/test.h new file mode 120000 index 0000000..2fb1bd5 --- /dev/null +++ b/libobj/tests/test.h @@ -0,0 +1 @@ +../../libmisc/tests/test.h \ No newline at end of file diff --git a/libobj/tests/test_nest.c b/libobj/tests/test_nest.c new file mode 100644 index 0000000..c9f9eba --- /dev/null +++ b/libobj/tests/test_nest.c @@ -0,0 +1,73 @@ +/* libobj/tests/test_nest.c - Tests for + * + * Copyright (C) 2025 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include /* for memcpy() */ + +#include + +#include "test.h" + +/* interfaces *****************************************************************/ + +#define reader_LO_IFACE \ + LO_FUNC(ssize_t, read, void *, size_t) +LO_INTERFACE(reader) + +#define writer_LO_IFACE \ + LO_FUNC(ssize_t, write, void *, size_t) +LO_INTERFACE(writer) + +#define read_writer_LO_IFACE \ + LO_NEST(reader) \ + LO_NEST(writer) +LO_INTERFACE(read_writer) + +/* implementation header ******************************************************/ + +struct myclass { + size_t len; + char buf[512]; +}; +LO_IMPLEMENTATION_H(reader, struct myclass, myclass) +LO_IMPLEMENTATION_H(writer, struct myclass, myclass) +LO_IMPLEMENTATION_H(read_writer, struct myclass, myclass) + +/* implementation main ********************************************************/ + +LO_IMPLEMENTATION_C(reader, struct myclass, myclass, static) +LO_IMPLEMENTATION_C(writer, struct myclass, myclass, static) +LO_IMPLEMENTATION_C(read_writer, struct myclass, myclass, static) + +static ssize_t myclass_read(struct myclass *self, void *buf, size_t count) { + test_assert(self); + if (count > self->len) + count = self->len; + memcpy(buf, self->buf, count); + return count; +} + +static ssize_t myclass_write(struct myclass *self, void *buf, size_t count) { + test_assert(self); + if (self->len) + return -1; + if (count > sizeof(self->buf)) + count = sizeof(self->buf); + memcpy(self->buf, buf, count); + self->len = count; + return count; +} + +/* main test body *************************************************************/ + +int main() { + struct myclass _obj = {0}; + lo_interface read_writer obj = lo_box_myclass_as_read_writer(&_obj); + test_assert(LO_CALL(obj, write, "Hello", 6) == 6); + char buf[6] = {0}; + test_assert(LO_CALL(obj, read, buf, 3) == 3); + test_assert(memcmp(buf, "Hel\0\0\0", 6) == 0); + return 0; +} diff --git a/libobj/tests/test_obj.c b/libobj/tests/test_obj.c new file mode 100644 index 0000000..89fff68 --- /dev/null +++ b/libobj/tests/test_obj.c @@ -0,0 +1,61 @@ +/* libobj/tests/test_obj.c - Tests for + * + * Copyright (C) 2024-2025 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include + +#include "test.h" + +/* `lo_inteface frobber` header ***********************************************/ + +#define frobber_LO_IFACE \ + /** Basic function. */ \ + LO_FUNC(int, frob) \ + /** Function that takes 1 argument. */ \ + LO_FUNC(int, frob1, int) \ + /** Function that returns nothing. */ \ + LO_FUNC(void, frob0) +LO_INTERFACE(frobber) + +/* `struct myclass` header ****************************************************/ + +struct myclass { + int a; +}; +LO_IMPLEMENTATION_H(frobber, struct myclass, myclass) + +/* `struct myclass` implementation ********************************************/ + +LO_IMPLEMENTATION_C(frobber, struct myclass, myclass, static) + +static int myclass_frob(struct myclass *self) { + test_assert(self); + return self->a; +} + +static int myclass_frob1(struct myclass *self, int arg) { + test_assert(self); + return arg; +} + +static void myclass_frob0(struct myclass *self) { + test_assert(self); +} + +/* main test body *************************************************************/ + +#define MAGIC1 909837 +#define MAGIC2 657441 + +int main() { + struct myclass obj = { + .a = MAGIC1, + }; + lo_interface frobber iface = lo_box_myclass_as_frobber(&obj); + test_assert(LO_CALL(iface, frob) == MAGIC1); + test_assert(LO_CALL(iface, frob1, MAGIC2) == MAGIC2); + LO_CALL(iface, frob0); + return 0; +} -- cgit v1.2.3-2-g168b