Skip to content
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

Fix free variable computation for default clauses in Machine #769

Merged
merged 6 commits into from
Jan 14, 2025

Conversation

phischu
Copy link
Collaborator

@phischu phischu commented Jan 10, 2025

Fixes 2 leaks from #758 (modifyat, updateat) and the leak blocking #711

@jiribenes
Copy link
Contributor

jiribenes commented Jan 10, 2025

On my machine, this completely unblocks #711 and even on CI, it now works with updateat and modifyat.


Unfortunately, it seems to break list_tail -- locally:

==54488==ERROR: AddressSanitizer: attempting double-free on 0x604000000950 in thread T0:
    #0 0x000100c2b23c in free+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64+0x5b23c)
    #1 0x000100938fbc in isShorterThan_2379+0x148 (list_tail:arm64+0x10000cfbc)
    #2 0x0001928b8270  (<unknown module>)

0x604000000950 is located 0 bytes inside of 48-byte region [0x604000000950,0x604000000980)
The crash is here in opt LLVM, I've marked @free with ???
define tailcc void @isShorterThan_2379(%Pos %x_2377, %Pos %y_2378, ptr %stack) local_unnamed_addr {
entry:
  br label %tailrecurse

tailrecurse:                                      ; preds = %eraseObject.exit23, %entry
  %x_2377.tr = phi %Pos [ %x_2377, %entry ], [ %v_coe_3357_9_4_43846, %eraseObject.exit23 ]
  %y_2378.tr = phi %Pos [ %y_2378, %entry ], [ %v_coe_3360_4_43673, %eraseObject.exit23 ]
  %tag_592 = extractvalue %Pos %y_2378.tr, 0
  switch i64 %tag_592, label %label_602 [
    i64 0, label %label_607
    i64 1, label %label_623
  ]

common.ret:                                       ; preds = %eraseObject.exit, %label_602
  ret void

label_601:                                        ; preds = %label_602
  %stackPointer_pointer.i = getelementptr i8, ptr %stack, i64 8
  %stackPointer.i = load ptr, ptr %stackPointer_pointer.i, align 8, !alias.scope !0
  %limit_pointer.i = getelementptr i8, ptr %stack, i64 24
  %limit.i = load ptr, ptr %limit_pointer.i, align 8, !alias.scope !0
  %isInside.i = icmp ule ptr %stackPointer.i, %limit.i
  tail call void @llvm.assume(i1 %isInside.i)
  %newStackPointer.i = getelementptr i8, ptr %stackPointer.i, i64 -24
  store ptr %newStackPointer.i, ptr %stackPointer_pointer.i, align 8, !alias.scope !0
  %returnAddress_598 = load ptr, ptr %newStackPointer.i, align 8, !noalias !0
  musttail call tailcc void %returnAddress_598(%Pos { i64 1, ptr null }, ptr %stack)
  ret void

label_602:                                        ; preds = %tailrecurse
  %tag_594 = extractvalue %Pos %x_2377.tr, 0
  %cond = icmp eq i64 %tag_594, 0
  br i1 %cond, label %label_601, label %common.ret

label_607:                                        ; preds = %tailrecurse
  %object.i39 = extractvalue %Pos %x_2377.tr, 1
  %isNull.i.i40 = icmp eq ptr %object.i39, null
  br i1 %isNull.i.i40, label %erasePositive.exit38, label %next.i.i41

next.i.i41:                                       ; preds = %label_607
  %referenceCount.i.i42 = load i64, ptr %object.i39, align 4
  %cond.i.i43 = icmp eq i64 %referenceCount.i.i42, 0
  br i1 %cond.i.i43, label %free.i.i46, label %decr.i.i44

decr.i.i44:                                       ; preds = %next.i.i41
  %referenceCount.1.i.i45 = add i64 %referenceCount.i.i42, -1
  store i64 %referenceCount.1.i.i45, ptr %object.i39, align 4
  br label %next.i.i29

free.i.i46:                                       ; preds = %next.i.i41
  %objectEraser.i.i47 = getelementptr i8, ptr %object.i39, i64 8
  %eraser.i.i48 = load ptr, ptr %objectEraser.i.i47, align 8
  %environment.i.i.i49 = getelementptr i8, ptr %object.i39, i64 16
  tail call void %eraser.i.i48(ptr %environment.i.i.i49)
  tail call void @free(ptr nonnull %object.i39) ; ???
  %referenceCount.i.i30.pr = load i64, ptr %object.i39, align 4
  br label %next.i.i29

next.i.i29:                                       ; preds = %decr.i.i44, %free.i.i46
  %referenceCount.i.i30 = phi i64 [ %referenceCount.1.i.i45, %decr.i.i44 ], [ %referenceCount.i.i30.pr, %free.i.i46 ]
  %cond.i.i31 = icmp eq i64 %referenceCount.i.i30, 0
  br i1 %cond.i.i31, label %free.i.i34, label %decr.i.i32

decr.i.i32:                                       ; preds = %next.i.i29
  %referenceCount.1.i.i33 = add i64 %referenceCount.i.i30, -1
  store i64 %referenceCount.1.i.i33, ptr %object.i39, align 4
  br label %erasePositive.exit38

free.i.i34:                                       ; preds = %next.i.i29
  %objectEraser.i.i35 = getelementptr i8, ptr %object.i39, i64 8
  %eraser.i.i36 = load ptr, ptr %objectEraser.i.i35, align 8
  %environment.i.i.i37 = getelementptr i8, ptr %object.i39, i64 16
  tail call void %eraser.i.i36(ptr %environment.i.i.i37)
  tail call void @free(ptr nonnull %object.i39) ; ???
  br label %erasePositive.exit38

erasePositive.exit38:                             ; preds = %label_607, %decr.i.i32, %free.i.i34
  %stackPointer_pointer.i51 = getelementptr i8, ptr %stack, i64 8
  %stackPointer.i52 = load ptr, ptr %stackPointer_pointer.i51, align 8, !alias.scope !0
  %limit_pointer.i53 = getelementptr i8, ptr %stack, i64 24
  %limit.i54 = load ptr, ptr %limit_pointer.i53, align 8, !alias.scope !0
  %isInside.i55 = icmp ule ptr %stackPointer.i52, %limit.i54
  tail call void @llvm.assume(i1 %isInside.i55)
  %newStackPointer.i56 = getelementptr i8, ptr %stackPointer.i52, i64 -24
  store ptr %newStackPointer.i56, ptr %stackPointer_pointer.i51, align 8, !alias.scope !0
  %returnAddress_604 = load ptr, ptr %newStackPointer.i56, align 8, !noalias !0
  musttail call tailcc void %returnAddress_604(%Pos zeroinitializer, ptr %stack)
  ret void

label_618:                                        ; preds = %eraseObject.exit
  br i1 %isNull.i.i, label %erasePositive.exit, label %next.i.i25

next.i.i25:                                       ; preds = %label_618
  %referenceCount.i.i26 = load i64, ptr %v_coe_3360_4_4367.unpack2, align 4
  %cond.i.i = icmp eq i64 %referenceCount.i.i26, 0
  br i1 %cond.i.i, label %free.i.i, label %decr.i.i

decr.i.i:                                         ; preds = %next.i.i25
  %referenceCount.1.i.i27 = add i64 %referenceCount.i.i26, -1
  store i64 %referenceCount.1.i.i27, ptr %v_coe_3360_4_4367.unpack2, align 4
  br label %erasePositive.exit

free.i.i:                                         ; preds = %next.i.i25
  %objectEraser.i.i = getelementptr i8, ptr %v_coe_3360_4_4367.unpack2, i64 8
  %eraser.i.i = load ptr, ptr %objectEraser.i.i, align 8
  %environment.i.i.i = getelementptr i8, ptr %v_coe_3360_4_4367.unpack2, i64 16
  tail call void %eraser.i.i(ptr %environment.i.i.i)
  tail call void @free(ptr nonnull %v_coe_3360_4_4367.unpack2) ; ???
  br label %erasePositive.exit

erasePositive.exit:                               ; preds = %label_618, %decr.i.i, %free.i.i
  %stackPointer_pointer.i57 = getelementptr i8, ptr %stack, i64 8
  %stackPointer.i58 = load ptr, ptr %stackPointer_pointer.i57, align 8, !alias.scope !0
  %limit_pointer.i59 = getelementptr i8, ptr %stack, i64 24
  %limit.i60 = load ptr, ptr %limit_pointer.i59, align 8, !alias.scope !0
  %isInside.i61 = icmp ule ptr %stackPointer.i58, %limit.i60
  tail call void @llvm.assume(i1 %isInside.i61)
  %newStackPointer.i62 = getelementptr i8, ptr %stackPointer.i58, i64 -24
  store ptr %newStackPointer.i62, ptr %stackPointer_pointer.i57, align 8, !alias.scope !0
  %returnAddress_615 = load ptr, ptr %newStackPointer.i62, align 8, !noalias !0
  musttail call tailcc void %returnAddress_615(%Pos { i64 1, ptr null }, ptr %stack)
  ret void

label_622:                                        ; preds = %eraseObject.exit
  %fields_612 = extractvalue %Pos %x_2377.tr, 1
  %environment.i7 = getelementptr i8, ptr %fields_612, i64 16
  %v_coe_3357_9_4_4384_pointer_621 = getelementptr i8, ptr %fields_612, i64 32
  %v_coe_3357_9_4_4384.unpack = load i64, ptr %v_coe_3357_9_4_4384_pointer_621, align 8, !noalias !0
  %v_coe_3357_9_4_4384.elt4 = getelementptr i8, ptr %fields_612, i64 40
  %v_coe_3357_9_4_4384.unpack5 = load ptr, ptr %v_coe_3357_9_4_4384.elt4, align 8, !noalias !0
  %isNull.i.i8 = icmp eq ptr %v_coe_3357_9_4_4384.unpack5, null
  br i1 %isNull.i.i8, label %next.i14, label %next.i.i9

next.i.i9:                                        ; preds = %label_622
  %referenceCount.i.i10 = load i64, ptr %v_coe_3357_9_4_4384.unpack5, align 4
  %referenceCount.1.i.i11 = add i64 %referenceCount.i.i10, 1
  store i64 %referenceCount.1.i.i11, ptr %v_coe_3357_9_4_4384.unpack5, align 4
  br label %next.i14

next.i14:                                         ; preds = %next.i.i9, %label_622
  %referenceCount.i15 = load i64, ptr %fields_612, align 4
  %cond.i16 = icmp eq i64 %referenceCount.i15, 0
  br i1 %cond.i16, label %free.i19, label %decr.i17

decr.i17:                                         ; preds = %next.i14
  %referenceCount.1.i18 = add i64 %referenceCount.i15, -1
  store i64 %referenceCount.1.i18, ptr %fields_612, align 4
  br label %eraseObject.exit23

free.i19:                                         ; preds = %next.i14
  %objectEraser.i20 = getelementptr i8, ptr %fields_612, i64 8
  %eraser.i21 = load ptr, ptr %objectEraser.i20, align 8
  tail call void %eraser.i21(ptr %environment.i7)
  tail call void @free(ptr nonnull %fields_612) ; ???
  br label %eraseObject.exit23

eraseObject.exit23:                               ; preds = %decr.i17, %free.i19
  %0 = insertvalue %Pos poison, i64 %v_coe_3357_9_4_4384.unpack, 0
  %v_coe_3357_9_4_43846 = insertvalue %Pos %0, ptr %v_coe_3357_9_4_4384.unpack5, 1
  %1 = insertvalue %Pos poison, i64 %v_coe_3360_4_4367.unpack, 0
  %v_coe_3360_4_43673 = insertvalue %Pos %1, ptr %v_coe_3360_4_4367.unpack2, 1
  br label %tailrecurse

label_623:                                        ; preds = %tailrecurse
  %fields_593 = extractvalue %Pos %y_2378.tr, 1
  %environment.i = getelementptr i8, ptr %fields_593, i64 16
  %v_coe_3360_4_4367_pointer_610 = getelementptr i8, ptr %fields_593, i64 32
  %v_coe_3360_4_4367.unpack = load i64, ptr %v_coe_3360_4_4367_pointer_610, align 8, !noalias !0
  %v_coe_3360_4_4367.elt1 = getelementptr i8, ptr %fields_593, i64 40
  %v_coe_3360_4_4367.unpack2 = load ptr, ptr %v_coe_3360_4_4367.elt1, align 8, !noalias !0
  %isNull.i.i = icmp eq ptr %v_coe_3360_4_4367.unpack2, null
  br i1 %isNull.i.i, label %next.i, label %next.i.i

next.i.i:                                         ; preds = %label_623
  %referenceCount.i.i = load i64, ptr %v_coe_3360_4_4367.unpack2, align 4
  %referenceCount.1.i.i = add i64 %referenceCount.i.i, 1
  store i64 %referenceCount.1.i.i, ptr %v_coe_3360_4_4367.unpack2, align 4
  br label %next.i

next.i:                                           ; preds = %next.i.i, %label_623
  %referenceCount.i = load i64, ptr %fields_593, align 4
  %cond.i = icmp eq i64 %referenceCount.i, 0
  br i1 %cond.i, label %free.i, label %decr.i

decr.i:                                           ; preds = %next.i
  %referenceCount.1.i = add i64 %referenceCount.i, -1
  store i64 %referenceCount.1.i, ptr %fields_593, align 4
  br label %eraseObject.exit

free.i:                                           ; preds = %next.i
  %objectEraser.i = getelementptr i8, ptr %fields_593, i64 8
  %eraser.i = load ptr, ptr %objectEraser.i, align 8
  tail call void %eraser.i(ptr %environment.i)
  tail call void @free(ptr nonnull %fields_593) ; ???
  br label %eraseObject.exit

eraseObject.exit:                                 ; preds = %decr.i, %free.i
  %tag_611 = extractvalue %Pos %x_2377.tr, 0
  switch i64 %tag_611, label %common.ret [
    i64 0, label %label_618
    i64 1, label %label_622
  ]
}
Here's the unopt LLVM of the same function
define tailcc void @isShorterThan_2379(%Pos %x_2377, %Pos %y_2378, %Stack %stack) {
        
    entry:
        
        ; definition isShorterThan_2379, environment length 2
        
        ; switch y_2378, 2 clauses
        %tag_592 = extractvalue %Pos %y_2378, 0
        %fields_593 = extractvalue %Pos %y_2378, 1
        switch i64 %tag_592, label %label_602 [i64 0, label %label_607 i64 1, label %label_623]
    
    label_596:
        
        ret void
    
    label_601:
        
        ; construct booleanLiteral_5273, tag 1, 0 values
        %booleanLiteral_5273_temporary_597 = insertvalue %Pos zeroinitializer, i64 1, 0
        %booleanLiteral_5273 = insertvalue %Pos %booleanLiteral_5273_temporary_597, %Object null, 1
        
        ; return, 1 values
        %stackPointer_599 = call ccc %StackPointer @stackDeallocate(%Stack %stack, i64 24)
        %returnAddress_pointer_600 = getelementptr %FrameHeader, %StackPointer %stackPointer_599, i64 0, i32 0
        %returnAddress_598 = load %ReturnAddress, ptr %returnAddress_pointer_600, !noalias !2
        musttail call tailcc void %returnAddress_598(%Pos %booleanLiteral_5273, %Stack %stack)
        ret void
    
    label_602:
        
        ; switch x_2377, 1 clauses
        %tag_594 = extractvalue %Pos %x_2377, 0
        %fields_595 = extractvalue %Pos %x_2377, 1
        switch i64 %tag_594, label %label_596 [i64 0, label %label_601]
    
    label_607:
        call ccc void @erasePositive(%Pos %x_2377)
        call ccc void @erasePositive(%Pos %x_2377)
        
        ; construct booleanLiteral_5271, tag 0, 0 values
        %booleanLiteral_5271_temporary_603 = insertvalue %Pos zeroinitializer, i64 0, 0
        %booleanLiteral_5271 = insertvalue %Pos %booleanLiteral_5271_temporary_603, %Object null, 1
        
        ; return, 1 values
        %stackPointer_605 = call ccc %StackPointer @stackDeallocate(%Stack %stack, i64 24)
        %returnAddress_pointer_606 = getelementptr %FrameHeader, %StackPointer %stackPointer_605, i64 0, i32 0
        %returnAddress_604 = load %ReturnAddress, ptr %returnAddress_pointer_606, !noalias !2
        musttail call tailcc void %returnAddress_604(%Pos %booleanLiteral_5271, %Stack %stack)
        ret void
    
    label_613:
        
        ret void
    
    label_618:
        call ccc void @erasePositive(%Pos %v_coe_3360_4_4367)
        
        ; construct booleanLiteral_5272, tag 1, 0 values
        %booleanLiteral_5272_temporary_614 = insertvalue %Pos zeroinitializer, i64 1, 0
        %booleanLiteral_5272 = insertvalue %Pos %booleanLiteral_5272_temporary_614, %Object null, 1
        
        ; return, 1 values
        %stackPointer_616 = call ccc %StackPointer @stackDeallocate(%Stack %stack, i64 24)
        %returnAddress_pointer_617 = getelementptr %FrameHeader, %StackPointer %stackPointer_616, i64 0, i32 0
        %returnAddress_615 = load %ReturnAddress, ptr %returnAddress_pointer_617, !noalias !2
        musttail call tailcc void %returnAddress_615(%Pos %booleanLiteral_5272, %Stack %stack)
        ret void
    
    label_622:
        %environment_619 = call ccc %Environment @objectEnvironment(%Object %fields_612)
        %v_coe_3356_8_3_4381_pointer_620 = getelementptr {%Pos, %Pos}, %Environment %environment_619, i64 0, i32 0
        %v_coe_3356_8_3_4381 = load %Pos, ptr %v_coe_3356_8_3_4381_pointer_620, !noalias !2
        %v_coe_3357_9_4_4384_pointer_621 = getelementptr {%Pos, %Pos}, %Environment %environment_619, i64 0, i32 1
        %v_coe_3357_9_4_4384 = load %Pos, ptr %v_coe_3357_9_4_4384_pointer_621, !noalias !2
        call ccc void @sharePositive(%Pos %v_coe_3357_9_4_4384)
        call ccc void @eraseObject(%Object %fields_612)
        
        ; substitution
        
        ; substitution [x_2377 !-> v_coe_3357_9_4_4384]
        
        ; substitution [y_2378 !-> v_coe_3360_4_4367]
        
        ; jump isShorterThan_2379
        musttail call tailcc void @isShorterThan_2379(%Pos %v_coe_3357_9_4_4384, %Pos %v_coe_3360_4_4367, %Stack %stack)
        ret void
    
    label_623:
        %environment_608 = call ccc %Environment @objectEnvironment(%Object %fields_593)
        %v_coe_3359_3_4370_pointer_609 = getelementptr {%Pos, %Pos}, %Environment %environment_608, i64 0, i32 0
        %v_coe_3359_3_4370 = load %Pos, ptr %v_coe_3359_3_4370_pointer_609, !noalias !2
        %v_coe_3360_4_4367_pointer_610 = getelementptr {%Pos, %Pos}, %Environment %environment_608, i64 0, i32 1
        %v_coe_3360_4_4367 = load %Pos, ptr %v_coe_3360_4_4367_pointer_610, !noalias !2
        call ccc void @sharePositive(%Pos %v_coe_3360_4_4367)
        call ccc void @eraseObject(%Object %fields_593)
        
        ; switch x_2377, 2 clauses
        %tag_611 = extractvalue %Pos %x_2377, 0
        %fields_612 = extractvalue %Pos %x_2377, 1
        switch i64 %tag_611, label %label_613 [i64 0, label %label_618 i64 1, label %label_622]
}

