Problem
Perry currently generates all LLVM symbol names automatically using the pattern perry_fn_<module_prefix>__<function_name>. There is no mechanism for TypeScript code to control the exported symbol name.
This limitation affects multiple use cases beyond plugins:
| Use Case |
What is Needed |
Plugins (perry/plugin) |
Export plugin_activate, plugin_deactivate, perry_plugin_abi_version with exact names |
| C/C++ FFI |
Export functions that C can call by a known name |
| WASM |
Human-readable export names instead of __wasm_func_<idx> |
| Embedding |
Other languages need to find functions by name via dlsym or equivalent |
| Dynamic loading |
Any scenario requiring runtime symbol lookup |
Concrete Example: Plugin System Gap
The documentation (docs/src/plugins/creating-plugins.md) states:
Perry automatically:
- Generates
perry_plugin_abi_version() returning the current ABI version
- Generates
plugin_activate(api_handle) calling your activate() function
- Generates
plugin_deactivate() calling your deactivate() function
However, the codegen does not emit these symbols. What actually gets exported:
# From nm -D theme.dylib
perry_module_init
perry_fn_theme_plugin_dylib_ts__activate # wrong name
perry_fn_theme_plugin_dylib_ts__deactivate # wrong name
__perry_wrap_perry_fn_theme_plugin_dylib_ts__activate
What the runtime expects (crates/perry-runtime/src/plugin.rs:507-528):
let activate_sym = CString::new("plugin_activate").unwrap();
let activate_ptr = libc::dlsym(handle, activate_sym.as_ptr());
if activate_ptr.is_null() {
eprintln!("[plugin] No plugin_activate symbol in {}", path_str);
return 0; // Plugin fails to load
}
Root Cause
The codegen in crates/perry-codegen/src/codegen/entry.rs only emits perry_module_init for dylib output (line 133). There is no mechanism to:
- Detect that the module exports
activate/deactivate functions
- Generate wrapper functions with specific symbol names
- Allow users to control symbol names in general
The scoped_fn_name helper (crates/perry-codegen/src/codegen/helpers.rs:111-112) hardcodes the naming pattern:
pub(super) fn scoped_fn_name(module_prefix: &str, hir_name: &str) -> String {
format!("perry_fn_{}__{}", module_prefix, sanitize(hir_name))
}
Questions
Before proposing a solution, I would like feedback on:
-
Is this a recognized gap? The documentation promises auto-generated plugin symbols, but they are not implemented.
-
What should the general capability be? This is not just a plugin problem -- it is a general FFI/embedding limitation. Should Perry provide a way to control exported symbol names?
-
What approach fits Perry's design? Some options:
- TypeScript decorator (e.g.
@exportName("symbol_name"))
- Manifest configuration (e.g.
perry.toml export map)
- Compiler directive or special comment
- Something else entirely
-
Should plugin-specific wrappers be auto-generated (as documented) or explicit (user-controlled via the general mechanism)?
Context
I am working on a plugin system for a text editor and encountered this gap. I implemented a working runtime plugin system using QuickJS (rquickjs) as a workaround, but compiled plugins would be preferable for type safety and performance.
Impact
Without this capability:
- The documented plugin system does not work on any platform
- Users cannot create FFI boundaries with custom symbol names
- WASM exports remain index-based (
__wasm_func_<idx>)
- Embedding Perry-compiled code requires knowing the mangled symbol names
With this capability:
- Plugin system works as documented
- General FFI/embedding use cases are enabled
- WASM can have readable export names
- Users have full control over their binary interface
Problem
Perry currently generates all LLVM symbol names automatically using the pattern
perry_fn_<module_prefix>__<function_name>. There is no mechanism for TypeScript code to control the exported symbol name.This limitation affects multiple use cases beyond plugins:
perry/plugin)plugin_activate,plugin_deactivate,perry_plugin_abi_versionwith exact names__wasm_func_<idx>dlsymor equivalentConcrete Example: Plugin System Gap
The documentation (
docs/src/plugins/creating-plugins.md) states:However, the codegen does not emit these symbols. What actually gets exported:
What the runtime expects (
crates/perry-runtime/src/plugin.rs:507-528):Root Cause
The codegen in
crates/perry-codegen/src/codegen/entry.rsonly emitsperry_module_initfor dylib output (line 133). There is no mechanism to:activate/deactivatefunctionsThe
scoped_fn_namehelper (crates/perry-codegen/src/codegen/helpers.rs:111-112) hardcodes the naming pattern:Questions
Before proposing a solution, I would like feedback on:
Is this a recognized gap? The documentation promises auto-generated plugin symbols, but they are not implemented.
What should the general capability be? This is not just a plugin problem -- it is a general FFI/embedding limitation. Should Perry provide a way to control exported symbol names?
What approach fits Perry's design? Some options:
@exportName("symbol_name"))perry.tomlexport map)Should plugin-specific wrappers be auto-generated (as documented) or explicit (user-controlled via the general mechanism)?
Context
I am working on a plugin system for a text editor and encountered this gap. I implemented a working runtime plugin system using QuickJS (
rquickjs) as a workaround, but compiled plugins would be preferable for type safety and performance.Impact
Without this capability:
__wasm_func_<idx>)With this capability: