effekt icon indicating copy to clipboard operation
effekt copied to clipboard

Use information about previous pattern matches to optimize

Open b-studios opened this issue 5 months ago • 3 comments

For the following effekt code

import args

def join(left: Bool, right: Bool): Bool = (left, right) match {
  case (true, true) => true
  case (false,     _) => false
  case (_    , false) => false
}

def main() = commandLineArgs() match {
  case Cons(input, _) => println(join(input == "a", input == "b"))
  case Nil() => ()
}

our pattern matching compiler currently generates

switch (v_r_0.__tag) {
      case 1: 
        const input_0 = v_r_0.head_0;
        const tmp_3 = input_0 === "a";
        const tmp_4 = input_0 === "b";
        if (tmp_3) if (tmp_4) {
          const tmp_5 = $effekt.println("true");
          return () => k_1(tmp_5, ks_2);
        } else if (tmp_4) {
          return $effekt.hole();  // UNREACHABLE
        } else {
          const tmp_6 = $effekt.println("false");
          return () => k_1(tmp_6, ks_2);
        } else if (tmp_3) {
          return $effekt.hole(); // UNREACHABLE
        } else {
          const tmp_7 = $effekt.println("false");
          return () => k_1(tmp_7, ks_2);
        }
        break;
      case 0:  return () => k_1($effekt.unit, ks_2);
    }

where I marked the two holes as unreachable. We seem to lose the positive information about scrutinees (maybe look at Sestoft's paper again?)

Ideally, in this case we would generate

    if (tmp_3) {
      if (tmp_4) {
        const tmp_5 = $effekt.println("true");
        return () => k_1(tmp_5, ks_2);
      } else {
        const tmp_6 = $effekt.println("false");
        return () => k_1(tmp_6, ks_2);
      }
    } else {
      const tmp_7 = $effekt.println("false");
      return () => k_1(tmp_7, ks_2);
    }

b-studios avatar Jul 15 '25 09:07 b-studios

In case it helps anybody else, here's the [lightly edited] Core repr. of join:

def join_3621(left_3619: Bool_379, right_3620: Bool_379) = {
  let v_r_3797 = make Tuple2_253[Bool_379, Bool_379] Tuple2_311(left_3619, right_3620)
  val v_r_3801: Bool_379 = v_r_3797 match {
    case Tuple2_311 { (first_3659: Bool_379, second_3658: Bool_379) => 
      if (first_3659) {
        if (second_3658) {
          return true
        } else {
          if (second_3658) {
            <> // UNREACHABLE
          } else {
            return false
          }
        }
      } else {
        if (first_3659) {
          <> // UNREACHABLE
        } else {
          return false
        }
      }
    }
  };
  return v_r_3801
};

jiribenes avatar Jul 15 '25 09:07 jiribenes

FWIW, if I do the following (which might be wrong):

             case Some(values) =>
               if values.exists(b => !variants.map(_.value).contains(b)) then
                 addDefault(c)
-              variants.foreach { v => addClause(v, c) }
+                variants.foreach { v => addClause(v, c) }

then the output becomes a tad better, but wrong :(

  val v_r_3801: Bool_379 = v_r_3797 match {
    case Tuple2_311 { (first_3659: Bool_379, second_3658: Bool_379) => 
      if (first_3659) {
        if (second_3658) {
          b_k0_3798()
        } else {
          <>
        }
      } else {
        if (first_3659) {
          <>
        } else {
          b_k1_3799()
        }
      }
    }
  };

jiribenes avatar Jul 15 '25 10:07 jiribenes

@jiribenes don't worry about it too much, I think I know what to do ;)

b-studios avatar Jul 15 '25 10:07 b-studios