Skip to content

Apply functions: optimizations #1358

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

Merged
merged 6 commits into from
Jan 13, 2023
Merged
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Feature is disabled by default while dune rules are being fixed. Enable with --enable=auto-link.
* Compiler: specialize string to js-string conversion for all valid utf8 strings (previously just ascii)
* Compiler: use identifier for object literals when possible
* Compiler: Cache function arity (the length prop of a function is slow with v8)

## Bug fixes
- Effects: fix Js.export and Js.export_all to work with functions
Expand Down
11 changes: 10 additions & 1 deletion compiler/lib/generate.ml
Original file line number Diff line number Diff line change
Expand Up @@ -981,8 +981,17 @@ let apply_fun_raw ctx f params exact cps =
if exact
then apply_directly
else
let l = Utf8_string.of_string_exn "l" in
J.ECond
( J.EBin (J.EqEq, J.EDot (f, Utf8_string.of_string_exn "length"), int n)
( J.EBin
( J.EqEq
, J.ECond
( J.EBin (J.Ge, J.EDot (f, l), int 0)
, J.EDot (f, l)
, J.EBin
(J.Eq, J.EDot (f, l), J.EDot (f, Utf8_string.of_string_exn "length"))
)
, int n )
, apply_directly
, ecall
(runtime_fun ctx "caml_call_gen")
Expand Down
8 changes: 6 additions & 2 deletions compiler/tests-compiler/call_gen.ml
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,14 @@ module M1 = struct
function m(_e_,_f_){return k(_d_,_c_,_e_,_f_)}
//end
function caml_call1(f,a0)
{return f.length == 1?f(a0):runtime.caml_call_gen(f,[a0])}
{return (f.l >= 0?f.l:f.l = f.length) == 1
?f(a0)
:runtime.caml_call_gen(f,[a0])}
//end
function caml_call2(f,a0,a1)
{return f.length == 2?f(a0,a1):runtime.caml_call_gen(f,[a0,a1])}
{return (f.l >= 0?f.l:f.l = f.length) == 2
?f(a0,a1)
:runtime.caml_call_gen(f,[a0,a1])}
//end
|}]
end
Expand Down
4 changes: 3 additions & 1 deletion compiler/tests-compiler/effects_toplevel.ml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
:runtime.caml_trampoline_return(f,[a0])}
function caml_cps_call2(f,a0,a1)
{return runtime.caml_stack_check_depth()
?f.length == 2?f(a0,a1):runtime.caml_call_gen(f,[a0,a1])
?(f.l >= 0?f.l:f.l = f.length) == 2
?f(a0,a1)
:runtime.caml_call_gen(f,[a0,a1])
:runtime.caml_trampoline_return(f,[a0,a1])}
function caml_cps_exact_call2(f,a0,a1)
{return runtime.caml_stack_check_depth()
Expand Down
4 changes: 3 additions & 1 deletion compiler/tests-compiler/gh1051.ml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ let%expect_test _ =
{|
Warning: integer overflow: integer 0xffffffff (4294967295) truncated to 0xffffffff (-1); the generated code might be incorrect.
function caml_call2(f,a0,a1)
{return f.length == 2?f(a0,a1):runtime.caml_call_gen(f,[a0,a1])}
{return (f.l >= 0?f.l:f.l = f.length) == 2
?f(a0,a1)
:runtime.caml_call_gen(f,[a0,a1])}
//end |}];
()
12 changes: 9 additions & 3 deletions compiler/tests-compiler/gh1354.ml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ with Exit ->
runtime=globalThis.jsoo_runtime,
caml_wrap_exception=runtime.caml_wrap_exception;
function caml_call2(f,a0,a1)
{return f.length == 2?f(a0,a1):runtime.caml_call_gen(f,[a0,a1])}
{return (f.l >= 0?f.l:f.l = f.length) == 2
?f(a0,a1)
:runtime.caml_call_gen(f,[a0,a1])}
var
global_data=runtime.caml_get_global_data(),
Stdlib=global_data.Stdlib,
Expand Down Expand Up @@ -108,7 +110,9 @@ with Exit ->
caml_string_of_jsbytes=runtime.caml_string_of_jsbytes,
caml_wrap_exception=runtime.caml_wrap_exception;
function caml_call3(f,a0,a1,a2)
{return f.length == 3?f(a0,a1,a2):runtime.caml_call_gen(f,[a0,a1,a2])}
{return (f.l >= 0?f.l:f.l = f.length) == 3
?f(a0,a1,a2)
:runtime.caml_call_gen(f,[a0,a1,a2])}
var
global_data=runtime.caml_get_global_data(),
Stdlib=global_data.Stdlib,
Expand Down Expand Up @@ -179,7 +183,9 @@ with Exit ->
caml_string_of_jsbytes=runtime.caml_string_of_jsbytes,
caml_wrap_exception=runtime.caml_wrap_exception;
function caml_call2(f,a0,a1)
{return f.length == 2?f(a0,a1):runtime.caml_call_gen(f,[a0,a1])}
{return (f.l >= 0?f.l:f.l = f.length) == 2
?f(a0,a1)
:runtime.caml_call_gen(f,[a0,a1])}
var
global_data=runtime.caml_get_global_data(),
Stdlib=global_data.Stdlib,
Expand Down
2 changes: 1 addition & 1 deletion lib/tests/dune.inc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
(library
;; lib/tests/test_fun_call.ml
(name test_fun_call_75)
(enabled_if true)
(enabled_if (<> %{profile} using-effects))
(modules test_fun_call)
(libraries js_of_ocaml unix)
(inline_tests (modes js))
Expand Down
5 changes: 4 additions & 1 deletion lib/tests/gen-rules/gen.ml
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ let prefix : string =

type enabled_if =
| GE5
| No_effects
| Any

let enabled_if = function
| "test_sys" -> GE5
| "test_fun_call" -> No_effects
| _ -> Any

let () =
Expand All @@ -77,5 +79,6 @@ let () =
(Hashtbl.hash prefix mod 100)
(match enabled_if basename with
| Any -> "true"
| GE5 -> "(>= %{ocaml_version} 5)")
| GE5 -> "(>= %{ocaml_version} 5)"
| No_effects -> "(<> %{profile} using-effects)")
basename)
22 changes: 11 additions & 11 deletions lib/tests/test_fun_call.ml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ let s x =
if(x === undefined)
return "undefined"
if(typeof x === "function")
return "function#" + x.length
return "function#" + x.length + "#" + x.l
if(x.toString() == "[object Arguments]")
return "(Arguments: " + Array.prototype.slice.call(x).toString() + ")";
return x.toString()
Expand Down Expand Up @@ -96,7 +96,7 @@ let%expect_test "partial application, 0 argument call is treated like 1 argument
let%expect_test _ =
let plus = Js.wrap_callback (fun a b -> a + b) in
call_and_log plus {| (function(f){ return f(1) }) |};
[%expect {| Result: function#0 |}];
[%expect {| Result: function#0#undefined |}];
call_and_log plus {| (function(f){ return f(1)(2) }) |};
[%expect {| Result: 3 |}];
call_and_log plus {| (function(f){ return f(1,2) }) |};
Expand Down Expand Up @@ -147,7 +147,7 @@ let%expect_test "wrap_callback_strict" =
(Js.Unsafe.callback_with_arity 2 cb3)
{| (function(f){ return f(1,2,3) }) |};
[%expect {|
Result: function#0 |}];
Result: function#1#1 |}];
call_and_log
(Js.Unsafe.callback_with_arity 2 cb3)
~cont:(fun g -> g 4)
Expand All @@ -164,7 +164,7 @@ let%expect_test "wrap_callback_strict" =
Result: 0 |}];
call_and_log (Js.Unsafe.callback_with_arity 2 cb3) {| (function(f){ return f(1,2) }) |};
[%expect {|
Result: function#0 |}]
Result: function#1#1 |}]

let%expect_test "wrap_callback_strict" =
call_and_log
Expand Down Expand Up @@ -238,7 +238,7 @@ let%expect_test "partial application, 0 argument call is treated 1 argument (und
let%expect_test _ =
let plus = Js.wrap_meth_callback (fun _ a b -> a + b) in
call_and_log plus {| (function(f){ return f(1) }) |};
[%expect {| Result: function#0 |}];
[%expect {| Result: function#0#undefined |}];
call_and_log plus {| (function(f){ return f(1)(2) }) |};
[%expect {| Result: 3 |}];
call_and_log plus {| (function(f){ return f(1,2) }) |};
Expand Down Expand Up @@ -291,7 +291,7 @@ let%expect_test "wrap_meth_callback_strict" =
(Js.Unsafe.meth_callback_with_arity 2 cb4)
{| (function(f){ return f.apply("this",[1,2,3]) }) |};
[%expect {|
Result: function#0 |}];
Result: function#1#1 |}];
call_and_log
(Js.Unsafe.meth_callback_with_arity 2 cb4)
~cont:(fun g -> g 4)
Expand All @@ -309,7 +309,7 @@ let%expect_test "wrap_meth_callback_strict" =
call_and_log
(Js.Unsafe.meth_callback_with_arity 2 cb4)
{| (function(f){ return f.apply("this",[1,2]) }) |};
[%expect {| Result: function#0 |}]
[%expect {| Result: function#1#1 |}]

let%expect_test "wrap_meth_callback_strict" =
call_and_log
Expand Down Expand Up @@ -354,7 +354,7 @@ let%expect_test "partial application, extra arguments set to undefined" =
let%expect_test _ =
call_and_log cb3 ~cont:(fun g -> g 1) {| (function(f){ return f }) |};
[%expect {|
Result: function#0 |}]
Result: function#2#2 |}]

let%expect_test _ =
call_and_log cb3 ~cont:(fun g -> g 1 2 3 4) {| (function(f){ return f }) |};
Expand All @@ -369,7 +369,7 @@ let%expect_test _ =
| _ -> Printf.printf "Error: unknown"
in
f cb5;
[%expect {| Result: function#0 |}];
[%expect {| Result: function#1#1 |}];
f cb4;
[%expect {|
got 1, 1, 2, 3, done
Expand Down Expand Up @@ -399,7 +399,7 @@ let%expect_test _ =
Result: 0 |}];
f (Obj.magic cb4);
[%expect {|
Result: function#0 |}];
Result: function#1#1 |}];
f (Obj.magic cb5);
[%expect {|
Result: function#0 |}]
Result: function#2#2 |}]
4 changes: 2 additions & 2 deletions runtime/jslib.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,15 +375,15 @@ function caml_js_wrap_meth_callback_unsafe(f) {
//Provides: caml_js_function_arity
//If: !effects
function caml_js_function_arity(f) {
return f.length
return (f.l >= 0)?f.l:(f.l = f.length)
}

//Provides: caml_js_function_arity
//If: effects
function caml_js_function_arity(f) {
// Functions have an additional continuation parameter. This should
// not be visible when calling them from JavaScript
return f.length - 1
return ((f.l >= 0)?f.l:(f.l = f.length)) - 1
}

//Provides: caml_js_equals mutable (const, const)
Expand Down
82 changes: 66 additions & 16 deletions runtime/stdlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function caml_call_gen(f, args) {
return caml_call_gen(f.fun, args);
//FIXME, can happen with too many arguments
if(typeof f !== "function") return f;
var n = f.length | 0;
var n = (f.l >= 0)?f.l:(f.l = f.length);
if(n === 0) return f.apply(null,args);
var argsLen = args.length | 0;
var d = n - argsLen | 0;
Expand All @@ -35,13 +35,37 @@ function caml_call_gen(f, args) {
return caml_call_gen(f.apply(null,args.slice(0,n)),args.slice(n));
}
else {
return function (){
var extra_args = (arguments.length == 0)?1:arguments.length;
var nargs = new Array(args.length+extra_args);
for(var i = 0; i < args.length; i++ ) nargs[i] = args[i];
for(var i = 0; i < arguments.length; i++ ) nargs[args.length+i] = arguments[i];
return caml_call_gen(f, nargs)
switch (d) {
case 1: {
var g = function (x){
var nargs = new Array(argsLen + 1);
for(var i = 0; i < argsLen; i++ ) nargs[i] = args[i];
nargs[argsLen] = x;
return f.apply(null, nargs)
};
break;
}
case 2: {
var g = function (x, y){
var nargs = new Array(argsLen + 2);
for(var i = 0; i < argsLen; i++ ) nargs[i] = args[i];
nargs[argsLen] = x;
nargs[argsLen + 1] = y;
return f.apply(null, nargs)
};
break;
}
default: {
var g = function (){
var extra_args = (arguments.length == 0)?1:arguments.length;
var nargs = new Array(args.length+extra_args);
for(var i = 0; i < args.length; i++ ) nargs[i] = args[i];
for(var i = 0; i < arguments.length; i++ ) nargs[args.length+i] = arguments[i];
return caml_call_gen(f, nargs)
};
}}
g.l = d;
return g;
}
}

Expand All @@ -52,7 +76,7 @@ function caml_call_gen(f, args) {
if (f.fun)
return caml_call_gen(f.fun, args);
if (typeof f !== "function") return args[args.length-1](f);
var n = f.length | 0;
var n = (f.l >= 0)?f.l:(f.l = f.length);
if (n === 0) return f.apply(null, args);
var argsLen = args.length | 0;
var d = n - argsLen | 0;
Expand All @@ -70,14 +94,40 @@ function caml_call_gen(f, args) {
} else {
argsLen--;
var k = args [argsLen];
return k (function () {
var extra_args = (arguments.length == 0)?1:arguments.length;
var nargs = new Array(argsLen + extra_args);
for(var i = 0; i < argsLen; i++ ) nargs[i] = args[i];
for(var i = 0; i < arguments.length; i++ )
nargs[argsLen + i] = arguments[i];
return caml_call_gen(f, nargs)
});
switch (d) {
case 1: {
var g = function (x, y){
var nargs = new Array(argsLen + 2);
for(var i = 0; i < argsLen; i++ ) nargs[i] = args[i];
nargs[argsLen] = x;
nargs[argsLen + 1] = y;
return f.apply(null, nargs)
};
break;
}
case 2: {
var g = function (x, y, z){
var nargs = new Array(argsLen + 3);
for(var i = 0; i < argsLen; i++ ) nargs[i] = args[i];
nargs[argsLen] = x;
nargs[argsLen + 1] = y;
nargs[argsLen + 2] = z;
return f.apply(null, nargs)
};
break;
}
default: {
var g = function (){
var extra_args = (arguments.length == 0)?1:arguments.length;
var nargs = new Array(argsLen + extra_args);
for(var i = 0; i < argsLen; i++ ) nargs[i] = args[i];
for(var i = 0; i < arguments.length; i++ )
nargs[argsLen + i] = arguments[i];
return caml_call_gen(f, nargs)
};
}}
g.l = d + 1;
return k(g);
}
}

Expand Down
Loading