Skip to content

Commit

Permalink
transformer: optimise calls to write string with simple interpolation…
Browse files Browse the repository at this point in the history
…s, implement `-d trace_transformer` (#22188)
  • Loading branch information
spytheman authored Oct 9, 2024
1 parent 8fcf3d7 commit 3e6fc36
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 3 deletions.
2 changes: 1 addition & 1 deletion vlib/strings/builder.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ pub fn (b &Builder) byte_at(n int) u8 {
}

// write appends the string `s` to the buffer
@[inline]
@[expand_simple_interpolation; inline]
pub fn (mut b Builder) write_string(s string) {
if s.len == 0 {
return
Expand Down
10 changes: 10 additions & 0 deletions vlib/v/ast/ast.v
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,8 @@ pub mut:
scope &Scope = unsafe { nil }
label_names []string
pos token.Pos // function declaration position
//
is_expand_simple_interpolation bool // true, when @[expand_simple_interpolation] is used on a fn. It should have a single string argument.
}

pub fn (f &FnDecl) new_method_with_receiver_type(new_type_ Type) FnDecl {
Expand Down Expand Up @@ -687,6 +689,10 @@ pub mut:
ctdefine_idx int // the index of the attribute, containing the compile time define [if mytag]
from_embedded_type Type // for interface only, fn from the embedded interface
from_embeded_type Type @[deprecated: 'use from_embedded_type instead'; deprecated_after: '2024-03-31']
//
is_expand_simple_interpolation bool // for tagging b.f(s string), which is then called with `b.f('some $x $y')`,
// when that call, should be expanded to `b.f('some '); b.f(x); b.f(' '); b.f(y);`
// Note: the same type, has to support also a .write_decimal(n i64) method.
}

fn (f &Fn) method_equals(o &Fn) bool {
Expand Down Expand Up @@ -810,6 +816,10 @@ pub mut:
scope &Scope = unsafe { nil }
from_embed_types []Type // holds the type of the embed that the method is called from
comments []Comment
//
is_expand_simple_interpolation bool // true, when the function/method is marked as @[expand_simple_interpolation]
// Calls to it with an interpolation argument like `b.f('x ${y}')`, will be converted to `b.f('x ')` followed by `b.f(y)`.
// The same type, has to support also a .write_decimal(n i64) method.
}

/*
Expand Down
19 changes: 19 additions & 0 deletions vlib/v/checker/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,23 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
}
}
}
if node.is_expand_simple_interpolation {
match true {
!node.is_method {
c.error('@[expand_simple_interpolation] is supported only on methods',
node.pos)
}
node.params.len != 2 {
c.error('methods tagged with @[expand_simple_interpolation], should have exactly 1 argument',
node.pos)
}
!node.params[1].typ.is_string() {
c.error('methods tagged with @[expand_simple_interpolation], should accept a single string',
node.pos)
}
else {}
}
}
}

// check_same_type_ignoring_pointers util function to check if the Types are the same, including all
Expand Down Expand Up @@ -1194,6 +1211,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast.
}
node.is_file_translated = func.is_file_translated
node.is_noreturn = func.is_noreturn
node.is_expand_simple_interpolation = func.is_expand_simple_interpolation
node.is_ctor_new = func.is_ctor_new
if !found_in_args {
if node.scope.known_var(fn_name) {
Expand Down Expand Up @@ -2325,6 +2343,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
c.need_recheck_generic_fns = true
}
node.is_noreturn = method.is_noreturn
node.is_expand_simple_interpolation = method.is_expand_simple_interpolation
node.is_ctor_new = method.is_ctor_new
node.return_type = method.return_type
if method.return_type.has_flag(.generic) {
Expand Down
18 changes: 16 additions & 2 deletions vlib/v/gen/c/text_manipulation.v
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module c

import v.util

@[expand_simple_interpolation]
fn (mut g Gen) write(s string) {
$if trace_gen ? {
if g.file == unsafe { nil } {
Expand All @@ -15,10 +16,23 @@ fn (mut g Gen) write(s string) {
}
if g.indent > 0 && g.empty_line {
g.out.write_string(util.tabs(g.indent))
// g.out_parallel[g.out_idx].write_string(util.tabs(g.indent))
}
g.out.write_string(s)
////g.out_parallel[g.out_idx].write_string(s)
g.empty_line = false
}

fn (mut g Gen) write_decimal(x i64) {
$if trace_gen ? {
if g.file == unsafe { nil } {
eprintln('gen file: <nil> | last_fn_c_name: ${g.last_fn_c_name:-45} | write_decimal: ${x}')
} else {
eprintln('gen file: ${g.file.path:-30} | last_fn_c_name: ${g.last_fn_c_name:-45} | write_decimal: ${x}')
}
}
if g.indent > 0 && g.empty_line {
g.out.write_string(util.tabs(g.indent))
}
g.out.write_decimal(x)
g.empty_line = false
}

Expand Down
10 changes: 10 additions & 0 deletions vlib/v/parser/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
mut is_ctor_new := false
mut is_c2v_variadic := false
mut is_markused := false
mut is_expand_simple_interpolation := false
mut comments := []ast.Comment{}
for fna in p.attrs {
match fna.name {
Expand Down Expand Up @@ -254,6 +255,9 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
p.prev_tok.pos())
}
}
'expand_simple_interpolation' {
is_expand_simple_interpolation = true
}
else {}
}
}
Expand Down Expand Up @@ -550,6 +554,8 @@ run them via `v file.v` instead',
pos: start_pos
name_pos: name_pos
language: language
//
is_expand_simple_interpolation: is_expand_simple_interpolation
})
} else {
name = match language {
Expand Down Expand Up @@ -603,6 +609,8 @@ run them via `v file.v` instead',
pos: start_pos
name_pos: name_pos
language: language
//
is_expand_simple_interpolation: is_expand_simple_interpolation
})
}
/*
Expand Down Expand Up @@ -688,6 +696,8 @@ run them via `v file.v` instead',
label_names: p.label_names
end_comments: p.eat_comments(same_line: true)
comments: comments
//
is_expand_simple_interpolation: is_expand_simple_interpolation
}
if generic_names.len > 0 {
p.table.register_fn_generic_types(fn_decl.fkey())
Expand Down
94 changes: 94 additions & 0 deletions vlib/v/transformer/transformer.v
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub mut:
mut:
is_assert bool
inside_dump bool
//
strings_builder_type ast.Type = ast.no_type
}

fn (mut t Transformer) trace[T](fbase string, x &T) {
Expand All @@ -38,6 +40,7 @@ pub fn new_transformer_with_table(table &ast.Table, pref_ &pref.Preferences) &Tr
}

pub fn (mut t Transformer) transform_files(ast_files []&ast.File) {
t.strings_builder_type = t.table.find_type_idx('strings.Builder')
for i in 0 .. ast_files.len {
mut file := unsafe { ast_files[i] }
t.transform(mut file)
Expand Down Expand Up @@ -178,6 +181,7 @@ pub fn (mut t Transformer) stmt(mut node ast.Stmt) ast.Stmt {
ntype := typeof(*node).replace('v.ast.', '')
eprintln('transformer: ${t.file.path:-50} | pos: ${node.pos.line_str():-39} | node: ${ntype:12} | ${node}')
}
mut onode := unsafe { node }
match mut node {
ast.EmptyStmt {}
ast.NodeError {}
Expand Down Expand Up @@ -244,6 +248,9 @@ pub fn (mut t Transformer) stmt(mut node ast.Stmt) ast.Stmt {
t.expr(mut node.expr)
}
}
if mut node.expr is ast.CallExpr && node.expr.is_expand_simple_interpolation {
t.simplify_nested_interpolation_in_sb(mut onode, mut node.expr, node.typ)
}
}
ast.FnDecl {
if t.pref.trace_calls {
Expand Down Expand Up @@ -1152,3 +1159,90 @@ pub fn (mut t Transformer) fn_decl_trace_calls(mut node ast.FnDecl) {
}
node.stmts.prepend(expr_stmt)
}

pub fn (mut t Transformer) simplify_nested_interpolation_in_sb(mut onode ast.Stmt, mut nexpr ast.CallExpr, ntype ast.Type) bool {
if t.pref.autofree {
return false
}
if nexpr.args[0].expr !is ast.StringInterLiteral {
return false
}
original := nexpr.args[0].expr as ast.StringInterLiteral
// only very simple string interpolations, without any formatting, like the following examples
// can be optimised to a list of simpler string builder calls, instead of using str_intp:
// >> sb.write_string('abc ${num}')
// >> sb.write_string('abc ${num} ${some_string} ${another_string} end')
for idx, w in original.fwidths {
if w != 0 {
return false
}
if original.precisions[idx] != 987698 {
return false
}
if original.need_fmts[idx] {
return false
}
// good ... no complex formatting found; now check the types (only strings and non float numbers are supported)
if original.expr_types[idx] == ast.string_type {
continue
}
if !original.expr_types[idx].is_int() {
return false
}
}

// first, insert all the statements, for writing the static strings, that were parts of the original string interpolation:
mut calls := []ast.Stmt{}
for val in original.vals {
if val == '' {
// there is no point in appending empty strings
// so instead, just emit an empty statement, to be ignored by the backend
calls << ast.EmptyStmt{}
continue
}
mut ncall := ast.ExprStmt{
expr: ast.Expr(ast.CallExpr{
...nexpr
args: [
ast.CallArg{
...nexpr.args[0]
expr: ast.StringLiteral{
val: val
}
},
]
})
typ: ntype
}
calls << ncall
}
// now, insert the statements for writing the variable expressions between the static strings:
for idx, expr in original.exprs {
mut ncall := ast.ExprStmt{
typ: ntype
expr: ast.Expr(ast.CallExpr{
...nexpr
args: [
ast.CallArg{
...nexpr.args[0]
expr: expr
},
]
})
}
etype := original.expr_types[idx]
if etype.is_int() {
if mut ncall.expr is ast.CallExpr {
ncall.expr.name = 'write_decimal'
}
}
calls.insert(1 + 2 * idx, ncall) // the new statements should be between the existing ones for static strings
}
// calls << ast.node
unsafe {
*onode = ast.Stmt(ast.Block{
stmts: calls
})
}
return true
}

0 comments on commit 3e6fc36

Please sign in to comment.