Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jule: new closures #111

Merged
merged 22 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f822904
api: initialize closures
mertcandav Aug 30, 2024
d9c83ac
compiler: initialize code generation for closures
mertcandav Aug 30, 2024
95d775c
sema: initialize capture analysis for closures
mertcandav Aug 30, 2024
173ec83
sema: caught captured variables for anonymous functions
mertcandav Aug 30, 2024
a3cbd64
compiler: generate ctx for closure
mertcandav Aug 30, 2024
62ea87c
compiler: initialize special scope generation for closure
mertcandav Aug 30, 2024
5973809
compiler: generate ctx-based scope for anonymous functions
mertcandav Aug 30, 2024
bdf39cf
api: fix naming
mertcandav Aug 30, 2024
c327bf0
compiler: initialize closures at entry point
mertcandav Aug 30, 2024
642b9cb
sema: fix captured analysis allows duplications
mertcandav Aug 30, 2024
a1791b6
compiler: minor fix for backend declaration handling
mertcandav Aug 30, 2024
b0ae61e
compiler: minor fix for closure ctx code generation
mertcandav Aug 30, 2024
6cb17cb
sema: minor fix for closure capture analysis
mertcandav Aug 30, 2024
6b9e618
api: minor naming fix
mertcandav Aug 30, 2024
ac59888
sema: minor fix for closure capture analysis
mertcandav Aug 30, 2024
c9b5760
sema, compiler: minor fix for capture analysis and handling of captur…
mertcandav Aug 30, 2024
3ff2529
compiler: minor formatting improvement for closure code generation
mertcandav Aug 30, 2024
2179162
compiler: fix anonymous function handling
mertcandav Aug 30, 2024
aaa1426
api: fix memory handling of closure implementation
mertcandav Aug 30, 2024
05e44b1
runtime: fix GC for closure ctx
mertcandav Aug 30, 2024
f93b5aa
api: minor improvements for closure runtime
mertcandav Aug 30, 2024
30605e8
jule: completing implementation of new closures
mertcandav Aug 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading