Repository: lkl/linux
Pull request #590: lkl: remove string functions duplicate implementation
Author: login: tavip
Created: 2025-04-09T00:00:18Z | Merged: 2025-04-13T03:29:17Z

Now that we have the ability to set kernel config options per host built we can avoid duplicating the implementation for string functions that may be provided by the host (e.g. memcpy, memset).

Fixes: #582 


Diff:
diff --git a/arch/lkl/Kconfig b/arch/lkl/Kconfig
index 25c228533887c7..cd207fc912f9dd 100644
--- a/arch/lkl/Kconfig
+++ b/arch/lkl/Kconfig
@@ -182,3 +182,24 @@ config BUILTIN_CMDLINE
 	  image and used at boot time. The command line provided to
 	  lkl_start_kernel is appended to this string to form the full
 	  command line.
+
+config LKL_HOST_MEMCPY
+	bool "Host provides memcpy"
+	default n
+	help
+	  This options should be set (in tools/lkl/Makefile.autoconf)
+	  if the host provides a memcpy implementation.
+
+config LKL_HOST_MEMSET
+	bool "Host provides memset"
+	default n
+	help
+	  This options should be set (in tools/lkl/Makefile.autoconf)
+	  if the host provides a memset implementation.
+
+config LKL_HOST_MEMMOVE
+	bool "Host provides memmove"
+	default n
+	help
+	  This options should be set (in tools/lkl/Makefile.autoconf)
+	  if the host provides a memmove implementation.
diff --git a/arch/lkl/include/asm/string.h b/arch/lkl/include/asm/string.h
index 44f151537fb21f..31157992bf6e69 100644
--- a/arch/lkl/include/asm/string.h
+++ b/arch/lkl/include/asm/string.h
@@ -7,70 +7,35 @@
 
 /* use __mem* names to avoid conflict with KASAN's mem* functions. */
 
+#ifdef CONFIG_LKL_HOST_MEMCPY
 #define __HAVE_ARCH_MEMCPY
 extern void *memcpy(void *dest, const void *src, size_t count);
 static inline void *__memcpy(void *dest, const void *src, size_t count)
 {
-	char *tmp = dest;
-	const char *s = src;
-
-	if (lkl_ops->memcpy)
-		return lkl_ops->memcpy(dest, src, count);
-
-	/* from lib/string.c */
-
-	while (count--)
-		*tmp++ = *s++;
-	return dest;
+	return lkl_ops->memcpy(dest, src, count);
 }
+#define memcpy(dst, src, len) __memcpy(dst, src, len)
+#endif
 
+#ifdef CONFIG_LKL_HOST_MEMSET
 #define __HAVE_ARCH_MEMSET
 extern void *memset(void *s, int c, size_t count);
 static inline void *__memset(void *s, int c, size_t count)
 {
-	char *xs = s;
-
-	if (lkl_ops->memset)
-		return lkl_ops->memset(s, c, count);
-
-	/* from lib/string.c */
-
-	while (count--)
-		*xs++ = c;
-	return s;
+	return lkl_ops->memset(s, c, count);
 }
+#define memset(s, c, n) __memset(s, c, n)
+#endif
 
+#ifdef CONFIG_LKL_HOST_MEMSET
 #define __HAVE_ARCH_MEMMOVE
 extern void *memmove(void *dest, const void *src, size_t count);
 static inline void *__memmove(void *dest, const void *src, size_t count)
 {
-	char *tmp;
-	const char *s;
-
-	if (lkl_ops->memmove)
-		return lkl_ops->memmove(dest, src, count);
-
-	/* from lib/string.c */
-
-	if (dest <= src) {
-		tmp = dest;
-		s = src;
-		while (count--)
-			*tmp++ = *s++;
-	} else {
-		tmp = dest;
-		tmp += count;
-		s = src;
-		s += count;
-		while (count--)
-			*--tmp = *--s;
-	}
-	return dest;
+	return lkl_ops->memmove(dest, src, count);
 }
-
-#define memcpy(dst, src, len) __memcpy(dst, src, len)
-#define memset(s, c, n) __memset(s, c, n)
 #define memmove(dst, src, len) __memmove(dst, src, len)
+#endif
 
 #if defined(CONFIG_KASAN)
 
