Foreign Function Interface
Overview of how the foreign function interface of Loop works.

Introduction

This document will go over the full process from source code to execution. Each step has an explanation of what is happening at why it is needed.

What is an Foreign Function Interface

A foreign function interface (also known as: FFI), is a mechanism with which one program can call and make use of services from other programs. In Loops' case it means that you can import compiled dynamically linked libraries and call functions from the imported library. In theory libraries like OpenCV, QT, etc. could be ported to Loop.

Overview

A quick explanation on how to use the FFI in Loop to give some context.

Syntax

To import a dynamic library you type this:
import "../http" as http
You should not provide a file extension, this is to make it work on Unix and Windows

Library

The library needs to conform to three rules:
  1. 1.
    Following the C application binary interface (ABI)
  2. 2.
    De compiled to a dynamically linked libary
    • .so, for Unix
    • .dll, for Windows
  3. 3.
    Have either a C-style header file or an function inside of the library which returns the string with all the function signatures. The latter is the preferred way of doing it, since it eliminates the need for another file.

Implementation

Loop is a compiled first and than interpreted language. Your source code goes from Loop -> Arc -> Lua and than just-in-time compiled.
We make use of the LuaJIT interpreter, the executable does not have any external dependencies. To compile to Lua and run the code, we make use of MLua it is a Rust library with (mostly) safe bindings to Lua.
The FFI module (which is explained below in more detail) is one of the only major parts which do not have a completely safe implementation.

Loop to Arc

In Arc there is a special instruction for loading of a library, it expects two arguments:
  1. 1.
    Location of the library (currently only supporting relative locations).
  2. 2.
    Namespace of the library
This step is mainly here for two main things. One, checking for syntax errors, and second for type inferrence. All the type inferrence in Loop happens at this step.
In the future Loop might also have inferred imports, that would happen at this step too.

Example

.LOADLIB
{
.CONSTANT CHAR[] "../http"
}
{
.CONSTANT CHAR[] "http"
}

Arc to Lua

Lua by default has a very good FFI to interface to interface with C code. Which was one of the original reasons why we chose to compile to Lua.

Generated Lua Code

First we need to import the library which does all things related FFI in Lua.
ffi = require("ffi")
Lua ffi expects to provide all the signatures from all the libraries you are going to import. Notice that the function signatures are provided in the same style as C headers
ffi.cdef[[
int dub(int i);
]]
You need to load the function inside your Lua program, you use the ffi.load() function for that
lib = ffi.load("lib.dll")
After imported the actual library, you can call the functions you want
res = lib.dub(100)
print(res)
This results in this code being generated
ffi = require("ffi")
ffi.cdef[[
int dub(int i);
]]
f_lib = ffi.load("lib.dll")
res = f_lib.dub(10)
print(res)
Most users will likely not use/see this code (unless they might be debugging or similar), this is to show the code generation process.

Function Signatures and Type Safety

Lua expects you to provide the header of the functions you are going to call, for that we make use or either the function_signatures() function or a header file. These are also needed to keep Loop type safe. Loop inherently does not know what functions there are in a library and how they look like. Using one of these mechanisms, it stays safe.

Future Improvements

A couple of things mentioned in this document are not implemented yet, and there are features which currently are not supported with this implementation regardless.

To be Implemented

  1. 1.
    Looking for *.h files when function_signatures() is not available. This means that libraries not written for Loop are currently not usable.
  2. 2.
    Return and parameter types, due to the current lack of header file parsing we cant do any type checking on these library functions.

New Features

  1. 1.
    Currently it is not possible to call const from library, this is useful for things like Pi or Sine.
  2. 2.
    An abstraction layer between FFI and Loop itself. This would allow for I.E. passing class (without methods) as C structs to function inside the library.
Last modified 1mo ago