From 9ac047b4f3423d5dd2694055cb5cb36a159c9dcf Mon Sep 17 00:00:00 2001 From: Vic Nightfall Date: Sun, 5 May 2024 14:46:10 +0200 Subject: [PATCH 1/5] Fix lambda ref count initializer --- src/compiler.pr | 55 ++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/compiler.pr b/src/compiler.pr index ec3ff0b..dd323f9 100644 --- a/src/compiler.pr +++ b/src/compiler.pr @@ -1420,7 +1420,7 @@ def convert_ref_to_ref(tpe: &typechecking::Type, value: Value, loc: &Value, stat add_type_meta(tpe, state) - let extract1_ret = state.extract_value(pointer(ref_meta), value, [0], loc) + let extract1_ret = state.extract_value(pointer(ref_meta()), value, [0], loc) let extract2_ret = state.extract_value(pointer(value.tpe.tpe), value, [1], loc) let extract3_ret = state.extract_value(pointer(builtins::Type_), value, [2], loc) let bitcast_ret = state.bitcast(pointer(tpe.tpe if tpe.tpe else builtins::int8_), extract2_ret, loc) @@ -1439,7 +1439,7 @@ def convert_ref_to_ref(tpe: &typechecking::Type, value: Value, loc: &Value, stat push_label(nonnull, state) br1.value.br.if_false = nonnull - let meta = state.load(ref_meta, extract1_ret, loc) + let meta = state.load(ref_meta(), extract1_ret, loc) let refcount = state.extract_value(builtins::int64_, meta, [0], loc) let iszero = state.icmp(CompareInt::sle, refcount, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value, loc) @@ -1488,7 +1488,7 @@ def convert_ref_to_ref(tpe: &typechecking::Type, value: Value, loc: &Value, stat def convert_ref_to_ptr(tpe: &typechecking::Type, value: Value, loc: &Value, state: &State) -> Value { if is_weak_ref(value.tpe) { - let meta = state.extract_value(pointer(ref_meta), value, [0], loc) + let meta = state.extract_value(pointer(ref_meta()), value, [0], loc) let ref_value = state.ptr_to_int(meta, loc) let isnull = state.icmp(CompareInt::eq, ref_value, @@ -1501,7 +1501,7 @@ def convert_ref_to_ptr(tpe: &typechecking::Type, value: Value, loc: &Value, stat push_label(nonnull, state) br1.value.br.if_false = nonnull - let meta_val = state.load(ref_meta, meta, loc) + let meta_val = state.load(ref_meta(), meta, loc) let cnt = state.extract_value(builtins::int64_, meta_val, [0], loc) let iszero = state.icmp(CompareInt::sle, cnt, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) let res = state.alloca(pointer(tpe.tpe), loc) @@ -1543,10 +1543,16 @@ def convert_ref_to_ptr(tpe: &typechecking::Type, value: Value, loc: &Value, stat } // Reference metadata struct -let ref_meta = typechecking::make_struct_type([ - [ tpe = builtins::int64_, name = "refcount" ] !typechecking::StructMember, - [ tpe = builtins::int64_, name = "weakcount" ] !typechecking::StructMember -]) +var _ref_meta: &typechecking::Type +def ref_meta -> &typechecking::Type { + if not _ref_meta { + _ref_meta = typechecking::make_struct_type([ + [ tpe = builtins::int64_, name = "refcount" ] !typechecking::StructMember, + [ tpe = builtins::int64_, name = "weakcount" ] !typechecking::StructMember + ]) + } + return _ref_meta +} def convert_value_to_ref(tpe: &typechecking::Type, value: Value, loc: &Value, state: &State, initial_ref_count: size_t = 0) -> Value { if tpe.tpe and value.tpe.kind != tpe.tpe.kind { @@ -1570,11 +1576,11 @@ def convert_value_to_ref(tpe: &typechecking::Type, value: Value, loc: &Value, st if not is_null { // create ref counts let args1 = allocate_ref(Value, 1) - args1(0) = [ kind = ValueKind::INT, tpe = builtins::int64_, i = ref_meta.size] !Value + args1(0) = [ kind = ValueKind::INT, tpe = builtins::int64_, i = ref_meta().size] !Value var call1_ret = state.call("malloc", pointer(builtins::int8_), args1, loc) - call1_ret = state.bitcast(pointer(ref_meta), call1_ret, loc) - refcount = state.gep(pointer(builtins::int64_), ref_meta, call1_ret, [make_int_value(0), make_int_value(0)], loc) - let weakcount = state.gep(pointer(builtins::int64_), ref_meta, call1_ret, [make_int_value(0), make_int_value(1)], loc) + call1_ret = state.bitcast(pointer(ref_meta()), call1_ret, loc) + refcount = state.gep(pointer(builtins::int64_), ref_meta(), call1_ret, [make_int_value(0), make_int_value(0)], loc) + let weakcount = state.gep(pointer(builtins::int64_), ref_meta(), call1_ret, [make_int_value(0), make_int_value(1)], loc) state.store(refcount, [ kind = ValueKind::INT, tpe = builtins::int64_, i = initial_ref_count ] !Value) state.store(weakcount, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 1 ] !Value) } @@ -3449,7 +3455,7 @@ def insert_destructors(scpe: &scope::Scope, loc: &Value, state: &State) { def check_clear_ref_meta(meta_ptr: Value, loc: &Value, state: &State) { import_cstd_function("free", state) - let meta = state.load(ref_meta, meta_ptr, loc) + let meta = state.load(ref_meta(), meta_ptr, loc) let cnt = state.extract_value(builtins::int64_, meta, [1], loc) let cond = state.icmp(CompareInt::sle, cnt, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) @@ -3477,7 +3483,7 @@ def insert_destructor(value: Value, loc: &Value, state: &State) { if is_weak_ref(value.tpe.tpe) { // value refers to an address so we gotta load it first value = state.load(value.tpe.tpe, value, loc) - let meta = state.extract_value(pointer(ref_meta), value, [0], loc) + let meta = state.extract_value(pointer(ref_meta()), value, [0], loc) let ptr_addr = state.ptr_to_int(meta, loc) let cond = state.icmp(CompareInt::eq, ptr_addr, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) @@ -3562,7 +3568,7 @@ def insert_destructor(value: Value, loc: &Value, state: &State) { def increase_ref_count_of_value(value: Value, loc: &Value, state: &State) { if not is_ref_or_weak(value.tpe) { return } - let meta = state.extract_value(pointer(ref_meta), value, [0], loc) + let meta = state.extract_value(pointer(ref_meta()), value, [0], loc) let ptr_addr = state.ptr_to_int(meta, loc) let cond = state.icmp(CompareInt::eq, ptr_addr, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) @@ -5874,9 +5880,12 @@ def create_closure_context(function: &Function, ret: Value, loc: &Value, state: let context_tpe = function.state let context_ptr_i8 = state.call("malloc", pointer(builtins::int8_), [[ kind = ValueKind::INT, tpe = builtins::size_t_, i = context_tpe.size ] !Value], loc) - let ref_count_i8 = state.call("malloc", pointer(builtins::int8_), [[ kind = ValueKind::INT, tpe = builtins::size_t_, i = builtins::int64_.size ] !Value], loc) + let ref_count_i8 = state.call("malloc", pointer(builtins::int8_), [[ kind = ValueKind::INT, tpe = builtins::size_t_, i = ref_meta().size ] !Value], loc) let ref_count = state.bitcast(pointer(builtins::int64_), ref_count_i8, loc) - state.store(ref_count, [ kind = ValueKind::INT, tpe = ValueKind::int64_, i = 1 ] !Value, loc) + state.store(ref_count, [ kind = ValueKind::STRUCT, tpe = ref_meta(), values = [ + [kind = ValueKind::INT, tpe = builtins::int64_, i = 1 ] !Value, + [kind = ValueKind::INT, tpe = builtins::int64_, i = 1 ] !Value + ]] !Value, loc) let context_tpe_value = do_create_type(reference(context_tpe), state.module) @@ -8227,12 +8236,12 @@ export def create_destructor(tpe: &typechecking::Type) { } def get_ref_count_ptr(value: Value, loc: &Value, state: &State) -> Value { - let meta = state.extract_value(pointer(ref_meta), value, [0], loc) - return state.gep(pointer(builtins::int64_), ref_meta, meta, [make_int_value(0), make_int_value(0)], loc) + let meta = state.extract_value(pointer(ref_meta()), value, [0], loc) + return state.gep(pointer(builtins::int64_), ref_meta(), meta, [make_int_value(0), make_int_value(0)], loc) } def get_weak_count_ptr(value: Value, loc: &Value, state: &State) -> Value { - let meta = state.extract_value(pointer(ref_meta), value, [0], loc) - return state.gep(pointer(builtins::int64_), ref_meta, meta, [make_int_value(0), make_int_value(1)], loc) + let meta = state.extract_value(pointer(ref_meta()), value, [0], loc) + return state.gep(pointer(builtins::int64_), ref_meta(), meta, [make_int_value(0), make_int_value(1)], loc) } def create_destructor(tpe: &typechecking::Type, value: Value, state: &State) { @@ -8290,8 +8299,8 @@ def create_destructor(tpe: &typechecking::Type, value: Value, state: &State) { if typechecking::is_ref(tpe.tpe) and tpe.tpe.tpe and not is_interface(tpe.tpe.tpe) { // Decrease ref count ref = state.load(tpe.tpe, value) - ref_count = state.extract_value(pointer(ref_meta), ref, [0]) - let ref_count_ptr = state.gep(pointer(builtins::int64_), ref_meta, ref_count, [make_int_value(0), make_int_value(0)]) + ref_count = state.extract_value(pointer(ref_meta()), ref, [0]) + let ref_count_ptr = state.gep(pointer(builtins::int64_), ref_meta(), ref_count, [make_int_value(0), make_int_value(0)]) let ref_count_value = state.ptr_to_int(ref_count_ptr) let null_cond = state.icmp(CompareInt::eq, ref_count_value, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) null_br = make_insn(InsnKind::BR) From af2c39b6fde15b5b32122ae890fa62adc37a4138 Mon Sep 17 00:00:00 2001 From: Vic Nightfall Date: Sun, 5 May 2024 14:55:39 +0200 Subject: [PATCH 2/5] Fix ref count for generators --- src/compiler.pr | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/compiler.pr b/src/compiler.pr index dd323f9..71974db 100644 --- a/src/compiler.pr +++ b/src/compiler.pr @@ -5881,7 +5881,7 @@ def create_closure_context(function: &Function, ret: Value, loc: &Value, state: let context_ptr_i8 = state.call("malloc", pointer(builtins::int8_), [[ kind = ValueKind::INT, tpe = builtins::size_t_, i = context_tpe.size ] !Value], loc) let ref_count_i8 = state.call("malloc", pointer(builtins::int8_), [[ kind = ValueKind::INT, tpe = builtins::size_t_, i = ref_meta().size ] !Value], loc) - let ref_count = state.bitcast(pointer(builtins::int64_), ref_count_i8, loc) + let ref_count = state.bitcast(pointer(ref_meta()), ref_count_i8, loc) state.store(ref_count, [ kind = ValueKind::STRUCT, tpe = ref_meta(), values = [ [kind = ValueKind::INT, tpe = builtins::int64_, i = 1 ] !Value, [kind = ValueKind::INT, tpe = builtins::int64_, i = 1 ] !Value @@ -7135,11 +7135,12 @@ def create_generator( let context_ptr_i8_ptr = state.gep(pointer(generator.fields(1).tpe), generator, gen_ptr, [make_int_value(0), make_int_value(1)]) state.store(context_ptr_i8_ptr, context_ptr_i8) - let ref_count_ptr_i8 = state.call("malloc", typechecking::pointer(builtins::int8_), [ - [ kind = ValueKind::INT, tpe = builtins::size_t_, i = builtins::size_t_.size ] !Value - ]) - let ref_count = state.bitcast(pointer(builtins::size_t_), ref_count_ptr_i8) - state.store(ref_count, [ kind = ValueKind::INT, tpe = builtins::size_t_, i = 1 ] !Value) + let ref_count_i8 = state.call("malloc", pointer(builtins::int8_), [[ kind = ValueKind::INT, tpe = builtins::size_t_, i = ref_meta().size ] !Value]) + let ref_count = state.bitcast(pointer(ref_meta()), ref_count_i8) + state.store(ref_count, [ kind = ValueKind::STRUCT, tpe = ref_meta(), values = [ + [kind = ValueKind::INT, tpe = builtins::int64_, i = 1 ] !Value, + [kind = ValueKind::INT, tpe = builtins::int64_, i = 1 ] !Value + ]] !Value) var gen_ref = [ kind = ValueKind::ZEROINITIALIZER, tpe = reference(generator) ] !Value gen_ref = state.insert_value(reference(generator), gen_ref, ref_count, [0]) From bed2b9e103c91dc89e5795796443d1fbfa8bbe80 Mon Sep 17 00:00:00 2001 From: Vic Nightfall Date: Sun, 5 May 2024 19:02:37 +0200 Subject: [PATCH 3/5] Fix a few more bugs preventing the compiler from compiling itself! --- src/compiler.pr | 50 +++++++++++++++++++++++++++++++++++++++++-------- src/repl.pr | 2 +- src/runtime.pr | 7 ++++++- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/compiler.pr b/src/compiler.pr index 71974db..b09dd88 100644 --- a/src/compiler.pr +++ b/src/compiler.pr @@ -600,6 +600,7 @@ export type State = struct { return_address: Value // This is used as the return address in inline functions return_br: &Insn // This is used to jump back to the end of the function inline_start_block: &Block // This represents the first block of the current function + context_ptr: int // Counts up the current free_context } export def destruct(state: *State) { @@ -1544,7 +1545,7 @@ def convert_ref_to_ptr(tpe: &typechecking::Type, value: Value, loc: &Value, stat // Reference metadata struct var _ref_meta: &typechecking::Type -def ref_meta -> &typechecking::Type { +export def ref_meta -> &typechecking::Type { if not _ref_meta { _ref_meta = typechecking::make_struct_type([ [ tpe = builtins::int64_, name = "refcount" ] !typechecking::StructMember, @@ -3616,9 +3617,13 @@ def copy_reference(value: Value, loc: &Value, state: &State) -> Value { let size = [ kind = ValueKind::INT, tpe = builtins::int64_, i = value.tpe.tpe.size ] !Value let size_int64 = [ kind = ValueKind::INT, tpe = builtins::int64_, i = builtins::int64_.size ] !Value - let ref_count_ptr_i8 = state.call("malloc", pointer(builtins::int8_), [size_int64], loc) - let ref_count_ptr = state.bitcast(pointer(builtins::int64_), ref_count_ptr_i8) - state.store(ref_count_ptr, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) + let ref_count_i8 = state.call("malloc", pointer(builtins::int8_), [[ kind = ValueKind::INT, tpe = builtins::size_t_, i = ref_meta().size ] !Value]) + let ref_count = state.bitcast(pointer(ref_meta()), ref_count_i8) + state.store(ref_count, [ kind = ValueKind::STRUCT, tpe = ref_meta(), values = [ + [kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value, + [kind = ValueKind::INT, tpe = builtins::int64_, i = 1 ] !Value + ]] !Value) + let value_ptr = state.extract_value(pointer(value.tpe.tpe), value, [1], loc) let type_ptr = state.extract_value(pointer(builtins::Type_), value, [2], loc) @@ -3628,7 +3633,7 @@ def copy_reference(value: Value, loc: &Value, state: &State) -> Value { let copy_ptr = state.bitcast(pointer(value.tpe.tpe), copy_ptr_i8, loc) var ret = [ kind = ValueKind::UNDEF, tpe = value.tpe ] !Value - ret = state.insert_value(value.tpe, ret, ref_count_ptr, [0], loc) + ret = state.insert_value(value.tpe, ret, ref_count_i8, [0], loc) ret = state.insert_value(value.tpe, ret, copy_ptr, [1], loc) ret = state.insert_value(value.tpe, ret, type_ptr, [2], loc) @@ -7653,9 +7658,10 @@ export def create_function( let parameter_t2 = vector::make(typechecking::NamedParameter) parameter_t2.push([ name = "__context", _tpe = typechecking::pointer(null) ] !typechecking::NamedParameter) - let free_context_tpe = typechecking::make_function_type_n(parser::make_identifier(tpe.name + ".free_context"), - free_context_parameters, vector::make(type &typechecking::Type), state.module, context = state.module + let free_context_tpe = typechecking::make_function_type_n(parser::make_identifier(tpe.name + ".free_context." + state.context_ptr), + free_context_parameters, vector::make(type &typechecking::Type), state.module, context = function.module ) + state.context_ptr += 1 let free_context = predeclare_function(free_context_tpe, state.module) free_context.is_compiled = true @@ -8296,11 +8302,24 @@ def create_destructor(tpe: &typechecking::Type, value: Value, state: &State) { var ref: Value var ref_count: Value var null_br: &Insn, br: &Insn + var br1: &Insn if typechecking::is_ref(tpe.tpe) and tpe.tpe.tpe and not is_interface(tpe.tpe.tpe) { // Decrease ref count ref = state.load(tpe.tpe, value) ref_count = state.extract_value(pointer(ref_meta()), ref, [0]) + let ref_count_value1 = state.ptr_to_int(ref_count) + + let isnull = state.icmp(CompareInt::eq, ref_count_value1, + [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) + + br1 = make_insn(InsnKind::BR) + br1.value.br = [ cond = isnull ] !InsnBr + push_insn(br1, state) + let nonnull = make_label(state) + push_label(nonnull, state) + br1.value.br.if_false = nonnull + let ref_count_ptr = state.gep(pointer(builtins::int64_), ref_meta(), ref_count, [make_int_value(0), make_int_value(0)]) let ref_count_value = state.ptr_to_int(ref_count_ptr) let null_cond = state.icmp(CompareInt::eq, ref_count_value, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) @@ -8393,7 +8412,7 @@ def create_destructor(tpe: &typechecking::Type, value: Value, state: &State) { let ref = state.load(tpe.tpe, value) // Extract type - let ref_count = state.extract_value(pointer(builtins::int64_), ref, [0]) + let ref_count = get_ref_count_ptr(ref, null, state) let ref_count_value = state.ptr_to_int(ref_count) let cond = state.icmp(CompareInt::eq, ref_count_value, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) null_br2 = make_insn(InsnKind::BR) @@ -8484,6 +8503,18 @@ def create_destructor(tpe: &typechecking::Type, value: Value, state: &State) { state.call(fun.type_name, null, [convert_ref_to_ref(builtins::Ref_, ref, null, state)]) } + let ref_count_value1 = state.ptr_to_int(ref_count) + + let isnull = state.icmp(CompareInt::eq, ref_count_value1, + [ kind = ValueKind::INT, tpe = builtins::int64_, i = 0 ] !Value) + + let br2 = make_insn(InsnKind::BR) + br2.value.br = [ cond = isnull ] !InsnBr + push_insn(br2, state) + let nonnull = make_label(state) + push_label(nonnull, state) + br2.value.br.if_false = nonnull + let cnt = get_weak_count_ptr(ref, null, state) var cnt_value = state.load(builtins::int64_, cnt) cnt_value = state.sub(builtins::int64_, cnt_value, [ kind = ValueKind::INT, tpe = builtins::int64_, i = 1 ] !Value) @@ -8510,6 +8541,9 @@ def create_destructor(tpe: &typechecking::Type, value: Value, state: &State) { to_end.value.br_unc.label_ = end_label br.value.br.if_false = end_label null_br.value.br.if_true = end_label + br1.value.br.if_true = end_label + br2.value.br.if_true = end_label + push_label(end_label, state) } } diff --git a/src/repl.pr b/src/repl.pr index d9c9f18..a744754 100644 --- a/src/repl.pr +++ b/src/repl.pr @@ -442,7 +442,7 @@ def execute(source: Str) { stack_frame = eval::make_stack_frame(main_function.block, "main", 1) - let mem = allocate(size_of type *) !** + let mem = allocate(16) !** // FIXME What are we doing here??? @mem = (*_args) !* stack_frame.locals("args.value") = mem diff --git a/src/runtime.pr b/src/runtime.pr index fbddf30..cf15caf 100644 --- a/src/runtime.pr +++ b/src/runtime.pr @@ -89,8 +89,13 @@ export type Field = struct { tpe: *Type } +export type Refcount = struct { + strong_cnt: int64 + weak_cnt: int64 +} + export type Ref = struct { - ref_count: *int64 + ref_count: *Refcount value: * tpe: *Type } From 641377abfe86e9dc73309f57992d9f29ecfd73a9 Mon Sep 17 00:00:00 2001 From: Vic Nightfall Date: Sun, 5 May 2024 19:02:49 +0200 Subject: [PATCH 4/5] Breaking change --- src/eval.pr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/eval.pr b/src/eval.pr index ae8f6f5..fc694b0 100644 --- a/src/eval.pr +++ b/src/eval.pr @@ -131,7 +131,7 @@ export def get(mem: *, tpe: &typechecking::Type) -> compiler::Value { return [ kind = compiler::ValueKind::POINTER, tpe = tpe, i = @(mem !*int64) ] !compiler::Value case typechecking::TypeKind::REFERENCE, typechecking::TypeKind::WEAK_REF let values = allocate_ref(compiler::Value, 3) - values(0) = [ kind = compiler::ValueKind::POINTER, tpe = typechecking::pointer(builtins::size_t_), i = @(mem !*int64) ] !compiler::Value + values(0) = [ kind = compiler::ValueKind::POINTER, tpe = typechecking::pointer(compiler::ref_meta()), i = @(mem !*int64) ] !compiler::Value values(1) = [ kind = compiler::ValueKind::POINTER, tpe = typechecking::pointer(tpe.tpe), i = @((mem ++ (size_of type *)) !*int64) ] !compiler::Value values(2) = [ kind = compiler::ValueKind::POINTER, tpe = typechecking::pointer(builtins::Type_), i = @((mem ++ (size_of type *) * 2) !*int64) ] !compiler::Value return [ kind = compiler::ValueKind::STRUCT, tpe = tpe, values = values ] !compiler::Value @@ -364,7 +364,7 @@ def unwrap_undef(value: compiler::Value) -> compiler::Value { value = [ kind = compiler::ValueKind::ARRAY, tpe = value.tpe, values = values ] !compiler::Value } else if value.tpe.kind == typechecking::TypeKind::REFERENCE or value.tpe.kind == typechecking::TypeKind::WEAK_REF { let values = allocate_ref(compiler::Value, 3) - values(0) = [ kind = compiler::ValueKind::UNDEF, tpe = typechecking::pointer(builtins::size_t_) ] !compiler::Value + values(0) = [ kind = compiler::ValueKind::UNDEF, tpe = typechecking::pointer(compiler::ref_meta()) ] !compiler::Value values(1) = [ kind = compiler::ValueKind::UNDEF, tpe = typechecking::pointer(value.tpe.tpe) ] !compiler::Value values(2) = [ kind = compiler::ValueKind::UNDEF, tpe = typechecking::pointer(builtins::Type_) ] !compiler::Value value = [ kind = compiler::ValueKind::STRUCT, tpe = value.tpe, values = values ] !compiler::Value @@ -469,7 +469,7 @@ def eval_GetElementPtr(insn: &compiler::Insn, state: &State) { } } else if tpe.kind == typechecking::TypeKind::REFERENCE { if index == 0 { - tpe = typechecking::pointer(builtins::size_t_) + tpe = typechecking::pointer(compiler::ref_meta()) } else if index == 1 { addr = addr ++ (size_of type *) tpe = tpe.tpe From cb2ac85b82c633c5a36148c593918f736b8d129a Mon Sep 17 00:00:00 2001 From: Vic Nightfall Date: Sun, 5 May 2024 19:57:27 +0200 Subject: [PATCH 5/5] Update version --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 739174e..4575bfc 100644 --- a/version +++ b/version @@ -1 +1 @@ -VERSION=0.3.11 \ No newline at end of file +VERSION=0.3.12