Skip to content

Optimize compilation of switch statements for untagged variants #7135

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 4 commits into from
Nov 5, 2024
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
- Improve negation handling in combination with and/or to simplify generated code (especially coming out of pattern matching). https://github.com/rescript-lang/rescript-compiler/pull/7138
- optimize JavaScript code generation by using x == null checks and improving type-based optimizations for string/number literals. https://github.com/rescript-lang/rescript-compiler/pull/7141
- Improve pattern matching on optional fields. https://github.com/rescript-lang/rescript-compiler/pull/7143 https://github.com/rescript-lang/rescript-compiler/pull/7144
- Optimize compilation of switch statements for untagged variants when there are no literal cases. https://github.com/rescript-lang/rescript-compiler/pull/7135


#### :house: Internal
Expand Down
42 changes: 31 additions & 11 deletions compiler/core/lam_compile.ml
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ let compile output_prefix =
(* [e] will be used twice *)
let dispatch e =
let is_a_literal_case =
if block_cases <> [] then
if untagged then
E.is_a_literal_case
~literal_cases:(get_literal_cases sw_names)
~block_cases e
Expand All @@ -732,21 +732,41 @@ let compile output_prefix =
~has_null_undefined_other:(has_null_undefined_other sw_names)
e
in
S.if_ is_a_literal_case
(compile_cases ~cxt ~switch_exp:e ~block_cases
~default:sw_num_default ~get_tag:get_const_tag sw_consts)
~else_:
(compile_cases ~untagged ~cxt
~switch_exp:(if untagged then e else E.tag ~name:tag_name e)
~block_cases ~default:sw_blocks_default
~get_tag:get_block_tag sw_blocks)
let eq_default d1 d2 =
match (d1, d2) with
| Default lam1, Default lam2 -> Lam.eq_approx lam1 lam2
| Complete, Complete -> true
| NonComplete, NonComplete -> true
| _ -> false
in
if
untagged
&& List.length sw_consts = 0
&& eq_default sw_num_default sw_blocks_default
then
compile_cases ~untagged ~cxt
~switch_exp:(if untagged then e else E.tag ~name:tag_name e)
~block_cases ~default:sw_blocks_default ~get_tag:get_block_tag
sw_blocks
else
[
S.if_ is_a_literal_case
(compile_cases ~cxt ~switch_exp:e ~block_cases
~default:sw_num_default ~get_tag:get_const_tag sw_consts)
~else_:
(compile_cases ~untagged ~cxt
~switch_exp:
(if untagged then e else E.tag ~name:tag_name e)
~block_cases ~default:sw_blocks_default
~get_tag:get_block_tag sw_blocks);
]
in
match e.expression_desc with
| J.Var _ -> [dispatch e]
| J.Var _ -> dispatch e
| _ ->
let v = Ext_ident.create_tmp () in
(* Necessary avoid duplicated computation*)
[S.define_variable ~kind:Variable v e; dispatch (E.var v)])
S.define_variable ~kind:Variable v e :: dispatch (E.var v))
in
match lambda_cxt.continuation with
(* Needs declare first *)
Expand Down
52 changes: 21 additions & 31 deletions tests/tests/src/UntaggedVariants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -376,10 +376,6 @@ let TestFunctionCase = {
let someJson = '[{"name": "Haan"}, {"name": "Mr"}, false]';

function check$1(s) {
if (s === undefined || s === null || s === false || s === true) {
console.log("Nope...");
return;
}
if (Array.isArray(s)) {
if (s.length !== 3) {
console.log("Nope...");
Expand All @@ -388,46 +384,40 @@ function check$1(s) {
let match = s[0];
if (match === true) {
let match$1 = s[1];
if (match$1 === false) {
let match$2 = s[2];
if (match$2 === undefined || match$2 === null || match$2 === false || match$2 === true) {
console.log("Nope...");
return;
}
if (Array.isArray(match$2)) {
if (match$2.length !== 2) {
console.log("Nope...");
return;
}
let match$3 = match$2[0];
if (match$3 === undefined || match$3 === null || match$3 === false || match$3 === true) {
console.log("Nope...");
return;
}
if (match$3 === "My name is") {
let match$4 = match$2[1];
if (match$4 === undefined || match$4 === null || match$4 === false || match$4 === true) {
if (match$1 === undefined || match$1 === null || match$1 === false || match$1 === true) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically this could be simplified to == null || type of "boolean", but I'm not sure it's that important tbh.

if (match$1 === false) {
let match$2 = s[2];
if (Array.isArray(match$2)) {
if (match$2.length !== 2) {
console.log("Nope...");
return;
}
if (typeof match$4 === "number") {
if (match$4 !== 10) {
let match$3 = match$2[0];
if (typeof match$3 === "string") {
if (match$3 === "My name is") {
let match$4 = match$2[1];
if (typeof match$4 === "number") {
if (match$4 !== 10) {
console.log("Nope...");
} else {
console.log("yup");
}
return;
}
console.log("Nope...");
} else {
console.log("yup");
return;
}
console.log("Nope...");
return;
}
console.log("Nope...");
return;
} else {
console.log("Nope...");
return;
}
} else {
console.log("Nope...");
return;
}
console.log("Nope...");
return;
} else {
console.log("Nope...");
return;
Expand Down
12 changes: 6 additions & 6 deletions tests/tests/src/and_or_simplify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ function check_null_eq_typeof(x) {
}

function check_null_neq_typeof(x) {
if (typeof x !== "boolean") {
return 4;
} else {
if (typeof x === "boolean") {
return 3;
} else {
return 4;
}
}

Expand All @@ -18,10 +18,10 @@ function check_undefined_eq_typeof(x) {
}

function check_undefined_neq_typeof(x) {
if (typeof x !== "boolean") {
return 4;
} else {
if (typeof x === "boolean") {
return 3;
} else {
return 4;
}
}

Expand Down
18 changes: 9 additions & 9 deletions tests/tests/src/core/Core_JsonTests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ import * as Test from "./Test.mjs";
function decodeJsonTest() {
let json = {"someProp":{"otherProp": null, "thirdProp": [true, false]}};
let decodedCorrectly;
if (json === null || !(typeof json === "object" && !Array.isArray(json))) {
decodedCorrectly = false;
} else {
if (typeof json === "object" && !Array.isArray(json)) {
let match = json["someProp"];
if (match !== undefined && !(match === null || !(typeof match === "object" && !Array.isArray(match)))) {
if (match !== undefined && typeof match === "object" && !Array.isArray(match)) {
let match$1 = match["thirdProp"];
if (match$1 !== undefined && !(match$1 === null || !(Array.isArray(match$1) && match$1.length === 2))) {
if (match$1 !== undefined && Array.isArray(match$1) && match$1.length === 2) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be further simplified as a simple simplification, in a separate PR.

let match$2 = match$1[0];
if (match$2 === null || !(typeof match$2 === "boolean" && match$2)) {
decodedCorrectly = false;
} else {
if (typeof match$2 === "boolean" && match$2) {
let match$3 = match$1[1];
decodedCorrectly = match$3 === null ? false : typeof match$3 === "boolean" && !match$3;
decodedCorrectly = typeof match$3 === "boolean" && !match$3;
} else {
decodedCorrectly = false;
}
} else {
decodedCorrectly = false;
}
} else {
decodedCorrectly = false;
}
} else {
decodedCorrectly = false;
}
Test.run([
[
Expand Down
59 changes: 59 additions & 0 deletions tests/tests/src/json_decorders.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Generated by ReScript, PLEASE EDIT WITH CARE

import * as $$Array from "rescript/lib/es6/Array.js";

function decodeUser(json) {
if (!(typeof json === "object" && !Array.isArray(json))) {
return;
}
let id = json.id;
if (typeof id !== "string") {
return;
}
let name = json.name;
if (typeof name !== "string") {
return;
}
let age = json.age;
if (typeof age !== "number") {
return;
}
let email = json.email;
let tmp;
tmp = typeof email === "string" ? email : undefined;
return {
id: id,
name: name,
age: age | 0,
email: tmp
};
}

function decodeGroup(json) {
if (!(typeof json === "object" && !Array.isArray(json))) {
return;
}
let id = json.id;
if (typeof id !== "string") {
return;
}
let name = json.name;
if (typeof name !== "string") {
return;
}
let users = json.users;
if (Array.isArray(users)) {
return {
id: id,
name: name,
users: $$Array.filterMap(users, decodeUser)
};
}

}

export {
decodeUser,
decodeGroup,
}
/* No side effect */
45 changes: 45 additions & 0 deletions tests/tests/src/json_decorders.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
type user = {
id: string,
name: string,
age: int,
email: option<string>,
}

type group = {
id: string,
name: string,
users: array<user>,
}

let decodeUser = json => {
switch json {
| JSON.Object(dict{
"id": JSON.String(id),
"name": String(name),
"age": Number(age),
"email": ?email,
}) =>
Some({
id,
name,
age: age->Float.toInt,
email: switch email {
| Some(String(email)) => Some(email)
| _ => None
},
})
| _ => None
}
}

let decodeGroup = json => {
switch json {
| JSON.Object(dict{"id": JSON.String(id), "name": String(name), "users": Array(users)}) =>
Some({
id,
name,
users: users->Array.filterMap(decodeUser),
})
| _ => None
}
}
16 changes: 5 additions & 11 deletions tests/tests/src/pattern_match_json.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,22 @@ import * as Primitive_option from "rescript/lib/es6/Primitive_option.js";

function decodeGroup(group) {
let id = group.id;
if (id == null) {
return [
"e",
"f"
];
}
if (typeof id !== "string") {
return [
"e",
"f"
];
}
let name = group.name;
if (typeof name !== "string") {
if (typeof name === "string") {
return [
"e",
"f"
id,
name
];
} else {
return [
id,
name
"e",
"f"
];
}
}
Expand Down