summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-01-26 12:53:37 -0700
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-02-09 23:42:11 -0700
commit9cd5486484653dd8cc42b43bcde45379982c3177 (patch)
tree7705acce61527697f7075c326d219a006542de91
parent55e0167e2ffa3e26ea99ceed30704e21da3d25c8 (diff)
Add libobj, a replacement for libmisc/vcall.h that internally is more like Go
-rw-r--r--CMakeLists.txt1
-rwxr-xr-xbuild-aux/stack.c.gen18
-rw-r--r--libobj/CMakeLists.txt14
-rw-r--r--libobj/include/libobj/obj.h154
l---------libobj/tests/test.h1
-rw-r--r--libobj/tests/test_nest.c73
-rw-r--r--libobj/tests/test_obj.c61
7 files changed, 314 insertions, 8 deletions
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<meth>\S+)\s*=\s*(?P<impl>\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<obj>[^,]+), (?P<meth>[^,)]+)[,)].*")
+ re_call_objcall = re.compile(
+ r"(?:VCALL|LO_CALL)\((?P<obj>[^,]+), (?P<meth>[^,)]+)[,)].*"
+ )
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 <lukeshu@lukeshu.com>
+# 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 "$<$<COMPILE_LANGUAGE:C>:-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 <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBOBJ_OBJ_H_
+#define _LIBOBJ_OBJ_H_
+
+#include <libmisc/macro.h>
+
+/**
+ * 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 <libobj/obj.h>
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <string.h> /* for memcpy() */
+
+#include <libobj/obj.h>
+
+#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 <libobj/obj.h>
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libobj/obj.h>
+
+#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;
+}