Phasor 2.2.0
Stack VM based Programming Language
Loading...
Searching...
No Matches
ffi.cpp
Go to the documentation of this file.
1#include "ffi.hpp"
2#include <vector>
3#include <iostream>
4#include <memory>
5#include <string>
6#include <stdexcept>
7#include <sstream>
8#include <filesystem>
9#if defined(__APPLE__)
10#include <mach-o/dyld.h>
11#elif defined(__linux__)
12#include <unistd.h>
13#endif
14
15#define INSTANCED_FFI(fn) [this](const std::vector<Value> &args, VM *vm) { return this->fn(args, vm); }
16
17#define VM_PRINT(str) vm->setRegister(VM::r0, str); vm->operation(OpCode::PRINT_R, VM::r0);
18
19namespace Phasor
20{
29{
30 switch (c_value.type)
31 {
33 return Phasor::Value();
35 return Phasor::Value(c_value.as.b);
36 case PHASOR_TYPE_INT:
37 return Phasor::Value(c_value.as.i);
39 return Phasor::Value(c_value.as.f);
41 if (c_value.as.s) {
42 return Phasor::Value(c_value.as.s);
43 }
44 return Phasor::Value("");
46 {
47 std::vector<Phasor::Value> cpp_elements;
48 if (c_value.as.a.elements && c_value.as.a.count > 0) {
49 cpp_elements.reserve(c_value.as.a.count);
50 for (size_t i = 0; i < c_value.as.a.count; ++i) {
51 cpp_elements.push_back(from_c_value(c_value.as.a.elements[i]));
52 }
53 }
54 return Phasor::Value::createArray(std::move(cpp_elements));
55 }
56 default:
57 return Phasor::Value();
58 }
59}
60
69static PhasorValue to_c_value(const Phasor::Value& cpp_value,
70 std::vector<std::unique_ptr<char[]>>& string_arena,
71 std::vector<std::unique_ptr<PhasorValue[]>>& array_arena)
72{
73 switch (cpp_value.getType())
74 {
75 case ValueType::Null:
76 return phasor_make_null();
77 case ValueType::Bool:
78 return phasor_make_bool(cpp_value.asBool());
79 case ValueType::Int:
80 return phasor_make_int(cpp_value.asInt());
82 return phasor_make_float(cpp_value.asFloat());
84 {
85 const auto& str = cpp_value.asString();
86 auto c_str = std::make_unique<char[]>(str.length() + 1);
87 std::copy(str.begin(), str.end(), c_str.get());
88 c_str[str.length()] = '\0';
89 PhasorValue val = phasor_make_string(c_str.get());
90 string_arena.push_back(std::move(c_str));
91 return val;
92 }
94 {
95 const auto& cpp_array = *cpp_value.asArray();
96 size_t count = cpp_array.size();
97 if (count == 0) {
98 return phasor_make_array(nullptr, 0);
99 }
100
101 auto c_array = std::make_unique<PhasorValue[]>(count);
102 for (size_t i = 0; i < count; ++i) {
103 c_array[i] = to_c_value(cpp_array[i], string_arena, array_arena);
104 }
105
106 PhasorValue val = phasor_make_array(c_array.get(), count);
107 array_arena.push_back(std::move(c_array));
108 return val;
109 }
110 default:
111 return phasor_make_null();
112 }
113}
114
115// =============================================================================
116// FFI Host Implementation
117// =============================================================================
118
128static Phasor::Value c_native_func_wrapper(PhasorNativeFunction c_func, Phasor::VM* vm, const std::vector<Phasor::Value>& args)
129{
130 std::vector<std::unique_ptr<char[]>> string_arena;
131 std::vector<std::unique_ptr<PhasorValue[]>> array_arena;
132
133 std::vector<PhasorValue> c_args;
134 c_args.reserve(args.size());
135 for (const auto& arg : args)
136 {
137 c_args.push_back(to_c_value(arg, string_arena, array_arena));
138 }
139
140 PhasorValue c_result = c_func(reinterpret_cast<PhasorVM*>(vm), (int)c_args.size(), c_args.data());
141
142 return from_c_value(c_result);
143}
144
152static void register_native_c_func(PhasorVM* vm, const char* name, PhasorNativeFunction func)
153{
154 Phasor::VM* cpp_vm = reinterpret_cast<Phasor::VM*>(vm);
155
156 auto wrapper = [func](const std::vector<Phasor::Value>& args, Phasor::VM* vm_param) -> Phasor::Value
157 {
158 return c_native_func_wrapper(func, vm_param, args);
159 };
160
161 cpp_vm->registerNativeFunction(name, wrapper);
162}
163
167bool FFI::loadPlugin(const std::filesystem::path &library, VM *vm)
168{
169 using PluginEntryFunc = void (*)(const PhasorAPI*, PhasorVM*);
170
171#if defined(_WIN32)
172 HMODULE lib = LoadLibraryA(library.string().c_str());
173 if (!lib) {
174 std::stringstream ss;
175 ss << "FFI Error: Failed to load library " << library.string() << ". Code: " << GetLastError();
176 throw std::runtime_error(ss.str());
177 return false;
178 }
179 auto entry_point = (PluginEntryFunc)GetProcAddress(lib, "phasor_plugin_entry");
180#else
181 void *lib = dlopen(library.string().c_str(), RTLD_NOW);
182 if (!lib) {
183 std::stringstream ss;
184 ss << "FFI Error: Failed to load library " << library.string() << ". Error: " << dlerror();
185 throw std::runtime_error(ss.str());
186 return false;
187 }
188 auto entry_point = (PluginEntryFunc)dlsym(lib, "phasor_plugin_entry");
189#endif
190
191 if (!entry_point)
192 {
193 throw std::runtime_error(std::string("FFI Error: Could not find entry point 'phasor_plugin_entry' in " + library.string()));
194#if defined(_WIN32)
195 FreeLibrary(lib);
196#else
197 dlclose(lib);
198#endif
199 return false;
200 }
201
202 PhasorAPI api;
204
205 entry_point(&api, reinterpret_cast<PhasorVM*>(vm));
206
207 plugins_.push_back(Plugin{lib, library.string(), nullptr});
208 return true;
209}
210
211bool FFI::addPlugin(const std::filesystem::path &pluginPath)
212{
213 return loadPlugin(pluginPath, vm_);
214}
215
219std::vector<std::string> FFI::scanPlugins(const std::filesystem::path &folder)
220{
221 if (folder.empty()) return {};
222
223 std::vector<std::string> plugins;
224 std::filesystem::path exeDir;
225 std::vector<std::filesystem::path> foldersToScan;
226
227#if defined(_WIN32)
228 char path[MAX_PATH];
229 GetModuleFileNameA(nullptr, path, MAX_PATH);
230 exeDir = std::filesystem::path(path).parent_path();
231#elif defined(__APPLE__)
232 char path[1024];
233 uint32_t size = sizeof(path);
234 if (_NSGetExecutablePath(path, &size) == 0)
235 exeDir = std::filesystem::path(path).parent_path();
236 else
237 exeDir = std::filesystem::current_path();
238#else
239 char path[1024];
240 ssize_t count = readlink("/proc/self/exe", path, sizeof(path));
241 if (count != -1)
242 exeDir = std::filesystem::path(std::string(path, count)).parent_path();
243 else
244 exeDir = std::filesystem::current_path();
245#endif
246
247 foldersToScan.push_back(exeDir / folder);
248 if (!std::filesystem::equivalent(exeDir, std::filesystem::current_path())) {
249 foldersToScan.push_back(std::filesystem::current_path() / folder);
250 }
251
252 for (auto &folderPath : foldersToScan)
253 {
254 if (!std::filesystem::exists(folderPath) || !std::filesystem::is_directory(folderPath))
255 continue;
256
257 for (auto &p : std::filesystem::directory_iterator(folderPath))
258 {
259 if (!p.is_regular_file()) continue;
260 auto ext = p.path().extension().string();
261#if defined(_WIN32)
262 if (ext == ".dll" || ext == ".phsp")
263#elif defined(__APPLE__)
264 if (ext == ".dylib" || ext == ".phsp")
265#else
266 if (ext == ".so" || ext == ".phsp")
267#endif
268 plugins.push_back(p.path().string());
269 }
270 }
271 return plugins;
272}
273
278{
279 for (auto &plugin : plugins_)
280 {
281#if defined(_WIN32)
282 FreeLibrary(plugin.handle);
283#else
284 dlclose(plugin.handle);
285#endif
286 }
287 plugins_.clear();
288}
289
290FFI::FFI(const std::filesystem::path &pluginFolder, VM *vm) : pluginFolder_(pluginFolder), vm_(vm)
291{
293 auto plugins = scanPlugins(pluginFolder_);
294 for (const auto &pluginPath : plugins)
295 {
296 try
297 {
298 loadPlugin(pluginPath, vm);
299 }
300 catch (const std::runtime_error &e)
301 {
302 std::cerr << e.what() << std::endl;
303 }
304 }
305}
306
308{
309 unloadAll();
310}
311
312Value FFI::native_add_plugin(const std::vector<Value> &args, VM *vm)
313{
314 if (args.size() != 1)
315 {
316 throw std::runtime_error("load_plugin expects exactly 1 argument: the plugin path.");
317 }
318 std::filesystem::path pluginPath = args[0].asString();
319 if (!std::filesystem::exists(pluginPath))
320 {
321 throw std::runtime_error("Plugin file does not exist: " + pluginPath.string());
322 }
323 return addPlugin(pluginPath);
324}
325} // namespace Phasor
PhasorValue(* PhasorNativeFunction)(PhasorVM *vm, int argc, const PhasorValue *argv)
Definition PhasorFFI.h:148
static PhasorValue phasor_make_array(const PhasorValue *elements, size_t count)
Definition PhasorFFI.h:115
static PhasorValue phasor_make_float(double f)
Definition PhasorFFI.h:101
static PhasorValue phasor_make_string(const char *s)
Definition PhasorFFI.h:108
static PhasorValue phasor_make_null()
Definition PhasorFFI.h:81
static PhasorValue phasor_make_bool(bool b)
Definition PhasorFFI.h:87
static PhasorValue phasor_make_int(int64_t i)
Definition PhasorFFI.h:94
struct PhasorVM PhasorVM
Definition PhasorFFI.h:42
@ PHASOR_TYPE_FLOAT
Definition PhasorFFI.h:49
@ PHASOR_TYPE_NULL
Definition PhasorFFI.h:46
@ PHASOR_TYPE_ARRAY
Definition PhasorFFI.h:51
@ PHASOR_TYPE_STRING
Definition PhasorFFI.h:50
@ PHASOR_TYPE_BOOL
Definition PhasorFFI.h:47
@ PHASOR_TYPE_INT
Definition PhasorFFI.h:48
void unloadAll()
Unloads all currently loaded plugins and clears internal state.
Definition ffi.cpp:277
std::vector< Plugin > plugins_
Loaded plugins.
Definition ffi.hpp:95
std::filesystem::path pluginFolder_
Plugin search folder.
Definition ffi.hpp:96
~FFI()
Destructor. Unloads all loaded plugins.
Definition ffi.cpp:307
std::vector< std::string > scanPlugins(const std::filesystem::path &folder)
Scans a folder for plugin libraries.
Definition ffi.cpp:219
Value native_add_plugin(const std::vector< Value > &args, VM *vm)
Native function to load a plugin at runtime.
Definition ffi.cpp:312
bool loadPlugin(const std::filesystem::path &library, VM *vm)
Loads a single plugin from a library file.
Definition ffi.cpp:167
bool addPlugin(const std::filesystem::path &pluginPath)
Adds a single plugin from the specified path.
Definition ffi.cpp:211
VM * vm_
Pointer to the Phasor VM.
Definition ffi.hpp:97
FFI(const std::filesystem::path &pluginFolder, VM *vm)
Constructs the FFI manager and loads plugins.
Definition ffi.cpp:290
Virtual Machine.
Definition VM.hpp:18
void registerNativeFunction(const std::string &name, NativeFunction fn)
Register a native function.
Definition VM.cpp:869
A value in the Phasor VM.
Definition Value.hpp:33
double asFloat() const
Get the value as a double.
Definition Value.hpp:157
std::string asString() const
Get the value as a string.
Definition Value.hpp:166
std::shared_ptr< ArrayInstance > asArray()
Get the value as an array.
Definition Value.hpp:173
int64_t asInt() const
Get the value as an integer.
Definition Value.hpp:148
ValueType getType() const
Get the type of the value.
Definition Value.hpp:87
bool asBool() const
Get the value as a boolean.
Definition Value.hpp:143
static Value createArray(std::vector< Value > elements={})
Definition Value.hpp:427
#define INSTANCED_FFI(fn)
Definition ffi.cpp:15
The Phasor Programming Language and Runtime.
Definition AST.hpp:8
static Phasor::Value from_c_value(const PhasorValue &c_value)
Converts a C-style FFI value to a C++ VM value.
Definition ffi.cpp:28
static Phasor::Value c_native_func_wrapper(PhasorNativeFunction c_func, Phasor::VM *vm, const std::vector< Phasor::Value > &args)
The "trampoline" that wraps a C function from a plugin.
Definition ffi.cpp:128
static PhasorValue to_c_value(const Phasor::Value &cpp_value, std::vector< std::unique_ptr< char[]> > &string_arena, std::vector< std::unique_ptr< PhasorValue[]> > &array_arena)
Converts a C++ VM value to a C-style FFI value.
Definition ffi.cpp:69
static void register_native_c_func(PhasorVM *vm, const char *name, PhasorNativeFunction func)
The concrete implementation of the PhasorRegisterFunction API call.
Definition ffi.cpp:152
Represents a loaded plugin.
Definition ffi.hpp:30
PhasorRegisterFunction register_function
Definition PhasorFFI.h:157
struct PhasorValue::@257106260015165034006071221344325333366100147341::@324103060207145140324203224012174236216253240107 a
const PhasorValue * elements
Definition PhasorFFI.h:70
const char * s
Definition PhasorFFI.h:67
int64_t i
Definition PhasorFFI.h:65
PhasorValueType type
Definition PhasorFFI.h:62
union PhasorValue::@257106260015165034006071221344325333366100147341 as
size_t count
Definition PhasorFFI.h:71
double f
Definition PhasorFFI.h:66