Skip to content

Panic: ARC borrow certifier: use of unbound refcounted local #9634

@ageron

Description

@ageron

While solving exercise alphametics, roc check passes but I run into a panic at runtime, about an unbound refcounted local.

Here's the code:

Alphametics :: {}.{
	solve : Str -> Try(List((U8, U8)), [InvalidAssignment])
	solve = |problem| {
		{ addends, sum } = parse(problem)?

		# We can represent the equation as a dictionary of the letters mapped to their coefficients
		# when we simplify the equation. For example, we can write AB + A + B == C as 11A + 2B + (-1)C == 0.
		# That then becomes this dictionary: `Dict.from_list([('A', 11), ('B', 2), ('C', -1)])
		equation : Dict(U8, I64)
		equation = {
			addends.fold(
				Dict.empty(),
				|dict, term| { dict -> insert_term(term, 1) },
			) -> insert_term(sum, -1)
		}

		leading_digits : Set(U8)
		leading_digits = 
			addends.map(
				|letters| {
					letters.first() ?? 0
				},
			)
				->Set.from_list()
				.insert(
					sum.first() ?? 0,
				)

		find_match : List((U8, U8)), List(U8), Set(U8) -> Try(List((U8, U8)), [InvalidAssignment])
		find_match = |assignments, remaining_vars, remaining_digits| {
			match remaining_vars {
				[] => {
					total_val : I64
					total_val = 
						assignments.fold(
							0,
							|total, (letter, value)| {
								(equation.get(letter) ?? 0) * value.to_i64() + total
							},
						)

					if total_val != 0 {
						Err(InvalidAssignment)
					} else {
						Ok(assignments)
					}
				}
				[letter, .. as rest] => {
					find_first_ok(
						remaining_digits,
						|digit| {
							if digit == 0 and leading_digits.contains(letter) {
								Err(InvalidAssignment)
							} else {
								# Each digit has to be unique, so once we use a digit we remove it from the pool
								find_match(assignments.append((letter, digit)), rest, remaining_digits.remove(digit))
							}
						}
					)
				}
			}
		}

		digits = Set.from_list([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
		find_match([], equation.keys(), digits)
	}
}

# Apply a function to each element of a list until the function returns an Ok, then return that value
find_first_ok : Set(a), (a -> Try(b, err)) -> Try(b, [InvalidAssignment])
find_first_ok = |set, func| {
	set.to_list().fold_until(
		Err(InvalidAssignment),
		|state, elem| {
			match func(elem) {
				Err(_) => Continue(state)
				Ok(val) => Break(Ok(val))
			}
		},
	)
}

# Update the equation with the values of a term
insert_term : Dict(U8, I64), List(U8), I64 -> Dict(U8, I64)
insert_term = |equation, letters, polarity| {
	letters
		->list_reverse()
		.fold_with_index(
			equation,
			|dict, letter, index| {
				coeff = pow_int(10, index) * polarity
				dict.update(
					letter,
					|val| {
						match val {
							Err(Missing) => Ok(coeff)
							Ok(c) => Ok((c + coeff))
						}
					},
				)
			},
		)
}

parse : Str -> Try({ addends : List(List(U8)), sum : List(U8) }, _)
parse = |problem| {
	{ before, after } = problem->split_first(" == ")?
	addends = 
		before
			.split_on(
				" + ",
			)
			.map(
				|s| {
					s.to_utf8()
				},
			)
	Ok({ addends, sum: after.to_utf8() })
}


# # The following function should soon be available in Roc's builtins
split_first : Str, Str -> Try({ before : Str, after : Str }, [InvalidAssignment])
split_first = |str, sep| {
	match str.split_on(sep) {
		[] => Err(InvalidAssignment)
		[_] => Err(InvalidAssignment)
		[before, .. as rest] => Ok({ before, after: rest -> Str.join_with(sep) })
	}
}

list_reverse : List(a) -> List(a)
list_reverse = |list| {
	match list {
		[] => []
		[first, .. as rest] => list_reverse(rest).append(first)
	}
}

reverse : Str -> Str
reverse = |str| {
	str
		.to_utf8()
		->list_reverse()
		->Str.from_utf8()
		?? ""
}

pow_int : I64, U64 -> I64
pow_int = |number, pow| {
	1.to(pow).fold(
		1,
		|acc, _| {
			acc * number
		},
	)
}

main! = |_args| {
	result = Alphametics.solve("I + BB == ILL")
	dbg result
	Ok({})
}
Details
thread 30915278 panic: ARC borrow certifier: proc=52 stmt=4235: use of unbound refcounted local 550
failure context: proc=52 local=550 layout=50
  args: 531 532
  stmt 4214: jump target=68
  stmt 4217: jump target=68
  stmt 4218: decref value=534 next=4217
  stmt 4223: incref value=536 next=4222
  stmt 4224: assign_ref target=536 op=field next=4223
  stmt 4225: assign_call target=535 next=4224
  stmt 4226: assign_struct target=543 next=4225
  stmt 4227: assign_ref target=547 op=local next=4226
  stmt 4228: assign_ref target=546 op=field next=4227
  stmt 4229: incref value=542 next=4228
  stmt 4230: assign_ref target=542 op=field next=4229
  stmt 4231: assign_struct target=534 next=4230
  stmt 4232: incref value=549 next=4231
  stmt 4233: assign_ref target=549 op=field next=4232
  stmt 4234: incref value=548 next=4233
  stmt 4235: assign_ref target=548 op=local next=4234
  stmt 4236: switch_stmt
  stmt 4237: jump target=69
  stmt 4238: assign_call target=552 next=4237
  stmt 4239: assign_ref target=555 op=field next=4238
  stmt 4240: assign_ref target=554 op=field next=4239
  stmt 4241: jump target=69
  stmt 4242: assign_ref target=552 op=local next=4241
  stmt 4243: assign_tag target=553 next=4242
  stmt 4244: switch_stmt
  stmt 4245: assign_call target=558 next=4244
  stmt 4246: assign_literal target=560 next=4245
  stmt 4247: assign_ref target=559 op=local next=4246
  stmt 4248: join id=69 body=4236 remainder=4247
  stmt 4249: join id=68 body=731 remainder=4248

error return context:
/Users/ageron/dev/software/roc/src/lir/arc_certify.zig:624:9: 0x1073cffcf in fail__anon_118703 (roc)
        return error.Certification;
        ^
/Users/ageron/dev/software/roc/src/lir/arc_certify.zig:734:13: 0x1073c9d7f in requireLive (roc)
            return self.fail("use of unbound refcounted local {d}", .{@intFromEnum(local)});
            ^
/Users/ageron/dev/software/roc/src/lir/arc_certify.zig:1399:37: 0x1073b058f in runSegment (roc)
                                _ = try self.requireLive(&state, source);
                                    ^
/Users/ageron/dev/software/roc/src/lir/arc_certify.zig:1360:39: 0x1073af2fb in certifyProc (roc)
                .segment => |segment| try self.runSegment(&work, segment),
                                      ^
/Users/ageron/dev/software/roc/src/lir/arc_certify.zig:113:9: 0x1073ac1c3 in certifyStore (roc)
        try certifier.certifyProc(@enumFromInt(@as(u32, @intCast(index))), proc, body);
        ^

stack trace:
/Users/ageron/dev/software/roc/src/lir/arc_certify.zig:270:28: 0x1073a441f in certifyStoreOrPanic (roc)
            std.debug.panic("ARC borrow certifier: {s}{s}", .{ diag.message(), context.text() });
                           ^
/Users/ageron/dev/software/roc/src/lir/arc.zig:180:44: 0x1073a3a87 in insert (roc)
        try arc_certify.certifyStoreOrPanic(store.allocator, store, layouts, .{ .sigs = all_sigs }, options.roots);
                                           ^
/Users/ageron/dev/software/roc/src/lir/checked_pipeline.zig:234:19: 0x1073a135f in lowerCheckedModulesToLir (roc)
    try Arc.insert(&lowered.lir_result.store, &lowered.lir_result.layouts, .{
                  ^
/Users/ageron/dev/software/roc/src/cli/main.zig:2642:69: 0x1096eb24b in buildLirImageWithCoordinator (roc)
    const lowered = try lir.CheckedPipeline.lowerCheckedModulesToLir(
                                                                    ^
/Users/ageron/dev/software/roc/src/cli/main.zig:2002:56: 0x1097061c7 in rocRunDefaultApp (roc)
    const shm_result = try buildLirImageWithCoordinator(ctx, app_path, original_source_dir, args.max_threads);
                                                       ^
/Users/ageron/dev/software/roc/src/cli/main.zig:1514:32: 0x1096e21db in rocRun (roc)
        return rocRunDefaultApp(ctx, args, source);
                               ^
/Users/ageron/dev/software/roc/src/cli/main.zig:868:19: 0x1070f46eb in mainArgs (roc)
            rocRun(&ctx, run_args) catch |err| switch (err) {
                  ^
/Users/ageron/dev/software/roc/src/cli/main.zig:751:13: 0x1070f32cb in main (roc)
    mainArgs(gpa, arena, args, init.io) catch |err| {
            ^
/opt/homebrew/Cellar/zig/0.16.0_1/lib/zig/std/start.zig:737:30: 0x1070ef96f in callMain (roc)
    return wrapMain(root.main(.{
                             ^
???:?:?: 0x18fd36b97 in start (/usr/lib/dyld)
zsh: abort      roc test24.roc

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions