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

Very WIP: [wasm] Bidirectional Julia/JS object access #32791

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ include(string((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "version_git.jl")) #
include("osutils.jl")
include("c.jl")

Sys.isjsvm() && include("jsobject.jl")

# Core I/O
include("io.jl")
include("iostream.jl")
Expand Down
74 changes: 74 additions & 0 deletions base/jsobject.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module JS

using .Base.Meta
import .Base: convert

export @jscall

abstract type JSBoxed end
primitive type JSObject <: JSBoxed 32 end
primitive type JSString <: JSBoxed 32 end
primitive type JSSymbol <: JSBoxed 32 end
struct JSUndefined; end
struct JSNull; end

const JSNumber = Float64

const undefined = JSUndefined.instance
const null = JSNull.instance

const JSAny = Union{JSBoxed, Float64, JSUndefined, JSNull}

macro jscall(expr)
isexpr(expr, :call) || error("@jscall argument must be a call")
target_expr = expr.args[1]
if isa(target_expr, Symbol)
target = QuoteNode(target_expr)
else
if !isexpr(target_expr, :(.)) ||
!isa(target_expr.args[1], Symbol) ||
!(isa(target_expr.args[2], QuoteNode) &&
isa(target_expr.args[2].value, Symbol))
error("Unsupported call target `$(target_expr)`")
end
target = (target_expr.args[2].value, target_expr.args[1])
end
b = Expr(:block)
converted_args = Symbol[]
for a in expr.args[2:end]
x = gensym()
push!(b.args, :($x = jsconvert($(esc(a)))))
push!(converted_args, x)
end
atypes = [JSAny for _ in converted_args]
quote
$b
$(Expr(:foreigncall, target, JSAny, Core.svec(atypes...), QuoteNode(:jscall),
length(atypes), converted_args...))
end
end

jsconvert(x) = convert(JSAny, x)

function getproperty(this::JSObject, sym::Symbol)
@jscall Reflect.get(this, sym)
end

function setproperty!(this::JSObject, sym::Symbol, val)
@jscall Reflect.set(this, sym, val)
end

# Yes, we're converting a pointer to Float64 here, but at least in
# compiled code, we basically expect the compiler on both sides to
# undo this conversion.
jsconvert(x::Ptr) = convert(JSNumber, convert(UInt32, x))
jsconvert(x::AbstractString) = convert(JSString, x)

function convert(::Type{JSString}, x::String)
@GC.preserve x begin
ptr = pointer(x)
@jscall UTF8ToString(ptr)
end
end

end
3 changes: 3 additions & 0 deletions src/datatype.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ jl_datatype_t *jl_new_uninitialized_datatype(void)
{
jl_ptls_t ptls = jl_get_ptls_states();
jl_datatype_t *t = (jl_datatype_t*)jl_gc_alloc(ptls, sizeof(jl_datatype_t), jl_datatype_type);
// We require 16 byte alignment for datatypes, since we are using the low
// bits of pointers to them for GC bits.
// assert( ((uintptr_t)t & ~(uintptr_t)15) == 0 );
t->hasfreetypevars = 0;
t->isdispatchtuple = 0;
t->isbitstype = 0;
Expand Down
10 changes: 5 additions & 5 deletions src/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ static bigval_t **sweep_big_list(int sweep_full, bigval_t **pv) JL_NOTSAFEPOINT
bigval_t *nxt = v->next;
int bits = v->bits.gc;
int old_bits = bits;
if (gc_marked(bits)) {
if (gc_alive(v->bits)) {
pv = &v->next;
int age = v->age;
if (age >= PROMOTE_AGE || bits == GC_OLD_MARKED) {
Expand Down Expand Up @@ -1069,8 +1069,8 @@ static void sweep_malloced_arrays(void) JL_NOTSAFEPOINT
mallocarray_t **pma = &ptls2->heap.mallocarrays;
while (ma != NULL) {
mallocarray_t *nxt = ma->next;
int bits = jl_astaggedvalue(ma->a)->bits.gc;
if (gc_marked(bits)) {
struct _jl_taggedvalue_bits bits = jl_astaggedvalue(ma->a)->bits;
if (gc_alive(bits)) {
pma = &ma->next;
}
else {
Expand All @@ -1080,7 +1080,7 @@ static void sweep_malloced_arrays(void) JL_NOTSAFEPOINT
ma->next = ptls2->heap.mafreelist;
ptls2->heap.mafreelist = ma;
}
gc_time_count_mallocd_array(bits);
gc_time_count_mallocd_array(bits.gc);
ma = nxt;
}
}
Expand Down Expand Up @@ -1263,7 +1263,7 @@ static jl_taggedvalue_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_t
uint8_t msk = 1; // mask for the age bit in the current age byte
while ((char*)v <= lim) {
int bits = v->bits.gc;
if (!gc_marked(bits)) {
if (!gc_alive(v->bits)) {
*pfl = v;
pfl = &v->next;
pfl_begin = pfl_begin ? pfl_begin : pfl;
Expand Down
9 changes: 6 additions & 3 deletions src/gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,7 @@ JL_EXTENSION typedef struct _bigval_t {
//struct jl_taggedvalue_t <>;
union {
uintptr_t header;
struct {
uintptr_t gc:2;
} bits;
struct _jl_taggedvalue_bits bits;
};
// must be 64-byte aligned here, in 32 & 64 bit modes
} bigval_t;
Expand Down Expand Up @@ -401,6 +399,11 @@ STATIC_INLINE int gc_marked(uintptr_t bits) JL_NOTSAFEPOINT
return (bits & GC_MARKED) != 0;
}

STATIC_INLINE int gc_alive(struct _jl_taggedvalue_bits bits) JL_NOTSAFEPOINT
{
return gc_marked(bits.gc) || bits.gc_held;
}

STATIC_INLINE int gc_old(uintptr_t bits) JL_NOTSAFEPOINT
{
return (bits & GC_OLD) != 0;
Expand Down
75 changes: 75 additions & 0 deletions src/interpreter.c
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,51 @@ SECT_INTERP static void eval_stmt_value(jl_value_t *stmt, interpreter_state *s)
s->locals[jl_source_nslots(s->src) + s->ip] = res;
}

jl_sym_t *get_sym_or_global_if_const(jl_value_t *arg)
{
if (jl_is_globalref(arg)) {
jl_module_t *mod = jl_globalref_mod(arg);
jl_sym_t *sym = jl_globalref_name(arg);
if (jl_is_const(mod, sym)) {
jl_value_t *val = jl_get_global(mod, sym);
if (jl_is_symbol(val)) {
return (jl_sym_t *)val;
}
return NULL;
}
return NULL;
} else if (jl_is_quotenode(arg)) {
if (!jl_is_symbol(jl_quotenode_value(arg)))
return NULL;
return (jl_sym_t*)jl_quotenode_value(arg);
}
}

void jl_foreigncall_get_syms(jl_value_t *target, jl_sym_t **fname, jl_sym_t **libname)
{
if (jl_is_expr(target)) {
if (jl_expr_nargs(target) != 3)
return;
jl_value_t *arg0 = jl_exprarg(target, 1);
jl_value_t *arg1 = jl_exprarg(target, 2);
*fname = get_sym_or_global_if_const(arg0);
*libname = get_sym_or_global_if_const(arg1);
return;
} else if (jl_is_tuple(target)) {
jl_sym_t *arg0 = (jl_sym_t *)jl_fieldref_noalloc(target, 0);
jl_sym_t *arg1 = (jl_sym_t *)jl_fieldref_noalloc(target, 1);
assert(jl_is_symbol(arg0) && jl_is_symbol(arg1));
*fname = arg0;
*libname = arg1;
} else {
*fname = get_sym_or_global_if_const(target);
}
}

#ifdef _OS_EMSCRIPTEN_
extern jl_value_t *jl_do_jscall(char *libname, char *fname, jl_value_t **args, size_t nargs);
#endif

SECT_INTERP static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s)
{
jl_code_info_t *src = s->src;
Expand Down Expand Up @@ -516,6 +561,36 @@ SECT_INTERP static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s)
else if (head == method_sym && nargs == 1) {
return eval_methoddef(ex, s);
}
else if (head == foreigncall_sym) {
jl_sym_t *fname = NULL, *libname = NULL;
jl_foreigncall_get_syms(args[0], &fname, &libname);

jl_sym_t *cc_sym = *(jl_sym_t**)args[3];
assert(jl_is_symbol(cc_sym));
if (cc_sym == jl_symbol("jscall")) {
#ifdef _OS_EMSCRIPTEN_
jl_value_t **ev_args;
JL_GC_PUSHARGS(ev_args, nargs-5);
for (int i = 5; i < nargs; ++i)
ev_args[i-5] = eval_value(args[i], s);
jl_value_t *result =
jl_do_jscall(libname ? jl_symbol_name(libname) : NULL,
jl_symbol_name(fname),
ev_args,
nargs-5);
JL_GC_POP();
// For now
if (!result)
result = jl_nothing;
return result;
#else
jl_error("jscall calling convention is only supported on jsvm targets");
#endif
}

jl_static_show(JL_STDERR, e);
jl_error("Encountered unsupported foreigncall in interpreter (this should not happen on supported platforms).");
}
jl_errorf("unsupported or misplaced expression %s", jl_symbol_name(head));
abort();
}
Expand Down
Loading