v2 / vlib / v / gen / c / match.v
639 lines · 625 sloc · 18.2 KB · 72f6f681059c7e1916c60286f022d663af1d45d9
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module c
5
6import v.ast
7import v.util
8
9fn (g &Gen) match_cond_can_use_directly(cond ast.Expr) bool {
10 return (cond in [ast.Ident, ast.IntegerLiteral, ast.StringLiteral, ast.FloatLiteral]
11 && (cond !is ast.Ident || (cond is ast.Ident && cond.or_expr.kind == .absent)))
12 || (cond is ast.SelectorExpr && cond.or_block.kind == .absent && (cond.expr !is ast.CallExpr
13 || (cond.expr as ast.CallExpr).or_block.kind == .absent))
14}
15
16fn (mut g Gen) need_tmp_var_in_match(node ast.MatchExpr) bool {
17 resolved_return_type := g.infer_match_expr_type(node)
18 if node.is_expr && resolved_return_type != ast.void_type && resolved_return_type != 0 {
19 if g.inside_struct_init {
20 return true
21 }
22 if g.table.sym(resolved_return_type).kind in [.sum_type, .interface, .multi_return]
23 || resolved_return_type.has_option_or_result() {
24 return true
25 }
26 if g.table.final_sym(node.cond_type).kind == .enum && node.branches.len > 5 {
27 return true
28 }
29 if !g.match_cond_can_use_directly(node.cond) {
30 return true
31 }
32 if g.need_tmp_var_in_expr(node.cond) {
33 return true
34 }
35 for branch in node.branches {
36 if branch.stmts.len > 1 {
37 return true
38 }
39 if branch.stmts.len == 1 {
40 if branch.stmts[0] is ast.ExprStmt {
41 stmt := branch.stmts[0] as ast.ExprStmt
42 if stmt.expr is ast.ArrayInit && stmt.expr.is_fixed {
43 return true
44 }
45 if g.need_tmp_var_in_expr(stmt.expr) {
46 return true
47 }
48 } else if branch.stmts[0] is ast.Return {
49 return true
50 } else if branch.stmts[0] is ast.BranchStmt {
51 return true
52 }
53 }
54 }
55 }
56 return false
57}
58
59fn (mut g Gen) match_expr(node ast.MatchExpr) {
60 if node.cond_type == 0 {
61 g.writeln('// match 0')
62 return
63 }
64 resolved_return_type := g.infer_match_expr_type(node)
65 need_tmp_var := g.need_tmp_var_in_match(node)
66 is_expr := (node.is_expr && resolved_return_type != ast.void_type) || g.inside_ternary > 0
67
68 mut cond_var := ''
69 mut tmp_var := ''
70 mut cur_line := ''
71 if is_expr && !need_tmp_var {
72 g.inside_ternary++
73 }
74 if is_expr {
75 if resolved_return_type.has_flag(.option) {
76 old := g.inside_match_option
77 defer(fn) {
78 g.inside_match_option = old
79 }
80 g.inside_match_option = true
81 } else if resolved_return_type.has_flag(.result) {
82 old := g.inside_match_result
83 defer(fn) {
84 g.inside_match_result = old
85 }
86 g.inside_match_result = true
87 }
88 }
89 if g.match_cond_can_use_directly(node.cond) {
90 cond_var = g.expr_string(node.cond)
91 } else {
92 line := if is_expr {
93 g.empty_line = true
94 g.go_before_last_stmt().trim_left('\t')
95 } else {
96 ''
97 }
98 cond_var = g.new_tmp_var()
99 g.write('${g.styp(node.cond_type)} ${cond_var} = ')
100 g.expr(node.cond)
101 g.writeln(';')
102 g.set_current_pos_as_last_stmt_pos()
103 g.write(line)
104 }
105 if need_tmp_var {
106 g.empty_line = true
107 cur_line = g.go_before_last_stmt().trim_left(' \t')
108 tmp_var = g.new_tmp_var()
109 mut func_decl := ''
110 ret_final_sym := g.table.final_sym(resolved_return_type)
111 if !resolved_return_type.has_option_or_result() && ret_final_sym.kind == .function {
112 if ret_final_sym.info is ast.FnType {
113 def := g.fn_var_signature(ast.void_type, ret_final_sym.info.func.return_type,
114 ret_final_sym.info.func.params.map(it.typ), tmp_var)
115 func_decl = '${def} = &${g.styp(resolved_return_type)};'
116 }
117 }
118 if func_decl != '' {
119 g.writeln(func_decl) // func, anon func declaration
120 } else {
121 g.writeln('${g.styp(resolved_return_type)} ${tmp_var} = ${g.type_default(resolved_return_type)};')
122 }
123 g.empty_line = true
124 if g.infix_left_var_name.len > 0 {
125 g.writeln('if (${g.infix_left_var_name}) {')
126 g.indent++
127 }
128 }
129
130 if is_expr && !need_tmp_var {
131 // brackets needed otherwise '?' will apply to everything on the left
132 g.write('(')
133 }
134 if node.is_sum_type {
135 g.match_expr_sumtype(node, is_expr, cond_var, tmp_var, resolved_return_type)
136 } else {
137 cond_fsym := g.table.final_sym(node.cond_type)
138 enum_is_multi_allowed := cond_fsym.info is ast.Enum && cond_fsym.info.is_multi_allowed
139 mut can_be_a_switch := true
140 all_branches: for branch in node.branches {
141 for expr in branch.exprs {
142 match expr {
143 ast.BoolLiteral, ast.IntegerLiteral, ast.CharLiteral, ast.EnumVal {
144 continue
145 }
146 else {
147 // ast.StringLiteral, ast.Ident, ast.RangeExpr can not used in switch cases in C
148 // eprintln('>>>> node.cond: ${node.cond} | branch expr: ${typeof(expr)} | expr: ${expr}')
149 can_be_a_switch = false
150 break all_branches
151 }
152 }
153 }
154 }
155 // eprintln('> can_be_a_switch: ${can_be_a_switch}')
156 if can_be_a_switch && !is_expr && g.loop_depth == 0 && g.fn_decl != unsafe { nil }
157 && cond_fsym.is_int() && !enum_is_multi_allowed {
158 g.match_expr_switch(node, is_expr, cond_var, tmp_var, cond_fsym, resolved_return_type)
159 } else if cond_fsym.kind == .enum && g.loop_depth == 0 && node.branches.len > 5
160 && g.fn_decl != unsafe { nil } && !enum_is_multi_allowed {
161 // do not optimize while in top-level
162 g.match_expr_switch(node, is_expr, cond_var, tmp_var, cond_fsym, resolved_return_type)
163 } else {
164 g.match_expr_classic(node, is_expr, cond_var, tmp_var, resolved_return_type)
165 }
166 }
167 g.set_current_pos_as_last_stmt_pos()
168 if need_tmp_var {
169 if g.infix_left_var_name.len > 0 {
170 g.writeln('')
171 g.indent--
172 g.writeln('}')
173 g.set_current_pos_as_last_stmt_pos()
174 }
175 }
176 g.write(cur_line)
177 if need_tmp_var {
178 g.write(tmp_var)
179 }
180 if is_expr && !need_tmp_var {
181 g.write(')')
182 g.decrement_inside_ternary()
183 }
184}
185
186fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string, resolved_return_type ast.Type) {
187 dot_or_ptr := g.dot_or_ptr(node.cond_type)
188 use_ternary := is_expr && tmp_var == ''
189 cond_sym := g.table.final_sym(node.cond_type)
190 for j, branch in node.branches {
191 mut sumtype_index := 0
192 // iterates through all types in sumtype branches
193 for {
194 g.aggregate_type_idx = sumtype_index
195 is_last := j == node.branches.len - 1 && sumtype_index == branch.exprs.len - 1
196 mut has_branch_type := false
197 mut had_old_branch_type := false
198 mut old_branch_type := ast.Type(0)
199 mut branch_type := ast.Type(0)
200 if cond_sym.kind == .sum_type && sumtype_index < branch.exprs.len {
201 branch_expr := unsafe { &branch.exprs[sumtype_index] }
202 if branch_expr is ast.TypeNode {
203 branch_type = branch_expr.typ
204 }
205 }
206 if branch.is_else || (use_ternary && is_last) {
207 if use_ternary {
208 // TODO: too many branches. maybe separate ?: matches
209 g.write(' : ')
210 } else {
211 g.writeln('')
212 g.write_v_source_line_info(branch)
213 g.writeln('else {')
214 }
215 } else {
216 if j > 0 || sumtype_index > 0 {
217 if use_ternary {
218 g.write(' : ')
219 } else {
220 g.write_v_source_line_info(branch)
221 g.write('else ')
222 }
223 }
224 if use_ternary {
225 g.write('(')
226 } else {
227 if j == 0 && sumtype_index == 0 {
228 g.empty_line = true
229 }
230 g.write_v_source_line_info(branch)
231 g.write('if (')
232 }
233 need_deref := node.cond_type.nr_muls() > 1
234 if need_deref {
235 g.write2('(', '*'.repeat(node.cond_type.nr_muls() - 1))
236 }
237 g.write(cond_var)
238 if need_deref {
239 g.write(')')
240 }
241 cur_expr := unsafe { &branch.exprs[sumtype_index] }
242 if cond_sym.kind == .sum_type {
243 g.write('${dot_or_ptr}_typ == ')
244 if cur_expr is ast.None {
245 g.write('${ast.none_type.idx()} /* none */')
246 } else {
247 g.expr(cur_expr)
248 }
249 } else if cond_sym.kind == .interface {
250 if cur_expr is ast.TypeNode {
251 branch_sym := g.table.sym(g.unwrap_generic(cur_expr.typ))
252 g.write('${dot_or_ptr}_typ == _${cond_sym.cname}_${branch_sym.cname}_index')
253 } else if cur_expr is ast.None && cond_sym.idx == ast.error_type_idx {
254 g.write('${dot_or_ptr}_typ == _IError_None___index')
255 }
256 }
257 if use_ternary {
258 g.write(')? ')
259 } else {
260 g.writeln(') {')
261 }
262 }
263 if branch_type != 0 {
264 has_branch_type = true
265 if old_type := g.type_resolver.type_map[cond_var] {
266 had_old_branch_type = true
267 old_branch_type = old_type
268 }
269 g.type_resolver.update_ct_type(cond_var, branch_type)
270 g.clear_type_resolution_caches()
271 }
272 if is_expr && tmp_var.len > 0
273 && g.table.sym(resolved_return_type).kind in [.sum_type, .interface] {
274 g.expected_cast_type = resolved_return_type
275 }
276 inside_interface_deref_old := g.inside_interface_deref
277 if is_expr && branch.stmts.len > 0 {
278 mut stmt := branch.stmts.last()
279 if mut stmt is ast.ExprStmt {
280 if mut stmt.expr is ast.Ident && stmt.expr.obj is ast.Var
281 && g.table.is_interface_var(stmt.expr.obj) {
282 g.inside_interface_deref = true
283 } else if mut stmt.expr is ast.PrefixExpr && stmt.expr.right is ast.Ident {
284 ident := stmt.expr.right as ast.Ident
285 if ident.obj is ast.Var && g.table.is_interface_var(ident.obj) {
286 g.inside_interface_deref = true
287 }
288 }
289 }
290 }
291 g.stmts_with_tmp_var(branch.stmts, tmp_var)
292 g.write_defer_stmts(branch.scope, false, node.pos)
293 g.inside_interface_deref = inside_interface_deref_old
294 g.expected_cast_type = 0
295 if has_branch_type {
296 if had_old_branch_type {
297 g.type_resolver.update_ct_type(cond_var, old_branch_type)
298 } else {
299 g.type_resolver.type_map.delete(cond_var)
300 }
301 g.clear_type_resolution_caches()
302 }
303 if g.inside_ternary == 0 {
304 g.writeln('}')
305 g.set_current_pos_as_last_stmt_pos()
306 }
307 sumtype_index++
308 if branch.exprs.len == 0 || sumtype_index == branch.exprs.len {
309 break
310 }
311 }
312 // reset global field for next use
313 g.aggregate_type_idx = 0
314 }
315}
316
317fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string, cond_fsym ast.TypeSymbol, resolved_return_type ast.Type) {
318 node_cond_type_unsigned := node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type]
319
320 covered_enum_cap := if cond_fsym.info is ast.Enum { cond_fsym.info.vals.len } else { 0 }
321 mut covered_enum := []string{cap: covered_enum_cap} // collects missing enum variant branches to avoid cstrict errors
322
323 // A branch that has a RangeExpr condition, cannot be emitted as a switch case branch;
324 // we will store each of them in range_branches, and then will handle them all in the default branch with if conditions:
325 mut range_branches := []ast.MatchBranch{cap: node.branches.len}
326 mut default_generated := false
327
328 g.empty_line = true
329 g.writeln('switch (${cond_var}) {')
330 g.indent++
331 for branch in node.branches {
332 if branch.is_else {
333 if cond_fsym.info is ast.Enum {
334 cname := '${cond_fsym.cname}__'
335 for val in cond_fsym.info.vals {
336 if val !in covered_enum {
337 g.writeln('case ${cname}${val}:')
338 }
339 }
340 }
341 g.writeln('default: {')
342 default_generated = true
343 g.indent++
344 if range_branches.len > 0 {
345 for range_branch in range_branches {
346 g.write('if (')
347 for i, expr in range_branch.exprs {
348 if i > 0 {
349 g.write(' || ')
350 }
351 if expr is ast.RangeExpr {
352 g.write('(')
353 if g.should_check_low_bound_in_range_expr(expr, node_cond_type_unsigned) {
354 g.write('${cond_var} >= ')
355 g.expr(expr.low)
356 g.write(' && ')
357 }
358 g.write('${cond_var} <= ')
359 g.expr(expr.high)
360 g.write(')')
361 } else {
362 g.write('${cond_var} == (')
363 g.expr(expr)
364 g.write(')')
365 }
366 }
367 g.writeln(') {')
368 ends_with_return := g.stmts_with_tmp_var(range_branch.stmts, tmp_var)
369 if !ends_with_return {
370 g.writeln('\tbreak;')
371 }
372 g.writeln('}')
373 }
374 }
375 } else {
376 if branch.exprs.any(it is ast.RangeExpr) {
377 range_branches << branch
378 continue
379 }
380 for expr in branch.exprs {
381 if expr is ast.EnumVal {
382 covered_enum << expr.val
383 }
384 g.write('case ')
385 g.expr(expr)
386 g.write(': ')
387 }
388 }
389 g.writeln('{')
390 if is_expr && tmp_var.len > 0
391 && g.table.sym(resolved_return_type).kind in [.sum_type, .interface] {
392 g.expected_cast_type = resolved_return_type
393 }
394 ends_with_return := g.stmts_with_tmp_var(branch.stmts, tmp_var)
395 g.expected_cast_type = 0
396 g.write_defer_stmts(branch.scope, false, node.pos)
397 if !ends_with_return {
398 g.writeln('\tbreak;')
399 }
400 g.writeln('}')
401 }
402 if range_branches.len > 0 && !default_generated {
403 g.writeln('default: {')
404 g.indent++
405 default_generated = true
406 for range_branch in range_branches {
407 g.write('if (')
408 for i, expr in range_branch.exprs {
409 if i > 0 {
410 g.write(' || ')
411 }
412 if expr is ast.RangeExpr {
413 g.write('(')
414 if g.should_check_low_bound_in_range_expr(expr, node_cond_type_unsigned) {
415 g.write('${cond_var} >= ')
416 g.expr(expr.low)
417 g.write(' && ')
418 }
419 g.write('${cond_var} <= ')
420 g.expr(expr.high)
421 g.write(')')
422 } else {
423 g.write('${cond_var} == (')
424 g.expr(expr)
425 g.write(')')
426 }
427 }
428 g.writeln(') {')
429 ends_with_return := g.stmts_with_tmp_var(range_branch.stmts, tmp_var)
430 if !ends_with_return {
431 g.writeln('\tbreak;')
432 }
433 g.write_defer_stmts(range_branch.scope, false, node.pos)
434 g.writeln('}')
435 }
436 }
437 if default_generated {
438 g.indent--
439 g.writeln('}')
440 }
441 g.indent--
442 g.writeln('}')
443}
444
445fn (mut g Gen) should_check_low_bound_in_range_expr(expr ast.RangeExpr, node_cond_type_unsigned bool) bool {
446 // if the type is unsigned, and the low bound of the range expression is 0,
447 // checking it at runtime is not needed:
448 mut should_check_low_bound := true
449 if node_cond_type_unsigned {
450 if expr.low is ast.IntegerLiteral {
451 if expr.low.val == '0' {
452 should_check_low_bound = false
453 }
454 } else if expr.low is ast.Ident {
455 mut elow := unsafe { expr.low }
456 if mut obj := g.table.global_scope.find_const(elow.full_name()) {
457 if mut obj.expr is ast.IntegerLiteral {
458 if obj.expr.val == '0' {
459 should_check_low_bound = false
460 }
461 }
462 }
463 }
464 }
465 return should_check_low_bound
466}
467
468fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string, resolved_return_type ast.Type) {
469 node_cond_type_unsigned := node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type]
470 type_sym := g.table.final_sym(node.cond_type)
471 use_ternary := is_expr && tmp_var == ''
472 mut reset_if := node.branches.any(it.exprs.any(g.match_must_reset_if(it)))
473 mut has_goto := false
474 for j, branch in node.branches {
475 is_last := j == node.branches.len - 1
476 if reset_if {
477 g.writeln('')
478 g.set_current_pos_as_last_stmt_pos()
479 }
480 if branch.is_else || (use_ternary && is_last) {
481 if node.branches.len > 1 {
482 if use_ternary {
483 // TODO: too many branches. maybe separate ?: matches
484 g.write(' : ')
485 } else {
486 g.writeln('')
487 g.write_v_source_line_info(branch)
488 g.writeln('else {')
489 }
490 }
491 } else {
492 if j > 0 {
493 if use_ternary {
494 g.write(' : ')
495 } else {
496 g.writeln('')
497 g.write_v_source_line_info(branch)
498 if !reset_if {
499 g.write('else ')
500 }
501 }
502 }
503 if use_ternary {
504 g.write('(')
505 } else {
506 if j == 0 {
507 g.writeln('')
508 }
509 g.write_v_source_line_info(branch)
510 g.write('if (')
511 }
512 for i, expr in branch.exprs {
513 if i > 0 {
514 g.write(' || ')
515 }
516 if expr is ast.None {
517 old_left_is_opt := g.left_is_opt
518 g.left_is_opt = true
519 g.expr(node.cond)
520 g.left_is_opt = old_left_is_opt
521 g.write('.state == 2')
522 continue
523 }
524 match type_sym.kind {
525 .array {
526 ptr_typ := g.equality_fn(node.cond_type)
527 g.write('${ptr_typ}_arr_eq(${cond_var}, ')
528 g.expr(expr)
529 g.write(')')
530 }
531 .array_fixed {
532 ptr_typ := g.equality_fn(node.cond_type)
533 g.write('${ptr_typ}_arr_eq(${cond_var}, ')
534 if expr is ast.ArrayInit {
535 g.write('(${g.styp(node.cond_type)})')
536 }
537 g.expr(expr)
538 g.write(')')
539 }
540 .map {
541 ptr_typ := g.equality_fn(node.cond_type)
542 g.write('${ptr_typ}_map_eq(${cond_var}, ')
543 g.expr(expr)
544 g.write(')')
545 }
546 .string {
547 if expr is ast.StringLiteral {
548 slit := cescape_nonascii(util.smart_quote(expr.val, expr.is_raw))
549 if node.cond_type.is_ptr() {
550 g.write('_SLIT_EQ(${cond_var}->str, ${cond_var}->len, "${slit}")')
551 } else {
552 g.write('_SLIT_EQ(${cond_var}.str, ${cond_var}.len, "${slit}")')
553 }
554 } else {
555 ptr_str := if node.cond_type.is_ptr() { '*' } else { '' }
556 g.write('builtin__fast_string_eq(${ptr_str}${cond_var}, ')
557 g.expr(expr)
558 g.write(')')
559 }
560 }
561 .struct {
562 derefs_expr := '*'.repeat(g.get_expr_type(expr).nr_muls())
563 derefs_ctype := '*'.repeat(node.cond_type.nr_muls())
564 ptr_typ := g.equality_fn(node.cond_type)
565 g.write('${ptr_typ}_struct_eq(${derefs_ctype}${cond_var}, ${derefs_expr}')
566 g.expr(expr)
567 g.write(')')
568 }
569 else {
570 if expr is ast.RangeExpr {
571 g.write('(')
572 if g.should_check_low_bound_in_range_expr(expr, node_cond_type_unsigned) {
573 g.write('${cond_var} >= ')
574 g.expr(expr.low)
575 g.write(' && ')
576 }
577 g.write('${cond_var} <= ')
578 g.expr(expr.high)
579 g.write(')')
580 } else if expr is ast.None {
581 old_left_is_opt := g.left_is_opt
582 g.left_is_opt = true
583 g.expr(node.cond)
584 g.left_is_opt = old_left_is_opt
585 g.write('.state == 2')
586 } else {
587 g.write('${cond_var} == (')
588 g.expr(expr)
589 g.write(')')
590 }
591 }
592 }
593 }
594 if use_ternary {
595 g.write(')? ')
596 } else {
597 g.writeln(') {')
598 }
599 }
600 if is_expr && tmp_var.len > 0
601 && g.table.sym(resolved_return_type).kind in [.sum_type, .interface] {
602 g.expected_cast_type = resolved_return_type
603 }
604 g.stmts_with_tmp_var(branch.stmts, tmp_var)
605 g.write_defer_stmts(branch.scope, false, node.pos)
606 g.expected_cast_type = 0
607 if g.inside_ternary == 0 && node.branches.len >= 1 {
608 if reset_if {
609 has_goto = true
610 g.writeln2('\tgoto end_block_${node.pos.line_nr};', '}')
611 g.set_current_pos_as_last_stmt_pos()
612 } else {
613 g.write('}')
614 }
615 }
616 }
617 if has_goto {
618 g.writeln('end_block_${node.pos.line_nr}: {}')
619 g.set_current_pos_as_last_stmt_pos()
620 }
621}
622
623// match_must_reset_if checks if codegen must break the if-elseif sequence in another if expr
624fn (mut g Gen) match_must_reset_if(node ast.Expr) bool {
625 return match node {
626 ast.CallExpr {
627 node.or_block.kind != .absent
628 }
629 ast.CastExpr {
630 node.typ.has_flag(.option)
631 }
632 ast.InfixExpr {
633 g.match_must_reset_if(node.left) || g.match_must_reset_if(node.right)
634 }
635 else {
636 false
637 }
638 }
639}
640