diff --git a/arch/lkl/kernel/init.c b/arch/lkl/kernel/init.c
index d3d7804acf04d1..a527514a7da2ed 100644
--- a/arch/lkl/kernel/init.c
+++ b/arch/lkl/kernel/init.c
@@ -4,6 +4,13 @@
 
 int lkl_init(struct lkl_host_operations *ops)
 {
+	if ((IS_ENABLED(CONFIG_LKL_HOST_MEMCPY) && !ops->memcpy)
+	 || (IS_ENABLED(CONFIG_LKL_HOST_MEMSET) && !ops->memset)
+	 || (IS_ENABLED(CONFIG_LKL_HOST_MEMMOVE) && !ops->memmove)) {
+		lkl_printf("unexpected NULL lkl_host_ops member\n");
+		return -1;
+	}
+
 	lkl_ops = ops;
 
 	return kasan_init();
diff --git a/arch/lkl/lib/string.c b/arch/lkl/lib/string.c
index a19dc9d8e9385b..bc32ae822a4007 100644
--- a/arch/lkl/lib/string.c
+++ b/arch/lkl/lib/string.c
@@ -14,22 +14,28 @@
 #undef memset
 #undef memmove
 
+#ifdef __HAVE_ARCH_MEMCPY
 __visible void *memcpy(void *dest, const void *src, size_t count)
 {
 	return __memcpy(dest, src, count);
 }
 EXPORT_SYMBOL(memcpy);
+#endif
 
+#ifdef __HAVE_ARCH_MEMSET
 __visible void *memset(void *s, int c, size_t count)
 {
 	return __memset(s, c, count);
 }
 EXPORT_SYMBOL(memset);
+#endif
 
+#ifdef __HAVE_ARCH_MEMMOVE
 __visible void *memmove(void *dest, const void *src, size_t count)
 {
 	return __memmove(dest, src, count);
 }
 EXPORT_SYMBOL(memmove);
+#endif
 
 #endif
diff --git a/tools/lkl/Makefile.autoconf b/tools/lkl/Makefile.autoconf
index 1ebb23f96e274c..36f24cfd05c814 100644
--- a/tools/lkl/Makefile.autoconf
+++ b/tools/lkl/Makefile.autoconf
@@ -89,6 +89,9 @@ define posix_host
   $(if $(filter $(1),elf32-i386),$(call set_autoconf_var,I386,y))
   $(if $(strip $(call find_include,jsmn.h)),$(call set_autoconf_var,JSMN,y))
   $(if $(filter %,$(zpoline)),$(call zpoline_conf,$(zpoline)))
+  $(call set_kernel_config,LKL_HOST_MEMCPY,y)
+  $(call set_kernel_config,LKL_HOST_MEMSET,y)
+  $(call set_kernel_config,LKL_HOST_MEMMOVE,y)
 endef
 
 define nt64_host
diff --git a/tools/lkl/tests/boot.c b/tools/lkl/tests/boot.c
index 1aafde024b40a2..b97dfd609261f9 100644
--- a/tools/lkl/tests/boot.c
+++ b/tools/lkl/tests/boot.c
@@ -780,7 +780,10 @@ int main(int argc, const char **argv)
 
 	lkl_host_ops.print = lkl_test_log;
 
-	lkl_init(&lkl_host_ops);
+	if (lkl_init(&lkl_host_ops) < 0) {
+		printf("%s\n", lkl_test_get_log());
+		return 1;
+	}
 
 	ret = lkl_test_run(tests, sizeof(tests)/sizeof(struct lkl_test),
 			"boot");
diff --git a/tools/lkl/tests/disk-vfio-pci.c b/tools/lkl/tests/disk-vfio-pci.c
index 7fd95a4543b242..b8659d5f0e68fc 100644
--- a/tools/lkl/tests/disk-vfio-pci.c
+++ b/tools/lkl/tests/disk-vfio-pci.c
@@ -109,7 +109,10 @@ int main(int argc, const char **argv)
 
 	lkl_host_ops.print = lkl_test_log;
 
-	lkl_init(&lkl_host_ops);
+	if (lkl_init(&lkl_host_ops) < 0) {
+		printf("%s\n", lkl_test_get_log());
+		return 1;
+	}
 
 	ret = lkl_test_run(tests, sizeof(tests) / sizeof(struct lkl_test),
 			"disk-vfio-pci %s", cla.fstype);
diff --git a/tools/lkl/tests/disk.c b/tools/lkl/tests/disk.c
index 57e30ca8de7e03..5e1a6113bfa72f 100644
--- a/tools/lkl/tests/disk.c
+++ b/tools/lkl/tests/disk.c
@@ -189,7 +189,10 @@ int main(int argc, const char **argv)
 
 	lkl_host_ops.print = lkl_test_log;
 
-	lkl_init(&lkl_host_ops);
+	if (lkl_init(&lkl_host_ops) < 0) {
+		printf("%s\n", lkl_test_get_log());
+		return 1;
+	}
 
 	ret = lkl_test_run(tests, sizeof(tests)/sizeof(struct lkl_test),
 			"disk %s", cla.fstype);


Reviews:
user: login: ddiss
body: Looks like a nice cleanup. One concern: `lkl_ops` is set at run-time, so we could have a "posix" host call `lkl_init()` with e.g. `lkl_host_operations.memset = NULL`.
It looks as though the main reason for using run-time ops instead of build-time is for overloading the `print()` hook, so perhaps we could switch the API to use build-time ops and provide `print()` hooks elsewhere? Another (simpler) option would be to fail `lkl_init()` if any of the mem ops are NULL alongside `CONFIG_LKL_HOST_MEM*=y`.
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-09T01:07:01Z
user: login: thehajime
body: lgtm, would wait for @rodionov 's input. 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-10T01:50:37Z
user: login: ddiss
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-10T02:24:09Z
user: login: rodionov
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-10T03:52:48Z
user: login: rodionov
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-10T03:57:10Z
user: login: rodionov
state: APPROVED
author_association: NONE
submitted_at: 2025-04-10T03:59:55Z
user: login: tavip
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T16:48:06Z
user: login: tavip
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T16:48:17Z
user: login: ddiss
body: Thanks @tavip ! Looks good
state: APPROVED
author_association: NONE
submitted_at: 2025-04-12T08:21:58Z
user: login: tavip
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-13T01:48:08Z

Review comments:
diff_hunk: @@ -6,6 +6,25 @@ int lkl_init(struct lkl_host_operations *ops)
 {
 	lkl_ops = ops;
path: arch/lkl/kernel/init.c
user: login: ddiss
body: Please put this after the config checks. I don't think `lkl_ops` should be initialized on failure...
_(edit: kasan_init() failure should probably leave it unset too, but that can be done separately)_
created_at: 2025-04-10T02:18:16Z
author_association: NONE
diff_hunk: @@ -6,6 +6,25 @@ int lkl_init(struct lkl_host_operations *ops)
 {
 	lkl_ops = ops;
 
+#ifdef CONFIG_LKL_HOST_MEMCPY
+	if (!ops->memcpy) {
+		lkl_printf("unexpected NULL lkl_host_ops member: memcpy\n");
+		return -1;
+	}
+#endif
+#ifdef CONFIG_LKL_HOST_MEMSET
+	if (!ops->memset) {
+		lkl_printf("unexpected NULL lkl_host_ops member: memset\n");
+		return -1;
+	}
+#endif
+#ifdef CONFIG_LKL_HOST_MEMMOVE
+	if (!ops->memmove) {
+		lkl_printf("unexpected NULL lkl_host_ops member: memmove\n");
+		return -1;
+	}
+#endif
+
path: arch/lkl/kernel/init.c
user: login: ddiss
body: coding-style.rst encourages avoiding `#ifdef CONFIG_...` outside of header files. How about something like:
```
        if ((IS_ENABLED(CONFIG_LKL_HOST_MEMCPY) && !ops->memcpy)
         || (IS_ENABLED(CONFIG_LKL_HOST_MEMSET) && !ops->memset)
         || (IS_ENABLED(CONFIG_LKL_HOST_MEMMOVE) && !ops->memmove)) {
                lkl_printf("unexpected NULL lkl_host_ops memory I/O call\n");
                return -1;
        }
```
The compiler should still be able to optimize it out for non-posix hosts.
created_at: 2025-04-10T02:23:06Z
author_association: NONE
diff_hunk: @@ -7,70 +7,35 @@
 
 /* use __mem* names to avoid conflict with KASAN's mem* functions. */
 
+#ifdef CONFIG_LKL_HOST_MEMCPY
 #define __HAVE_ARCH_MEMCPY
 extern void *memcpy(void *dest, const void *src, size_t count);
path: arch/lkl/include/asm/string.h
user: login: rodionov
body: I wonder if need this declaration here of memcpy function provided that we define memcpy macro below? If not, may be delete the declaration of the function alltogether? 

The same is applicable for memset and memmove functions.
created_at: 2025-04-10T03:52:48Z
author_association: NONE
diff_hunk: @@ -7,70 +7,35 @@
 
 /* use __mem* names to avoid conflict with KASAN's mem* functions. */
 
+#ifdef CONFIG_LKL_HOST_MEMCPY
 #define __HAVE_ARCH_MEMCPY
 extern void *memcpy(void *dest, const void *src, size_t count);
path: arch/lkl/include/asm/string.h
user: login: rodionov
body: Ah, I see it is actually used in arch/lkl/lib/string.c.
created_at: 2025-04-10T03:57:10Z
author_association: NONE
diff_hunk: @@ -6,6 +6,25 @@ int lkl_init(struct lkl_host_operations *ops)
 {
 	lkl_ops = ops;
path: arch/lkl/kernel/init.c
user: login: tavip
body: Done, thanks !
created_at: 2025-04-10T16:48:05Z
author_association: MEMBER
diff_hunk: @@ -6,6 +6,25 @@ int lkl_init(struct lkl_host_operations *ops)
 {
 	lkl_ops = ops;
 
+#ifdef CONFIG_LKL_HOST_MEMCPY
+	if (!ops->memcpy) {
+		lkl_printf("unexpected NULL lkl_host_ops member: memcpy\n");
+		return -1;
+	}
+#endif
+#ifdef CONFIG_LKL_HOST_MEMSET
+	if (!ops->memset) {
+		lkl_printf("unexpected NULL lkl_host_ops member: memset\n");
+		return -1;
+	}
+#endif
+#ifdef CONFIG_LKL_HOST_MEMMOVE
+	if (!ops->memmove) {
+		lkl_printf("unexpected NULL lkl_host_ops member: memmove\n");
+		return -1;
+	}
+#endif
+
path: arch/lkl/kernel/init.c
user: login: tavip
body: Done, thanks!
created_at: 2025-04-10T16:48:17Z
author_association: MEMBER
diff_hunk: @@ -4,6 +4,15 @@
 
 int lkl_init(struct lkl_host_operations *ops)
 {
+	int ret;
path: arch/lkl/kernel/init.c
user: login: ddiss
body: nit: `ret` is unused
created_at: 2025-04-12T08:21:18Z
author_association: NONE
diff_hunk: @@ -4,6 +4,15 @@
 
 int lkl_init(struct lkl_host_operations *ops)
 {
+	int ret;
path: arch/lkl/kernel/init.c
user: login: tavip
body: Fixed, thanks @ddiss !
created_at: 2025-04-13T01:48:08Z
author_association: MEMBER

PR comments:
user: login: tavip
created_at: 2025-04-10T00:25:47Z
body: > One concern: lkl_ops is set at run-time, so we could have a "posix" host call lkl_init() with e.g. lkl_host_operations.memset = NULL.

Good catch!

> It looks as though the main reason for using run-time ops instead of build-time is for overloading the print() hook, so perhaps we could switch the API to use build-time ops and provide print() hooks elsewhere? 

The original intent was to have LKL distributed as a shared library and allow external applications to override run-time ops. In practice, because we lack support for modules the former is not yet achievable.

On one hand having runtime host ops gives more flexibility to applications, on the other hand  built-time host ops would probably give a nice performance boost.

> Another (simpler) option would be to fail lkl_init() if any of the mem ops are NULL alongside CONFIG_LKL_HOST_MEM*=y.

Yes, this is a good option while we discuss more built-in vs run-time host ops. I'll send another version to add these checks.
author_association: MEMBER
user: login: rodionov
created_at: 2025-04-10T03:58:49Z
body: Thank you, @tavip! lgtm pending @ddiss's comments.
author_association: NONE