Skip to content

Commit

Permalink
[js] Change the calling convention
Browse files Browse the repository at this point in the history
This brings stuff in line with other backends.
When returning native types hllize them on the caller site.
str and int values are returned as unboxed js values.
  • Loading branch information
pmurias committed Feb 18, 2018
1 parent 82b0bbe commit 3464635
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 29 deletions.
2 changes: 2 additions & 0 deletions src/vm/js/Chunk.nqp
Expand Up @@ -9,6 +9,8 @@ my $T_CALL_ARG := 5; # Something that will be passed to a sub/method call
my $T_INT16 := 6; # We use a javascript number but always treat it as a 16bit integer
my $T_INT8 := 7; # We use a javascript number but always treat it as a 8bit integer

my $T_RETVAL := 8; # Something that will be returned from a sub/method call

my $T_VOID := -1; # Value of this type shouldn't exist, we use a "" as the expr
my $T_NONVAL := -2; # something that is not a nqp value

Expand Down
52 changes: 49 additions & 3 deletions src/vm/js/Compiler.nqp
Expand Up @@ -295,6 +295,23 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
0;
}


# Boxes/unboxes the return value when neccessary
method get_return_value(str $expr, @setup, :$want, :$node) {
my int $unpack_as_type :=
($want == $T_INT || $want == $T_STR || $want == $T_NUM)
?? $want
!! $T_OBJ;

my str $suffix := self.suffix_from_type($unpack_as_type);
my str $unpacked :=
$want == $T_VOID
?? $expr
!! "nqp.retval$suffix({$unpack_as_type == $T_OBJ ?? 'HLL' !! $*CTX}, $expr)";

self.stored_result(Chunk.new($unpack_as_type, $unpacked, @setup, :$node), :$want);
}

# turns a list of arguments for a call into a js code according to our most general calling convention
# $args is the list of QAST::Node arguments
# returns a Chunk containing either a comma separated list of arguments or an expression that evaluates to a array of arguments
Expand Down Expand Up @@ -542,6 +559,27 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
return Chunk.new($T_VOID, "", $chunk.setup);
}

if $desired == $T_RETVAL {
if $got == $T_OBJ {
return Chunk.new($T_RETVAL, $chunk.expr, $chunk);
}
if $got == $T_BOOL {
return Chunk.new($T_RETVAL, "({$chunk.expr} ? 1 : 0)", $chunk);
}
if $got_int {
return Chunk.new($T_RETVAL, $chunk.expr, $chunk);
}
if $got == $T_NUM {
return Chunk.new($T_RETVAL, "new nqp.NativeNumRet({$chunk.expr})", $chunk);
}
if $got == $T_STR {
return Chunk.new($T_RETVAL, $chunk.expr, $chunk);
}
if $got == $T_VOID {
return Chunk.new($T_RETVAL, "nqp.Null", $chunk);
}
}

if $desired == $T_CALL_ARG {
if $got == $T_OBJ {
return Chunk.new($T_CALL_ARG, $chunk.expr, $chunk);
Expand Down Expand Up @@ -636,6 +674,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {
%convert{$T_INT16} := 'intToObj';
%convert{$T_NUM} := 'numToObj';
%convert{$T_STR} := 'strToObj';
%convert{$T_RETVAL} := 'retval';
nqp::die("Can't coerce $got to OBJ") unless nqp::existskey(%convert, $got);
return Chunk.new($T_OBJ, "nqp.{%convert{$got}}(HLL, {$chunk.expr})", $chunk);
}
Expand Down Expand Up @@ -1094,7 +1133,7 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {

%*BLOCKS_INFO{$node.cuid} := $*BLOCK;

my int $body_want := $node.blocktype eq 'immediate' ?? $want !! $T_OBJ;
my int $body_want := $node.blocktype eq 'immediate' ?? $want !! $T_RETVAL;

my int $has_closure_template := $node.blocktype ne 'immediate';

Expand Down Expand Up @@ -1664,13 +1703,20 @@ class QAST::CompilerJS does DWIMYNameMangling does SerializeOnce {

multi method as_js(QAST::VarWithFallback $node, :$want) {
my $compiled := self.compile_var($node, :$want);
if $*BINDVAL || ($compiled.type != $T_OBJ && $compiled.type != $T_CALL_ARG) {
my int $can_be_null :=
$compiled.type == $T_OBJ
|| $compiled.type == $T_CALL_ARG
|| $compiled.type == $T_RETVAL;

if $*BINDVAL || !$can_be_null {
$compiled
}
else {
my $fallback := self.as_js($node.fallback, :want($T_OBJ));
my str $tmp := $*BLOCK.add_tmp();
Chunk.new($T_OBJ, $tmp, [

# We can put a T_OBJ into a T_CALL_ARG or T_RETVAL without casting
Chunk.new($compiled.type, $tmp, [
$compiled,
"if ({$compiled.expr} === nqp.Null) \{\n"
,$fallback
Expand Down
18 changes: 8 additions & 10 deletions src/vm/js/Operations.nqp
Expand Up @@ -275,7 +275,7 @@ class QAST::OperationsJS {
@setup.push("if ($ret.\$\$toBool($*CTX)) \{\n") if is_chain($part[$arg_idx]);
@setup.push($callee);
@setup.push($right);
@setup.push("$ret = {$callee.expr}.\$\$call($*CTX, null, {$left.expr}, {$right.expr});\n");
@setup.push("$ret = nqp.retval(HLL, {$callee.expr}.\$\$call($*CTX, null, {$left.expr}, {$right.expr}));\n");
@setup.push("\}") if is_chain($part[$arg_idx]);

Chunk.new($T_OBJ, $right.expr, @setup, :node($part));
Expand Down Expand Up @@ -657,8 +657,7 @@ class QAST::OperationsJS {

my str $call := $compiled_args.is_args_array ?? '.$$apply(' !! '.$$call(';

$comp.stored_result(
Chunk.new($T_OBJ, $callee.expr ~ ".\$\$decont($*CTX)" ~ $call ~ $compiled_args.expr ~ ')' , [$callee, $compiled_args], :$node), :$want);
$comp.get_return_value($callee.expr ~ ".\$\$decont($*CTX)" ~ $call ~ $compiled_args.expr ~ ')' , [$callee, $compiled_args], :$node, :$want);
});

%ops<callstatic> := %ops<call>;
Expand Down Expand Up @@ -727,8 +726,7 @@ class QAST::OperationsJS {

@setup.push($compiled_args);

$comp.stored_result(
Chunk.new($T_OBJ, "{$invocant.expr}.\$\$decont($*CTX)" ~ $method ~ $call ~ $compiled_args.expr ~ ')' , @setup, :$node), :$want);
$comp.get_return_value("{$invocant.expr}.\$\$decont($*CTX)" ~ $method ~ $call ~ $compiled_args.expr ~ ')', @setup, :$want, :$node);

});

Expand Down Expand Up @@ -1093,7 +1091,7 @@ class QAST::OperationsJS {
}

my $check_cond := $is_withy
?? Chunk.new($T_INT, "{$cond.expr}.\$\$decont($*CTX).defined($*CTX, null, {$cond.expr}.\$\$decont($*CTX)).\$\$toBool($*CTX)", $cond)
?? Chunk.new($T_INT, "nqp.retval(HLL, {$cond.expr}.\$\$decont($*CTX).defined($*CTX, null, {$cond.expr})).\$\$decont($*CTX).\$\$toBool($*CTX)", $cond)
!! $comp.coerce($cond, $T_BOOL);

my $cond_without_sideeffects := Chunk.new($cond.type, $cond.expr);
Expand Down Expand Up @@ -1560,7 +1558,7 @@ class QAST::OperationsJS {
add_simple_op('captureinnerlex', $T_OBJ, [$T_OBJ], :!inlinable, :side_effects);

add_simple_op('invokewithcapture', $T_OBJ, [$T_OBJ, $T_OBJ], sub ($invokee, $capture) {
"$invokee.\$\$apply([{$*CTX}].concat($capture.named, $capture.pos))"
"nqp.retval(HLL, $invokee.\$\$apply([{$*CTX}].concat($capture.named, $capture.pos)))"
}, :side_effects);


Expand Down Expand Up @@ -1664,10 +1662,10 @@ class QAST::OperationsJS {

# Continuations

add_simple_op('continuationreset', $T_OBJ, [$T_OBJ, $T_OBJ], :side_effects, :ctx);
add_simple_op('continuationinvoke', $T_OBJ, [$T_OBJ, $T_OBJ], :side_effects, :ctx);
add_simple_op('continuationreset', $T_OBJ, [$T_OBJ, $T_OBJ], :side_effects, :ctx, :takes_hll);
add_simple_op('continuationinvoke', $T_OBJ, [$T_OBJ, $T_OBJ], :side_effects, :ctx, :takes_hll);

add_simple_op('continuationcontrol', $T_OBJ, [$T_INT, $T_OBJ, $T_OBJ], :side_effects, :ctx);
add_simple_op('continuationcontrol', $T_OBJ, [$T_INT, $T_OBJ, $T_OBJ], :side_effects, :ctx, :takes_hll);


# Dealing with compiled compunits
Expand Down
22 changes: 11 additions & 11 deletions src/vm/js/nqp-runtime/core.js
Expand Up @@ -1118,7 +1118,7 @@ op.typeparameterized = function(type) {

const fibers = require('fibers');

function runTagged(tag, fiber, val, ctx) {
function runTagged(currentHLL, tag, fiber, val, ctx) {
let arg = val;
while (1) {
const control = fiber.run(arg);
Expand All @@ -1127,7 +1127,7 @@ function runTagged(tag, fiber, val, ctx) {
return control.value;
} else {
const cont = new Cont(tag, fiber);
const value = control.closure.$$call(ctx, null, cont);
const value = nqp.retval(currentHLL, control.closure.$$call(ctx, null, cont));
return value;
}
} else {
Expand All @@ -1151,26 +1151,26 @@ class Cont {
}
};

op.continuationreset = function(ctx, tag, block) {
op.continuationreset = function(ctx, currentHLL, tag, block) {
if (block instanceof Cont) {
return runTagged(tag, block.fiber, Null, ctx);
return runTagged(currentHLL, tag, block.fiber, Null, ctx);
} else {
const fiber = fibers(function() {
return {value: block.$$call(ctx, null), tag: tag};
return {value: nqp.retval(currentHLL, block.$$call(ctx, null)), tag: tag};
});
fiber.tag = tag;
return runTagged(tag, fiber, Null, ctx);
return runTagged(currentHLL, tag, fiber, Null, ctx);
}
};


op.continuationcontrol = function(ctx, protect, tag, closure) {
return fibers.yield({closure: closure, tag: tag});
op.continuationcontrol = function(ctx, currentHLL, protect, tag, closure) {
return nqp.retval(currentHLL, fibers.yield({closure: closure, tag: tag}));
};

op.continuationinvoke = function(ctx, cont, inject) {
const val = inject.$$call(ctx, null);
return runTagged(cont.tag, cont.fiber, val, ctx);
op.continuationinvoke = function(ctx, currentHLL, cont, inject) {
const val = nqp.retval(currentHLL, inject.$$call(ctx, null));
return runTagged(currentHLL, cont.tag, cont.fiber, val, ctx);
};

let generator = Math;
Expand Down
2 changes: 2 additions & 0 deletions src/vm/js/nqp-runtime/hll.js
Expand Up @@ -41,6 +41,8 @@ op.hllizefor = function(ctx, obj, language) {
}
}

// we don't support returning native typed stuff from foreign_transform_*

if (obj instanceof Hash || role == 5) {
const foreignTransformHash = config.get('foreign_transform_hash');
if (foreignTransformHash === undefined) return obj;
Expand Down
16 changes: 16 additions & 0 deletions src/vm/js/nqp-runtime/native-args.js
Expand Up @@ -45,3 +45,19 @@ class NativeStrArg {
};

exports.NativeStrArg = NativeStrArg;

class NativeNumRet {
constructor(value) {
this.value = value;
}

$$getNum() {
return this.value;
}

$$decont() {
return this;
}
}

exports.NativeNumRet = NativeNumRet;
70 changes: 66 additions & 4 deletions src/vm/js/nqp-runtime/runtime.js
Expand Up @@ -25,6 +25,8 @@ const NativeIntArg = exports.NativeIntArg = nativeArgs.NativeIntArg;
const NativeNumArg = exports.NativeNumArg = nativeArgs.NativeNumArg;
const NativeStrArg = exports.NativeStrArg = nativeArgs.NativeStrArg;

const NativeNumRet = exports.NativeNumRet = nativeArgs.NativeNumRet;

const stripMarks = require('./strip-marks.js');
const foldCase = require('fold-case');
const graphemes = require('./graphemes.js');
Expand Down Expand Up @@ -216,8 +218,8 @@ exports.toStr = function(arg_, ctx) {
} else if (arg.$$getStr) {
return arg.$$getStr();
} else if (arg.Str) {
const ret = arg.Str(ctx, null, arg).$$decont(ctx); // eslint-disable-line new-cap
return ret.$$getStr();
const ret = arg.Str(ctx, null, arg); // eslint-disable-line new-cap
return (typeof ret === 'string' ? ret : ret.$$decont(ctx).$$getStr());
} else if (arg.$$getNum) {
return coercions.numToStr(arg.$$getNum());
} else if (arg.$$getInt) {
Expand All @@ -236,7 +238,9 @@ exports.toNum = function(arg_, ctx) {
return coercions.strToNum(arg.value);
} else if (arg._STable && arg._STable.methodCache && arg._STable.methodCache.get('Num')) {
const result = arg.Num(ctx, null, arg); // eslint-disable-line new-cap
if (result.$$getNum) {
if (typeof result === 'number') {
return result;
} else if (result.$$getNum) {
return result.$$getNum();
} else if (result.$$numify) {
return result.$$numify();
Expand All @@ -260,6 +264,61 @@ exports.toInt = function(arg, ctx) {
return (exports.toNum(arg, ctx) | 0);
};

exports.retval = function(currentHLL, arg) {
if (typeof arg === 'number') {
return core.intToObj(currentHLL, arg);
} else if (typeof arg === 'string' || arg === nullStr) {
return core.strToObj(currentHLL, arg);
} else if (arg instanceof NativeNumRet) {
return core.numToObj(currentHLL, arg.value);
} else {
return arg;
}
};

exports.retval_bool = function(ctx, arg) {
if (typeof arg === 'number') {
return arg === 0 ? 0 : 1;
} else if (typeof arg === 'string') {
return arg === '' ? 0 : 1;
} else if (arg === nullStr) {
return 0;
} else {
return arg.$$toBool(ctx);
}
};

exports.retval_i = function(ctx, arg) {
if (typeof arg === 'number') {
return arg;
} else if (typeof arg === 'string' || arg === nullStr) {
return coercions.strToNum(arg)|0;
} else {
return exports.toInt(arg, ctx);
}
};


exports.retval_n = function(ctx, arg) {
if (typeof arg === 'number') {
return arg;
} else if (typeof arg === 'string' || arg === nullStr) {
return coercions.strToNum(arg);
} else {
return exports.toNum(arg, ctx);
}
};

exports.retval_s = function(ctx, arg) {
if (typeof arg === 'string' || arg === nullStr) {
return arg;
} else if (typeof arg === 'number') {
return arg.toString();
} else {
return exports.toStr(arg, ctx);
}
};

// Placeholder
exports.topContext = function() {
return null;
Expand Down Expand Up @@ -357,7 +416,8 @@ exports.dumpObj = function(obj) {
if (typeof obj === 'object') {
if (obj._STable) {
console.log(obj._STable.REPR.name);
console.log(obj._STable.HOW.name(null, null, obj._STable.HOW, obj));
const name = obj._STable.HOW.name(null, null, obj._STable.HOW, obj);
console.log(name instanceof NQPStr ? name.value : name);
} else {
console.log('no STable', obj.constructor.name);
}
Expand Down Expand Up @@ -477,6 +537,8 @@ const chunkNamesToTypes = {
T_BOOL: 4,
T_CALL_ARG: 5,
T_INT16: 6,
T_INT8: 7,
T_RETVAL: 8,

T_VOID: -1,
T_NONVAL: -2,
Expand Down
3 changes: 2 additions & 1 deletion src/vm/js/nqp-runtime/sixmodel.js
Expand Up @@ -134,7 +134,8 @@ class STable {
this.boolificationSpec = {mode: mode, method: method};
if (mode == 0) {
this.ObjConstructor.prototype.$$toBool = function(ctx) {
return method.$$call(ctx, {}, this).$$decont().$$toBool(ctx);
const ret = method.$$call(ctx, {}, this);
return (typeof ret === 'number' ? (ret === 0 ? 0 : 1) : ret.$$decont().$$toBool(ctx));
};
} else if (mode == 1) {
this.ObjConstructor.prototype.$$toBool = function(ctx) {
Expand Down

0 comments on commit 3464635

Please sign in to comment.