-
Notifications
You must be signed in to change notification settings - Fork 459
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
Optimize compilation of switch statements for untagged variants #7135
Conversation
There's the question of whether the check for optional fields could also be incorporated at the pattern matching level. |
a236b78
to
6bfad7d
Compare
Yes, it wouldn't be trivial and often inefficient at runtime. The simplest way to reduce computation and run safely with side effects is to create a function with a captured return, but I'm not sure if it's optimizable. Or we can ignore it as this kind of repetition is very well compressed in the real world via HTTP |
Umm, with side effects it compiles to function decodeGroup(group) {
let match = group.id;
if (match !== undefined) {
if (typeof match === "string") {
let match$1 = group.name;
if (match$1 !== undefined) {
if (typeof match$1 === "string") {
return [
match,
match$1
];
}
console.log("aaaaaaaa");
return [
"e",
"f"
];
}
console.log("aaaaaaaa");
return [
"e",
"f"
];
}
console.log("aaaaaaaa");
return [
"e",
"f"
];
}
console.log("aaaaaaaa");
return [
"e",
"f"
];
} (Makes bit better to understand the flow without optimization) Compiler obviously emits unnecessary branches. It should be: function decodeGroup(group) {
let match = group.id;
if (match !== undefined) {
if (typeof match === "string") {
let match$1 = group.name;
if (match$1 !== undefined) {
if (typeof match$1 === "string") {
return [
match,
match$1
];
}
}
}
}
console.log("aaaaaaaa");
return [
"e",
"f"
];
} (undefined checks also can be removed) |
Optional fields: #7143 |
6bfad7d
to
cd472da
Compare
First step: remove the distinction between cases with and without payload in the toplevel algorithm. On this test: ```res @unboxed type rec t = | Boolean(bool) | @as(null) Null | String(string) | Number(float) | Object(Dict.t<t>) | Array(array<t>) type group = { id: string, name: string, } let decodeGroup = group => { switch group { | (dict{"id": String(id), "name": String(name)}) => (id, name) | _ => ("e", "f") } } ``` Before: ```js function decodeGroup(group) { let match = group.id; if (match === undefined) { return [ "e", "f" ]; } if (match === null) { return [ "e", "f" ]; } if (typeof match !== "string") { return [ "e", "f" ]; } let match$1 = group.name; if (match$1 !== undefined && !(match$1 === null || typeof match$1 !== "string")) { return [ match, match$1 ]; } else { return [ "e", "f" ]; } } ``` After: ``` function decodeGroup(group) { let match = group.id; if (match === undefined) { return [ "e", "f" ]; } if (typeof match !== "string") { return [ "e", "f" ]; } let match$1 = group.name; if (match$1 !== undefined && typeof match$1 === "string") { return [ match, match$1 ]; } else { return [ "e", "f" ]; } } ``` The 3 cases have become 2: check for optional fields and check for which case it is.
cd472da
to
73cf47d
Compare
After rebasing on master, it's close to optimal for the motivating example. let decodeGroup = group => {
switch group {
| (dict{"id": String(id), "name": String(name)}) =>
(id, name)
| _ => ("e", "f")
}
} compiles to: function decodeGroup(group) {
let id = group.id;
if (typeof id !== "string") {
return [
"e",
"f"
];
}
let name = group.name;
if (typeof name === "string") {
return [
id,
name
];
} else {
return [
"e",
"f"
];
}
} |
This is a decompilation of the lambda code even before getting to the pattern matching compilation later on in the back-end: let decodeGroup = fun group ->
try
let id = group.id in
let match_val = group.dictValuesType in
match id with
| 1 ->
(match group.name with
| 1 -> (id, group.name)
| _ -> raise Exit)
| _ -> raise Exit
with Exit ->
(0, ("e", "f")) It means that the presence of |
There's something deep in the very first phases of pattern matching algorithm where pairs of values cannot be a single case. |
@zth this is ready for a functional review. |
Tagging @cknitt as well. |
This illustrates why the optimisation is not safe on tagged variants: #7146 |
Will test against a real-world project. |
Great! I was going to ask. |
Tested against a real-world project, and the output looked very good! 👍 Compared to alpha.4:
|
Agreed, looks great in my tests as well! 👍 |
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) { |
There was a problem hiding this comment.
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.
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) { |
There was a problem hiding this comment.
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.
First step: remove the distinction between cases with and without payload in the toplevel algorithm.
On this test:
Before:
After:
The 3 cases have become 2: check for optional fields and check for which case it is.