Skip to content

Commit

Permalink
Merge pull request #111 from julelang/closure
Browse files Browse the repository at this point in the history
jule: new closures
  • Loading branch information
mertcandav authored Aug 30, 2024
2 parents 2b7d38d + 30605e8 commit 2f32a98
Show file tree
Hide file tree
Showing 8 changed files with 487 additions and 129 deletions.
264 changes: 203 additions & 61 deletions api/fn.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,109 +7,109 @@

#include <string>
#include <cstddef>
#include <functional>
#include <thread>

#ifdef OS_WINDOWS
#include <synchapi.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif

#include "types.hpp"
#include "error.hpp"
#include "panic.hpp"
#include "platform.hpp"

#define __JULE_CO_SPAWN(ROUTINE) \
(std::thread{ROUTINE})
#define __JULE_CO(EXPR) \
(__JULE_CO_SPAWN([=](void) mutable -> void { EXPR; }).detach())

namespace jule
{
#ifdef OS_WINDOWS
#define __JULE_CLOSURE_MTX_INIT() InitializeSRWLock(&jule::__closure_mtx)
#define __JULE_CLOSURE_MTX_LOCK() AcquireSRWLockExclusive(&jule::__closure_mtx)
#define __JULE_CLOSURE_MTX_UNLOCK() ReleaseSRWLockExclusive(&jule::__closure_mtx)
#else
#define __JULE_CLOSURE_MTX_INIT() pthread_mutex_init(&jule::__closure_mtx, 0)
#define __JULE_CLOSURE_MTX_LOCK() pthread_mutex_lock(&jule::__closure_mtx)
#define __JULE_CLOSURE_MTX_UNLOCK() pthread_mutex_unlock(&jule::__closure_mtx)
#endif

// std::function wrapper of JuleC.
template <typename>
struct Fn;
#define __JULE_ASSUMED_PAGE_SIZE 0x4000
#define __JULE_CLOSURE_SIZE (((sizeof(void *) << 1 > sizeof(jule::__closure_thunk) ? sizeof(void *) << 1 : sizeof(jule::__closure_thunk)) + sizeof(void *) - 1) & ~(sizeof(void *) - 1))

template <typename T, typename... U>
jule::Uintptr addr_of_fn(std::function<T(U...)> f) noexcept;
#define __JULE_CLOSURE_PAGE_PTR(closure) \
((void **)(closure - __JULE_ASSUMED_PAGE_SIZE))

template <typename Function>
namespace jule
{
// std::function wrapper of JuleC.
template <typename Ret, typename... Args>
struct Fn
{
public:
std::function<Function> buffer;
jule::Uintptr _addr;
Ret (*f)(Args...);
jule::Ptr<jule::Uintptr> ctx; // Closure ctx.
void (*ctxHandler)(jule::Ptr<jule::Uintptr> &alloc) = nullptr;

Fn(void) = default;
Fn(const Fn<Function> &fn) = default;
Fn(const Fn<Ret, Args...> &) = default;
Fn(std::nullptr_t) : Fn() {}

Fn(const std::function<Function> &function) noexcept
Fn(Ret (*f)(Args...)) noexcept
{
this->_addr = jule::addr_of_fn(function);
if (this->_addr == 0)
this->_addr = (jule::Uintptr)(&function);
this->buffer = function;
this->f = f;
}

Fn(const Function *function) noexcept
~Fn(void) noexcept
{
this->buffer = function;
this->_addr = jule::addr_of_fn(this->buffer);
if (this->_addr == 0)
this->_addr = (jule::Uintptr)(function);
this->f = nullptr;
if (this->ctxHandler)
{
this->ctxHandler(this->ctx);
this->ctxHandler = nullptr;
this->ctx.ref = nullptr; // Disable GC for allocation.
this->ctx = nullptr; // Assign as nullptr safely.
}
}

template <typename... Arguments>
auto call(
Ret call(
#ifndef __JULE_ENABLE__PRODUCTION
const char *file,
#endif
Arguments... arguments)
Args... args)
{
#ifndef __JULE_DISABLE__SAFETY
if (this->buffer == nullptr)
if (this->f == nullptr)
#ifndef __JULE_ENABLE__PRODUCTION
jule::panic((std::string(__JULE_ERROR__INVALID_MEMORY) + "\nfile: ") + file);
#else
jule::panic(__JULE_ERROR__INVALID_MEMORY);
#endif // PRODUCTION
#endif // SAFETY
return this->buffer(arguments...);
return this->f(args...);
}

template <typename... Arguments>
inline auto operator()(Arguments... arguments)
inline auto operator()(Args... args)
{
#ifndef __JULE_ENABLE__PRODUCTION
return this->call<Arguments...>("/api/fn.hpp", arguments...);
return this->call<Args...>("/api/fn.hpp", args...);
#else
return this->call<Arguments...>(arguments...);
return this->call<Args...>(args...);
#endif
}

constexpr jule::Uintptr addr(void) const noexcept
inline Fn<Ret, Args...> &operator=(std::nullptr_t) noexcept
{
return this->_addr;
}

inline Fn<Function> &operator=(std::nullptr_t) noexcept
{
this->buffer = nullptr;
return *this;
}

inline Fn<Function> &operator=(const std::function<Function> &function)
{
this->buffer = function;
return *this;
}

inline Fn<Function> &operator=(const Function &function)
{
this->buffer = function;
this->f = nullptr;
return *this;
}

constexpr jule::Bool operator==(std::nullptr_t) const noexcept
{
return this->buffer == nullptr;
return this->f == nullptr;
}

constexpr jule::Bool operator!=(std::nullptr_t) const noexcept
Expand All @@ -118,24 +118,166 @@ namespace jule
}

friend std::ostream &operator<<(std::ostream &stream,
const Fn<Function> &src) noexcept
const Fn<Ret, Args...> &f) noexcept
{
if (src == nullptr)
if (f == nullptr)
return (stream << "<nil>");
return (stream << (void *)src._addr);
return (stream << (void *)f.f);
}
};