and here's the Machine IR:

def isShorterThan_2379 = {
  switch y_2378 
    0 : { () =>
      let booleanLiteral_9068 = 0();
      return booleanLiteral_9068
    }
    1 : { (v_coe_7247_3_8161 : Positive, v_coe_7248_4_8164 : Positive) =>
      switch x_2377 
        0 : { () =>
          let booleanLiteral_9069 = 1();
          return booleanLiteral_9069
        }
        1 : { (v_coe_7244_8_3_8177 : Positive, v_coe_7245_9_4_8179 : Positive) =>
          subst [x_2377 !-> v_coe_7245_9_4_8179, y_2378 !-> v_coe_7248_4_8164];
          jump isShorterThan_2379
        }
    } else { () =>
    switch x_2377 
      0 : { () =>
        let booleanLiteral_9070 = 1();
        return booleanLiteral_9070
      }
  }
};

and the Core IR:

def isShorterThan2379(x2377: List910[BoxedInt296], y2378: List910[BoxedInt296]) = y2378 match {
  case Nil1118 { () => 
    return false
  }
  case Cons1119 { (v_coe_5398_36316: BoxedInt296, v_coe_5399_46311: List910[BoxedInt296]) => 
    x2377 match {
      case Nil1118 { () => 
        return true
      }
      case Cons1119 { (v_coe_5395_8_36327: BoxedInt296, v_coe_5396_9_46330: List910[BoxedInt296]) => 
        isShorterThan2379(v_coe_5396_9_46330, v_coe_5399_46311)
      }
    }
  }
} else {
  x2377 match {
    case Nil1118 { () => 
      return true
    }
  }};

and the source is:

def isShorterThan(x: List[Int], y: List[Int]): Bool =
  (x, y) match {
    case (_, Nil()) => false
    case (Nil(), _) => true
    case (Cons(_, xs),Cons(_, ys)) => isShorterThan(xs, ys)
  }

The else in the pattern match is weird, filed as #770.

@jiribenes
Copy link
Contributor

Feel free to adopt this PR, I'm not planning to work on it further for now.

@jiribenes jiribenes changed the title Fix finding free variables Fix free variable computation for default clauses in Machine Jan 10, 2025
@marvinborner
Copy link
Member

marvinborner commented Jan 11, 2025

Variables appearing free in normal as well as default clauses were erased multiple times because we used a list instead of a set for tracking the free variables. I've pushed a fix, let me know if this is a valid solution.

This fixes the issue with list_tail (still weird that the default clause exists in the first place, though)

@jiribenes
Copy link
Contributor

I've just noticed the force push removed the repro (sigh) and the uncommenting of updateat and modifyat which are useful to test the changes with.

@marvinborner
Copy link
Member

I'm a bit lost at fixing tree_explore, maybe @mattisboeckle can help? In switch clauses that reference the scrutinee (i.e. value appears free in clause.body), the scrutinee potentially gets shared more than it gets erased, causing a memory leak. Although it's probably just a single-line fix where we forgot to add an eraser, all my attempts made some other tests fail. My reduced reproduction program for two kinds of leaks:

