Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/macos_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ jobs:
run: v run ci/macos_ci.vsh self_tests
- name: Build examples
run: v run ci/macos_ci.vsh build_examples
- name: Build hello_world with -autofree
run: v run ci/macos_ci.vsh build_hello_world_autofree
- name: Build tetris with -autofree
run: v run ci/macos_ci.vsh build_tetris_autofree
- name: Build blog tutorial with -autofree
Expand Down
6 changes: 6 additions & 0 deletions ci/macos_ci.vsh
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ fn build_examples_v_compiled_with_tcc() {
}
}

fn build_hello_world_autofree() {
exec('v -autofree -o hello_world examples/hello_world.v')
exec('./hello_world')
}

fn build_tetris_autofree() {
exec('v -autofree -o tetris examples/tetris/tetris.v')
}
Expand Down Expand Up @@ -151,6 +156,7 @@ const all_tasks = {
'test_pure_v_math_module': Task{test_pure_v_math_module, 'Test pure V math module'}
'self_tests': Task{self_tests, 'Self tests'}
'build_examples': Task{build_examples, 'Build examples'}
'build_hello_world_autofree': Task{build_hello_world_autofree, 'Build hello_world with -autofree'}
'build_tetris_autofree': Task{build_tetris_autofree, 'Build tetris with -autofree'}
'build_blog_autofree': Task{build_blog_autofree, 'Build blog tutorial with -autofree'}
'build_examples_prod': Task{build_examples_prod, 'Build examples with -prod'}
Expand Down
18 changes: 16 additions & 2 deletions vlib/v3/gen/c/interface.v
Original file line number Diff line number Diff line change
Expand Up @@ -1782,7 +1782,7 @@ fn (mut g FlatGen) gen_interface_dispatch_with_fallback(iface_name string, cn st
recv_is_ptr := concrete_params.len > 0 && concrete_params[0] is types.Pointer
recv := g.interface_dispatch_receiver_expr(concrete, concrete_params, recv_is_ptr)
g.write('\t\tcase ${id}: ')
mut call := '${g.cname(method_key)}(${recv}'
mut call := '${g.interface_method_call_cname(method_key)}(${recv}'
for ai, an in arg_names {
arg_idx := ai + 1
concrete_param := if arg_idx < concrete_params.len {
Expand Down Expand Up @@ -2289,6 +2289,20 @@ fn (g &FlatGen) interface_unaliased_type(typ types.Type) types.Type {
return clean
}

// interface_method_call_cname resolves a method to the symbol its definition is emitted
// under: autofree renames a `main` module method to `main__Recv_method`.
fn (g &FlatGen) interface_method_call_cname(method_key string) string {
plain := g.cname(method_key)
if !g.tc.autofree_mode {
return plain
}
candidate := g.qualified_fn_name_in_module_c('main', method_key.trim_string_left('main.'))
if candidate != plain && g.c_fn_symbol_exists(candidate) {
return candidate
}
return plain
}

fn (mut g FlatGen) interface_custom_str_expr(type_name string, typ types.Type, expr string) ?string {
method_key := g.tc.concrete_method_signature_key(type_name, 'str') or { return none }
if typ is types.Alias {
Expand All @@ -2308,7 +2322,7 @@ fn (mut g FlatGen) interface_custom_str_expr(type_name string, typ types.Type, e
} else {
if typ is types.Pointer { '*(${expr})' } else { expr }
}
return '${g.cname(method_key)}(${arg})'
return '${g.interface_method_call_cname(method_key)}(${arg})'
}

fn (mut g FlatGen) interface_pointer_str_expr(base_type types.Type, expr string, prefix_pointer bool, mut stack []string) ?string {
Expand Down
55 changes: 50 additions & 5 deletions vlib/v3/gen/c/stmt.v
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,18 @@ fn (mut g FlatGen) gen_current_return_ownership_drops() {
g.gen_ownership_drops(g.cur_return_drops)
}

// take_return_ownership_drops_for_node answers from the node-keyed list, but still takes
// the positional one to keep its counter in step. A lowering that adds or removes a
// return shifts every later positional index; the node id does not move.
fn (mut g FlatGen) take_return_ownership_drops_for_node(id flat.NodeId) []types.OwnershipDropEntry {
fn_name := qualify_name_in_module(g.tc.cur_module, g.cur_fn_name)
positional := g.take_return_ownership_drops()
if g.tc.ownership_has_return_node(fn_name, id) {
return g.take_return_node_ownership_drops(id)
}
return positional
}

fn (mut g FlatGen) take_return_ownership_drops() []types.OwnershipDropEntry {
fn_name := qualify_name_in_module(g.tc.cur_module, g.cur_fn_name)
entries := g.tc.ownership_drop_entries_at_return(fn_name, g.ownership_return_index)
Expand Down Expand Up @@ -732,18 +744,18 @@ fn (mut g FlatGen) take_propagation_ownership_drops() []types.OwnershipDropEntry
return entries
}

fn (mut g FlatGen) take_return_stmt_ownership_drops(node flat.Node) []types.OwnershipDropEntry {
fn (mut g FlatGen) take_return_stmt_ownership_drops(node flat.Node, id flat.NodeId) []types.OwnershipDropEntry {
mut entries := []types.OwnershipDropEntry{}
if source_id := transformed_return_source_id(node.value) {
entries = g.take_transformed_return_ownership_drops(source_id)
} else if node.typ.len == 0 {
entries = g.take_return_ownership_drops()
entries = g.take_return_ownership_drops_for_node(id)
} else if node.typ[0] !in [`!`, `?`] {
entries = []types.OwnershipDropEntry{}
} else if return_node_is_direct_optional_forward(node.value)
|| node.value == optional_success_return_value
|| g.return_stmt_is_explicit_optional_failure(node) {
entries = g.take_return_ownership_drops()
entries = g.take_return_ownership_drops_for_node(id)
} else {
entries = g.take_propagation_ownership_drops()
}
Expand All @@ -758,6 +770,14 @@ fn (mut g FlatGen) take_return_stmt_ownership_drops(node flat.Node) []types.Owne
return combined
}

fn (g &FlatGen) call_is_optional_failure_constructor(node flat.Node) bool {
if node.kind != .call || node.children_count == 0 {
return false
}
fn_n := g.a.child_node(&node, 0)
return fn_n.value == 'error' || fn_n.value == 'error_with_code'
}

fn (g &FlatGen) return_stmt_is_explicit_optional_failure(node flat.Node) bool {
if node.children_count != 1 {
return false
Expand Down Expand Up @@ -891,13 +911,33 @@ fn (g &FlatGen) ownership_recursive_drop_helper_types() map[string]string {
return representatives
}

// ownership_live_drop_value_type_names drops the destructors only dead functions needed.
// The checker records cleanup sites for every function it saw, markused-removed ones too.
fn (g &FlatGen) ownership_live_drop_value_type_names() []string {
filter := g.has_used_fn_filter()
mut names := map[string]bool{}
for fn_name, type_names in g.tc.ownership_drop_value_type_names_by_fn() {
// A closure is emitted with its enclosing function, which is the name markused knows.
owner := fn_name.all_before('__fn_literal_').all_before('__lambda_')
if filter && owner.len > 0 && !g.used_fn_contains_in_module(owner, '') {
continue
}
for type_name in type_names {
names[type_name] = true
}
}
mut result := names.keys()
result.sort()
return result
}

fn (mut g FlatGen) precompute_ownership_recursive_drop_helpers() {
g.recursive_drop_helpers.clear()
$if !ownership ? {
return
}
mut drop_struct_names := map[string]bool{}
for type_name in g.tc.ownership_drop_value_type_names() {
for type_name in g.ownership_live_drop_value_type_names() {
mut seen := map[string]bool{}
g.ownership_collect_drop_struct_names(g.tc.parse_type(type_name), 0, mut drop_struct_names, mut
seen)
Expand Down Expand Up @@ -2501,7 +2541,7 @@ fn (mut g FlatGen) gen_node(id flat.NodeId) {
old_return_node_id := g.cur_return_node_id
old_return_drops := g.cur_return_drops.clone()
g.cur_return_node_id = int(id)
g.cur_return_drops = g.take_return_stmt_ownership_drops(node)
g.cur_return_drops = g.take_return_stmt_ownership_drops(node, id)
defer {
g.cur_return_node_id = old_return_node_id
g.cur_return_drops = old_return_drops
Expand Down Expand Up @@ -4509,6 +4549,11 @@ fn (mut g FlatGen) gen_autofree_discarded_owned_call(id flat.NodeId, node flat.N
if typ is types.Void || typ is types.Unknown || !g.tc.ownership_type_requires_destruction(typ) {
return false
}
if g.call_is_optional_failure_constructor(node) && g.cur_fn_ret_is_optional {
// gen_expr wraps a bare `error(...)` in the function's `Optional_T`, so a temp
// declared as the call's own type would be initialised from the wrapper.
return false
}
tmp := '__discarded_owned_${g.tmp_count}'
g.tmp_count++
g.write('${g.value_c_type(typ)} ${tmp} = ')
Expand Down
8 changes: 8 additions & 0 deletions vlib/v3/markused/markused.v
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,14 @@ fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files
for seed in ['builtin.none__', 'builtin.error_sentinel'] {
enqueue(seed, mut used, mut queue)
}
// The `_result` destructor names every IError implementer's destructor, with no
// source-AST call site to follow. The branch below emits it too, so root them here.
ierror_destructor := if tc.autofree_mode { 'free' } else { 'drop' }
for impl in tc.ierror_impl_names() {
for alias in interface_implementer_method_aliases(impl, ierror_destructor, tc) {
enqueue(alias, mut used, mut queue)
}
}
}
enqueue_main_module_roots(fn_decls, mut used, mut queue)
if use_prepared {
Expand Down
34 changes: 34 additions & 0 deletions vlib/v3/tests/ownership/ownership_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -3562,3 +3562,37 @@ fn main() {
')
assert ok.exit_code == 0, ok.output
}

fn test_autofree_literal_only_program_keeps_ierror_destructors() {
v3_bin := ownership_build_v3()
ok_multi := run_autofree_check(v3_bin, 'literal_only_mixed_output', "
fn main() {
print('a')
eprintln('b')
println('c')
}
")
assert ok_multi.exit_code == 0, ok_multi.output
}

const shape_coverage_programs = {
'literal_only': "fn main() {\n\tprintln('hello')\n}\n"
'loop_accumulate': 'fn main() {\n\tmut n := 0\n\tfor i in 0 .. 4 {\n\t\tn += i\n\t}\n\tprintln(n)\n}\n'
'string_interp': "fn main() {\n\tname := 'world'\n\tprintln('hello \${name} \${name.len}')\n}\n"
'array_and_map': "fn main() {\n\tmut rows := []string{}\n\trows << 'a'.clone()\n\tmut seen := map[string]int{}\n\tseen['a'] = rows.len\n\tprintln(seen['a'])\n}\n"
'error_propagation': "fn parse(s string) !int {\n\tif s == '' {\n\t\treturn error('empty')\n\t}\n\treturn s.len\n}\n\nfn run() ! {\n\tn := parse('abc')!\n\tprintln(n)\n}\n\nfn main() {\n\trun() or { println(err) }\n}\n"
'owned_struct': "struct Row {\nmut:\n\tname string\n\tlabel ?string\n}\n\nfn build() Row {\n\treturn Row{\n\t\tname: 'x'.clone()\n\t}\n}\n\nfn main() {\n\tr := build()\n\tprintln(r.name)\n}\n"
'generic_fn': "fn first[T](items []T, fallback T) T {\n\tif items.len == 0 {\n\t\treturn fallback\n\t}\n\treturn items[0]\n}\n\nfn main() {\n\tprintln(first([]string{}, 'none'.clone()))\n}\n"
'interface_dispatch': 'interface Shape {\n\tarea() int\n}\n\nstruct Box {\n\tside int\n}\n\nfn (b Box) area() int {\n\treturn b.side * b.side\n}\n\nfn main() {\n\ts := Shape(Box{side: 3})\n\tprintln(s.area())\n}\n'
'match_option_arms': "struct DictHeader {\nmut:\n\tn int\n\tis_sorted ?bool\n}\n\nstruct DataHeader {\nmut:\n\tn int\n\tencoding string\n}\n\nstruct DataHeaderV2 {\nmut:\n\tn int\n\tstatistics ?string\n}\n\nenum PageType {\n\tindex_page\n\tdictionary_page\n\tdata_page\n\tdata_page_v2\n}\n\nstruct PageHeader {\nmut:\n\ttyp PageType\n\tdict ?DictHeader\n\tdata ?DataHeader\n\tv2 ?DataHeaderV2\n}\n\nfn consume(n int) !int {\n\tif n < 0 {\n\t\treturn error('negative')\n\t}\n\treturn n\n}\n\nfn take(header &PageHeader) ! {\n\tmatch header.typ {\n\t\t.index_page {}\n\t\t.dictionary_page {\n\t\t\thead := header.dict or { return error('no dictionary header') }\n\t\t\tsorted := head.is_sorted or { false }\n\t\t\tif sorted {\n\t\t\t\treturn error('sorted')\n\t\t\t}\n\t\t\tconsume(head.n)!\n\t\t}\n\t\t.data_page {\n\t\t\thead := header.data or { return error('no data header') }\n\t\t\tif head.encoding == '' {\n\t\t\t\treturn error('no encoding')\n\t\t\t}\n\t\t\tconsume(head.n)!\n\t\t}\n\t\t.data_page_v2 {\n\t\t\thead := header.v2 or { return error('no v2 header') }\n\t\t\tstats := head.statistics or { '' }\n\t\t\t_ = stats\n\t\t\tconsume(head.n)!\n\t\t}\n\t}\n}\n\nfn main() {\n\th := PageHeader{\n\t\ttyp: .data_page\n\t\tdata: DataHeader{\n\t\t\tn: 1\n\t\t\tencoding: 'plain'\n\t\t}\n\t}\n\ttake(&h) or { println(err) }\n\tprintln('ok')\n}\n"
}

fn test_ownership_and_autofree_build_every_program_shape() {
v3_bin := ownership_build_v3()
for name, code in shape_coverage_programs {
dropped := run_ownership_check(v3_bin, 'shape_drop_${name}', code)
assert dropped.exit_code == 0, '${name} under -ownership:\n${dropped.output}'
freed := run_autofree_check(v3_bin, 'shape_free_${name}', code)
assert freed.exit_code == 0, '${name} under -autofree:\n${freed.output}'
}
}
2 changes: 1 addition & 1 deletion vlib/v3/transform/array.v
Original file line number Diff line number Diff line change
Expand Up @@ -1463,7 +1463,7 @@ fn (mut t Transformer) try_lower_optional_array_append_stmt(_node flat.Node, lhs
not_ok := t.make_prefix(.not, t.make_selector(source, 'ok', 'bool'))
guard_stmts := t.optional_selector_lvalue_guard_stmts(t.a.child(&lhs_node, 1), lhs_node.value,
source)
result << t.make_if(not_ok, t.make_block(guard_stmts), t.make_empty())
result << t.make_if(not_ok, t.make_or_else_block(lhs_node.value, guard_stmts), t.make_empty())

mut rhs := flat.empty_node
if !push_many {
Expand Down
3 changes: 2 additions & 1 deletion vlib/v3/transform/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -2629,7 +2629,8 @@ fn (mut t Transformer) transform_call_arg_for_param(arg_id flat.NodeId, param_ty
err_expr := t.make_selector(opt_expr, 'err', 'IError')
else_stmts := t.lower_or_body_to_stmts_with_err_expr(flat.empty_node, '',
payload_type, '?', err_expr)
t.pending_stmts << t.make_if(not_ok, t.make_block(else_stmts), t.make_empty())
t.pending_stmts << t.make_if(not_ok, t.make_block_skip_scope_drops(else_stmts),
t.make_empty())
} else {
// Comptime option-payload-mut (e.g. `decode(mut result.field?)` in a
// `$for` decoder): the callee fills the payload, so mark it present and
Expand Down
16 changes: 13 additions & 3 deletions vlib/v3/transform/or.v
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,14 @@ fn (mut t Transformer) make_none_return_stmt_with_err_expr(err_expr flat.NodeId)
t.cur_fn_ret_type)
}

fn (mut t Transformer) make_or_else_block(mode string, stmts []flat.NodeId) flat.NodeId {
// `!` and `?` lower to a branch the source never wrote, so it has no checker scope.
if mode == '!' || mode == '?' {
return t.make_block_skip_scope_drops(stmts)
}
return t.make_block(stmts)
}

// lower_or_expr_to_temp converts lower or expr to temp data for transform.
fn (mut t Transformer) lower_or_expr_to_temp(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count < 2 {
Expand Down Expand Up @@ -1492,7 +1500,8 @@ fn (mut t Transformer) lower_or_expr_to_temp(id flat.NodeId, node flat.Node) fla
prelude << t.make_decl_assign_typed(opt_tmp, new_expr, expr_type)
opt_ident := t.make_ident(opt_tmp)
not_ok := t.make_prefix(.not, t.make_selector(opt_ident, 'ok', 'bool'))
else_block := t.make_block(t.lower_or_body_to_stmts(body_id, '', '', node.value, opt_tmp))
else_block := t.make_or_else_block(node.value, t.lower_or_body_to_stmts(body_id, '', '',
node.value, opt_tmp))
if_stmt := t.make_if(not_ok, else_block, t.make_empty())
t.pending_stmts = outer_pending
for stmt in prelude {
Expand Down Expand Up @@ -1522,7 +1531,7 @@ fn (mut t Transformer) lower_or_expr_to_temp(id flat.NodeId, node flat.Node) fla
} else {
t.lower_or_body_to_stmts(body_id, val_tmp, storage_value_type, node.value, opt_tmp)
}
else_block := t.make_block(else_stmts)
else_block := t.make_or_else_block(node.value, else_stmts)
if_stmt := t.make_if(ok_cond, then_block, else_block)
t.pending_stmts = outer_pending
for stmt in prelude {
Expand Down Expand Up @@ -1931,7 +1940,8 @@ fn (mut t Transformer) lower_or_expr_to_stmt(node flat.Node) {

opt_ident := t.make_ident(opt_tmp)
not_ok := t.make_prefix(.not, t.make_selector(opt_ident, 'ok', 'bool'))
else_block := t.make_block(t.lower_or_body_to_stmts(body_id, '', '', node.value, opt_tmp))
else_block := t.make_or_else_block(node.value, t.lower_or_body_to_stmts(body_id, '', '',
node.value, opt_tmp))
if_stmt := t.make_if(not_ok, else_block, t.make_empty())

t.pending_stmts = outer_pending
Expand Down
10 changes: 5 additions & 5 deletions vlib/v3/transform/transform.v
Original file line number Diff line number Diff line change
Expand Up @@ -10008,8 +10008,8 @@ fn (mut t Transformer) transform_pointer_optional_unwrap_lvalue(id flat.NodeId)
err_expr := t.make_selector(wrapper, 'err', 'IError')
not_ok := t.make_prefix(.not, t.make_selector(wrapper, 'ok', 'bool'))
body_id := t.a.child(&node, 1)
else_block := t.make_block(t.lower_or_body_to_stmts_with_err_expr(body_id, '', '', node.value,
err_expr))
else_block := t.make_or_else_block(node.value, t.lower_or_body_to_stmts_with_err_expr(body_id,
'', '', node.value, err_expr))
t.pending_stmts << t.make_if(not_ok, else_block, t.make_empty())
return t.make_selector(wrapper, 'value', value_type)
}
Expand Down Expand Up @@ -11579,7 +11579,7 @@ fn (mut t Transformer) try_lower_optional_selector_lvalue_assign(node flat.Node)
t.drain_pending(mut result)
not_ok := t.make_prefix(.not, t.make_selector(guard_source, 'ok', 'bool'))
guard_stmts := t.optional_selector_lvalue_guard_stmts(guard_body, guard_mode, guard_source)
result << t.make_if(not_ok, t.make_block(guard_stmts), t.make_empty())
result << t.make_if(not_ok, t.make_or_else_block(guard_mode, guard_stmts), t.make_empty())
lhs_type := t.lvalue_type(lhs_id)
sum_target := t.assignment_sum_target(lhs_id, rhs_id, lhs_type)
rhs := if node.op == .assign && sum_target.len > 0 {
Expand Down Expand Up @@ -18963,8 +18963,8 @@ fn (mut t Transformer) transform_amp_optional_unwrap(node flat.Node, child flat.
}
not_ok := t.make_prefix(.not, t.make_selector(source, 'ok', 'bool'))
err_expr := t.make_selector(source, 'err', 'IError')
else_block := t.make_block(t.lower_or_body_to_stmts_with_err_expr(body_id, '', '', child.value,
err_expr))
else_block := t.make_or_else_block(child.value, t.lower_or_body_to_stmts_with_err_expr(body_id,
'', '', child.value, err_expr))
t.pending_stmts << t.make_if(not_ok, else_block, t.make_empty())
value := t.make_selector(source, 'value', value_type)
addr := t.make_prefix(.amp, value)
Expand Down
Loading