template <typename T, typename... U>
jule::Uintptr addr_of_fn(std::function<T(U...)> f) noexcept
static jule::Uint __page_size = __JULE_ASSUMED_PAGE_SIZE;

#if defined(ARCH_AMD64)
static const char __closure_thunk[] = {
0xF3, 0x44, 0x0F, 0x7E, 0x3D, 0xF7, 0xBF, 0xFF, 0xFF, // movq xmm15, QWORD PTR [rip - userdata]
0xFF, 0x25, 0xF9, 0xBF, 0xFF, 0xFF // jmp QWORD PTR [rip - fn]
};
static char __closure_get_ctx_bytes[] = {
0xE0, 0x03, 0x11, 0xAA, // mov x0, x17
0xC0, 0x03, 0x5F, 0xD6 // ret
};
#elif defined(ARCH_I386)
static char __closure_thunk[] = {
0xe8, 0x00, 0x00, 0x00, 0x00, // call here
// here:
0x59, // pop ecx
0x66, 0x0F, 0x6E, 0xF9, // movd xmm7, ecx
0xff, 0xA1, 0xff, 0xbf, 0xff, 0xff, // jmp DWORD PTR [ecx - 0x4001] # <fn>
};
static char __closure_get_ctx_bytes[] = {
0x66, 0x0F, 0x7E, 0xF8, // movd eax, xmm7
0x8B, 0x80, 0xFB, 0xBF, 0xFF, 0xFF, // mov eax, DWORD PTR [eax - 0x4005]
0xc3 // ret
};
#elif defined(ARCH_ARM64)
static char __closure_thunk[] = {
0x11, 0x00, 0xFE, 0x58, // ldr x17, userdata
0x30, 0x00, 0xFE, 0x58, // ldr x16, fn
0x00, 0x02, 0x1F, 0xD6 // br x16
};
static char __closure_get_ctx_bytes[] = {
0xE0, 0x03, 0x11, 0xAA, // mov x0, x17
0xC0, 0x03, 0x5F, 0xD6 // ret
};
#endif

static jule::U8 *__closure_ptr = 0;
static jule::Int __closure_cap = 0;

static void *(*__closure_get_ctx)(void) = nullptr;

#ifdef OS_WINDOWS
static SRWLOCK __closure_mtx;
inline void __closure_mtx_init(void) noexcept { InitializeSRWLock(&jule::__closure_mtx) }
inline void __closure_mtx_lock(void) noexcept { AcquireSRWLockExclusive(&jule::__closure_mtx) }
inline void __closure_mtx_unlock(void) noexcept { ReleaseSRWLockExclusive(&jule::__closure_mtx) }
#else
static pthread_mutex_t __closure_mtx;

inline void __closure_mtx_init(void) noexcept
{
if (pthread_mutex_init(&jule::__closure_mtx, 0) != 0)
jule::panic("closure mutex initialization failed");
}

inline void __closure_mtx_lock(void) noexcept
{
if (pthread_mutex_lock(&jule::__closure_mtx) != 0)
jule::panic("closure mutex locking failed");
}

inline void __closure_mtx_unlock(void) noexcept
{
if (pthread_mutex_unlock(&jule::__closure_mtx) != 0)
jule::panic("closure mutex unlocking failed");
}
#endif

static void __closure_alloc(void) noexcept
{
typedef T(FnType)(U...);
FnType **fn_ptr = f.template target<FnType *>();
if (!fn_ptr)
return 0;
return (jule::Uintptr)(*fn_ptr);
#ifdef OS_WINDOWS
jule::U8 *p = (jule::U8 *)VirtualAlloc(NULL, jule::__page_size << 1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!p)
jule::panic(__JULE_ERROR__MEMORY_ALLOCATION_FAILED
"\nruntime: heap allocation failed for closure");
#else
jule::U8 *p = (jule::U8 *)mmap(0, jule::__page_size << 1, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (p == MAP_FAILED)
jule::panic(__JULE_ERROR__MEMORY_ALLOCATION_FAILED
"\nruntime: heap allocation failed for closure");
#endif
jule::U8 *x = p + jule::__page_size;
jule::Uint rem = jule::__page_size / __JULE_CLOSURE_SIZE;
jule::__closure_ptr = x;
jule::__closure_cap = rem;
while (rem > 0)
{
(void)memcpy(x, jule::__closure_thunk, sizeof(jule::__closure_thunk));
rem--;
x += __JULE_CLOSURE_SIZE;
}
#ifdef OS_WINDOWS
DWORD temp;
VirtualProtect(jule::__closure_ptr, jule::__page_size, PAGE_EXECUTE_READ, &temp);
#else
(void)mprotect(jule::__closure_ptr, jule::__page_size, PROT_READ | PROT_EXEC);
#endif
}

template <typename Ret, typename... Args>
jule::Fn<Ret, Args...> __new_closure(void *fn, jule::Ptr<jule::Uintptr> ctx, void (*ctxHandler)(jule::Ptr<jule::Uintptr> &)) noexcept
{
__JULE_CLOSURE_MTX_LOCK();
if (jule::__closure_cap < 1)
jule::__closure_alloc();
jule::__closure_cap--;
jule::U8 *closure = jule::__closure_ptr;
jule::__closure_ptr += __JULE_CLOSURE_SIZE;
void **ptr = __JULE_CLOSURE_PAGE_PTR(closure);
ptr[0] = ctx;
ptr[1] = fn;
__JULE_CLOSURE_MTX_UNLOCK();
Ret (*static_closure)(Args...) = (Ret(*)(Args...))closure;
jule::Fn<Ret, Args...> fn2(static_closure);
fn2.ctx = std::move(ctx);
fn2.ctxHandler = ctxHandler;
ctx = nullptr;
return fn2;
}

#ifdef OS_WINDOWS
void __closure_init(void) noexcept
{
SYSTEM_INFO si;
GetNativeSystemInfo(&si);
const uint32_t page_size = si.dwPageSize * (((jule::__page_size - 1) / si.dwPageSize) + 1);
jule::__page_size = page_size;
jule::__closure_alloc();
DWORD temp;
VirtualProtect(jule::__closure_ptr, page_size, PAGE_READWRITE, &temp);
(void)memcpy(jule::__closure_ptr, jule::__closure_get_ctx_bytes, sizeof(jule::__closure_get_ctx_bytes));
VirtualProtect(jule::__closure_ptr, page_size, PAGE_EXECUTE_READ, &temp);
jule::__closure_get_ctx = (void *(*)(void))jule::__closure_ptr;
jule::__closure_ptr += __JULE_CLOSURE_SIZE;
jule::__closure_cap--;
}
#else
void __closure_init(void) noexcept
{
uint32_t page_size = sysconf(_SC_PAGESIZE);
// page_size must initialized with relevant expression before multiplication.
page_size *= (((__JULE_ASSUMED_PAGE_SIZE - 1) / page_size) + 1);
jule::__page_size = page_size;
jule::__closure_alloc();
(void)mprotect(jule::__closure_ptr, page_size, PROT_READ | PROT_WRITE);
(void)memcpy(jule::__closure_ptr, jule::__closure_get_ctx_bytes, sizeof(jule::__closure_get_ctx_bytes));
(void)mprotect(jule::__closure_ptr, page_size, PROT_READ | PROT_EXEC);
jule::__closure_get_ctx = (void *(*)(void))jule::__closure_ptr;
jule::__closure_ptr += __JULE_CLOSURE_SIZE;
jule::__closure_cap--;
}
#endif
} // namespace jule

#endif // ifndef __JULE_FN_HPP
#endif // ifndef __JULE_FN_HPP
40 changes: 33 additions & 7 deletions src/julec/obj/cxx/expr.jule
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ enum compExprModel: type {

struct exprCoder {
oc: &ObjectCoder

// It will executed before common variable handling algorithm.
// If it returns true, common algorithm will not be executed.
varPrefixes: []fn(mut v: &Var): bool
}

impl exprCoder {
Expand Down Expand Up @@ -280,6 +284,11 @@ impl exprCoder {
}

fn var(mut &self, mut m: &Var) {
for _, prefix in self.varPrefixes {
if prefix(m) {
ret
}
}
if m.Binded {
d := findDirective(m.Directives, Directive.Namespace)
if d != nil {
Expand Down Expand Up @@ -918,13 +927,30 @@ impl exprCoder {
}

fn anonFunc(mut &self, mut m: &AnonFnExprModel) {
self.oc.tc.func(self.oc.Buf, m.Func)
self.oc.write("([=]")
self.oc.paramsIns(m.Func.Params)
self.oc.write(" mutable -> ")
self.oc.tc.funcInsResult(self.oc.Buf, m.Func)
self.oc.write(" ")
self.oc.sc.funcScope(m.Func)
ident := self.oc.pushAnonFn(m)
self.oc.write("jule::__new_closure<")
self.oc.tc.anonFunc(self.oc.Buf, m.Func)
self.oc.write(">((void*)")
self.oc.write(ident)
self.oc.write(", jule::Ptr<")
self.oc.write(ident)
self.oc.write(anonFnCtxSuffix)
self.oc.write(">::make(")
self.oc.write(ident)
self.oc.write(anonFnCtxSuffix)
self.oc.write("{")
for (i, mut v) in m.Captured {
self.oc.write(".")
identCoder.var(self.oc.Buf, v)
self.oc.write("=")
self.var(v)
if len(m.Captured)-i > 1 {
self.oc.write(",")
}
}
self.oc.write("}).as<" + typeCoder.Uintptr + ">(), ")
self.oc.write(ident)
self.oc.write(anonFnCtxHandlerSuffix)
self.oc.write(")")
}

Expand Down
Loading

0 comments on commit 2f32a98

Please sign in to comment.