def foo(l: List[Int]): Int =
  l match {
    case Nil() => 42
    case _ => foo(l.drop(1))
  }

record Bar[T](drinks: List[T])

def bar(bar: Bar[Int]): Int = bar match {
  case Bar(_) => foo(bar.drinks)
}

def main() = println(Bar([1, 2, 3]).bar)

@phischu
Copy link
Collaborator Author

phischu commented Jan 11, 2025

Ah, we have to erase the scrutinee in the default clause!

@marvinborner
Copy link
Member

Ooh yes, that's much simpler than what I suspected. How should we differentiate between default and normal clause generation? We use labelClause for both right now.

@jiribenes
Copy link
Contributor

jiribenes commented Jan 13, 2025

Ooh yes, that's much simpler than what I suspected. How should we differentiate between default and normal clause generation? We use labelClause for both right now.

FWIW when trying this out on Friday, @phischu tried to intuitively do something like:

+       def labelClause(clause: machine.Clause, isDefault: Boolean): String = {
-       def labelClause(clause: machine.Clause): String = {
          implicit val BC = BlockContext()
          BC.stack = stack

+         if (isDefault) { ... } else { ... }

...

        val defaultLabel = default match {
+         case Some(clause) => labelClause(clause, isDefault = true)
-         case Some(clause) => labelClause(clause)
          case None =>
            val label = freshName("label");
            emit(BasicBlock(label, List(), RetVoid()))
            label
        }
        val labels = clauses.map {
+         case (tag, clause) => (tag, labelClause(clause, isDefault = false))
-         case (tag, clause) => (tag, labelClause(clause))
        }

@jiribenes
Copy link
Contributor

If anybody reading this has the time, I'd love to know if the changes from the PR still fix the leak in #711 :)

@marvinborner
Copy link
Member

Yep, just tried and it looks like it's not leaking anymore!

@jiribenes
Copy link
Contributor

Yep, just tried and it looks like it's not leaking anymore!

Great, thanks for confirming! 🎉
I'll mark the PR as "Ready for review" then and we can perhaps quickly go through it tomorrow at the meeting?

@jiribenes jiribenes marked this pull request as ready for review January 13, 2025 13:01
@b-studios b-studios merged commit b977830 into master Jan 14, 2025
2 checks passed
@b-studios b-studios deleted the fix/more_leaks branch January 14, 2025 15:43
EveEme pushed a commit to EveEme/effekt that referenced this pull request Jan 20, 2025
…lang#769)

Fixes 2 leaks from effekt-lang#758 (`modifyat`, `updateat`) and the leak blocking
effekt-lang#711

---------

Co-authored-by: Marvin Borner <git@marvinborner.de>
Co-authored-by: Jiří Beneš <mail@jiribenes.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants