Skip to content

Commit d6eb7a5

Browse files
wasm: implement basic string operations (#26260)
1 parent e3616f2 commit d6eb7a5

7 files changed

Lines changed: 277 additions & 61 deletions

File tree

‎vlib/builtin/wasm/string.v‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,65 @@
11
module builtin
22

3+
// String: This is a minimal implementation of the "string" builtin until the WASM backend
4+
// is able to compile the original builtin implementation
5+
36
pub struct string {
47
pub:
58
str &u8
69
len int
710
}
11+
12+
// Concatenation operator: var += str / str + str
13+
// Note: This will alloc a new string with the content of these two strings
14+
pub fn (s string) + (other string) string {
15+
if s.len == 0 {
16+
return other
17+
}
18+
if other.len == 0 {
19+
return s
20+
}
21+
total_len := s.len + other.len
22+
result_ptr := unsafe { malloc(total_len) }
23+
if result_ptr == 0 {
24+
panic('string.+: malloc failed')
25+
}
26+
unsafe {
27+
vmemcpy(result_ptr, s.str, s.len)
28+
vmemcpy(result_ptr + s.len, other.str, other.len)
29+
}
30+
return string{
31+
str: result_ptr
32+
len: total_len
33+
}
34+
}
35+
36+
// Equality comparison: checks if two strings are identical
37+
pub fn (s string) == (other string) bool {
38+
if s.len != other.len {
39+
return false
40+
}
41+
42+
for i in 0 .. s.len {
43+
if s[i] != other[i] {
44+
return false
45+
}
46+
}
47+
48+
return true
49+
}
50+
51+
// Less-than comparison: lexicographically compares two strings
52+
pub fn (s string) < (other string) bool {
53+
// Taken from the C Backend
54+
for i in 0 .. s.len {
55+
if i >= other.len || s[i] > other[i] {
56+
return false
57+
} else if s[i] < other[i] {
58+
return true
59+
}
60+
}
61+
if s.len < other.len {
62+
return true
63+
}
64+
return false
65+
}

‎vlib/v/gen/wasm/gen.v‎

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,60 @@ pub fn (mut g Gen) handle_ptr_arithmetic(typ ast.Type) {
411411
}
412412
}
413413

414+
fn (mut g Gen) handle_string_operation(op token.Kind) {
415+
left_tmp := g.func.new_local_named(.i32_t, '__tmp<string>.left')
416+
right_tmp := g.func.new_local_named(.i32_t, '__tmp<string>.right')
417+
g.func.local_set(right_tmp)
418+
g.func.local_set(left_tmp)
419+
420+
match op {
421+
.plus {
422+
ret_var := g.new_local('', ast.string_type)
423+
g.ref(ret_var)
424+
g.func.local_get(left_tmp)
425+
g.func.local_get(right_tmp)
426+
g.func.call('string.+')
427+
g.get(ret_var)
428+
}
429+
.eq {
430+
g.func.local_get(left_tmp)
431+
g.func.local_get(right_tmp)
432+
g.func.call('string.==')
433+
}
434+
.ne {
435+
g.func.local_get(left_tmp)
436+
g.func.local_get(right_tmp)
437+
g.func.call('string.==')
438+
g.func.eqz(.i32_t)
439+
}
440+
.lt {
441+
g.func.local_get(left_tmp)
442+
g.func.local_get(right_tmp)
443+
g.func.call('string.<')
444+
}
445+
.gt {
446+
g.func.local_get(right_tmp)
447+
g.func.local_get(left_tmp)
448+
g.func.call('string.<')
449+
}
450+
.le {
451+
g.func.local_get(right_tmp)
452+
g.func.local_get(left_tmp)
453+
g.func.call('string.<')
454+
g.func.eqz(.i32_t)
455+
}
456+
.ge {
457+
g.func.local_get(left_tmp)
458+
g.func.local_get(right_tmp)
459+
g.func.call('string.<')
460+
g.func.eqz(.i32_t)
461+
}
462+
else {
463+
g.w_error('unsupported string operation: `${op}`')
464+
}
465+
}
466+
}
467+
414468
pub fn (mut g Gen) infix_expr(node ast.InfixExpr, expected ast.Type) {
415469
if node.op in [.logical_or, .and] {
416470
temp := g.func.new_local_named(.i32_t, '__tmp<bool>')
@@ -601,20 +655,28 @@ fn (mut g Gen) match_branch_exprs(node ast.MatchExpr, expected ast.Type, unpacke
601655
mut is_last_branch := branch_idx + 1 >= node.branches.len
602656
mut is_last_expr := expr_idx + 1 >= branch.exprs.len
603657

604-
wasm_type := g.as_numtype(g.get_wasm_type(node.cond_type))
605-
606658
expr := branch.exprs[expr_idx]
607659

608660
if expr is ast.RangeExpr {
661+
wasm_type := g.as_numtype(g.get_wasm_type(node.cond_type))
609662
is_signed := node.cond_type.is_signed()
610663

611664
g.expr(node.cond, node.cond_type)
612665
g.expr(expr.high, node.cond_type)
613666
g.func.le(wasm_type, is_signed)
614667
} else {
615-
g.expr(node.cond, node.cond_type)
616-
g.expr(expr, node.cond_type)
617-
g.func.eq(wasm_type)
668+
if g.is_param_type(node.cond_type) {
669+
// Param types -> strings etc
670+
g.expr(node.cond, node.cond_type)
671+
g.expr(expr, node.cond_type)
672+
g.infix_from_typ(node.cond_type, .eq)
673+
} else {
674+
// Numeric types -> direct comparison
675+
wasm_type := g.as_numtype(g.get_wasm_type(node.cond_type))
676+
g.expr(node.cond, node.cond_type)
677+
g.expr(expr, node.cond_type)
678+
g.func.eq(wasm_type)
679+
}
618680
}
619681

620682
blk := g.func.c_if([], unpacked_params)

‎vlib/v/gen/wasm/ops.v‎

Lines changed: 60 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -85,68 +85,78 @@ pub fn (mut g Gen) get_wasm_type(typ_ ast.Type) wasm.ValType {
8585
g.w_error("get_wasm_type: unreachable type '${*g.table.sym(typ)}' ${ts.info}")
8686
}
8787

88+
pub fn (mut g Gen) infix_param_type(typ ast.Type, op token.Kind) {
89+
match typ {
90+
ast.string_type {
91+
g.handle_string_operation(op)
92+
}
93+
else {
94+
eprintln(*g.table.sym(typ))
95+
panic('unimplemented infix operation for type')
96+
}
97+
}
98+
}
99+
88100
pub fn (mut g Gen) infix_from_typ(typ ast.Type, op token.Kind) {
89101
if g.is_param_type(typ) {
90-
eprintln(*g.table.sym(typ))
91-
panic('unimplemented')
102+
g.infix_param_type(typ, op)
103+
} else {
104+
g.infix_numeric_type(typ, op)
92105
}
106+
}
93107

108+
fn (mut g Gen) infix_numeric_type(typ ast.Type, op token.Kind) {
94109
wasm_typ := g.as_numtype(g.get_wasm_type(typ))
95110

111+
// This adds two tiers of comparaison but is a lot cleaner
96112
match op {
97-
.plus {
98-
g.func.add(wasm_typ)
99-
}
100-
.minus {
101-
g.func.sub(wasm_typ)
102-
}
103-
.mul {
104-
g.func.mul(wasm_typ)
105-
}
106-
.mod {
107-
g.func.rem(wasm_typ, typ.is_signed())
108-
}
109-
.div {
110-
g.func.div(wasm_typ, typ.is_signed())
111-
}
112-
.eq {
113-
g.func.eq(wasm_typ)
114-
}
115-
.ne {
116-
g.func.ne(wasm_typ)
117-
}
118-
.gt {
119-
g.func.gt(wasm_typ, typ.is_signed())
120-
}
121-
.lt {
122-
g.func.lt(wasm_typ, typ.is_signed())
123-
}
124-
.ge {
125-
g.func.ge(wasm_typ, typ.is_signed())
126-
}
127-
.le {
128-
g.func.le(wasm_typ, typ.is_signed())
113+
.plus, .minus, .mul, .div, .mod {
114+
g.emit_arithmetic_op(wasm_typ, typ, op)
129115
}
130-
.xor {
131-
g.func.b_xor(wasm_typ)
116+
.eq, .ne, .gt, .lt, .ge, .le {
117+
g.emit_comparison_op(wasm_typ, typ, op)
132118
}
133-
.pipe {
134-
g.func.b_or(wasm_typ)
135-
}
136-
.amp {
137-
g.func.b_and(wasm_typ)
138-
}
139-
.left_shift {
140-
g.func.b_shl(wasm_typ)
141-
}
142-
.right_shift {
143-
g.func.b_shr(wasm_typ, true)
144-
}
145-
.unsigned_right_shift {
146-
g.func.b_shr(wasm_typ, false)
119+
.xor, .pipe, .amp, .left_shift, .right_shift, .unsigned_right_shift {
120+
g.emit_bitwise_op(wasm_typ, typ, op)
147121
}
148122
else {
149123
g.w_error('bad infix: op `${op}`')
150124
}
151125
}
152126
}
127+
128+
fn (mut g Gen) emit_arithmetic_op(wasm_typ wasm.NumType, typ ast.Type, op token.Kind) {
129+
match op {
130+
.plus { g.func.add(wasm_typ) }
131+
.minus { g.func.sub(wasm_typ) }
132+
.mul { g.func.mul(wasm_typ) }
133+
.div { g.func.div(wasm_typ, typ.is_signed()) }
134+
.mod { g.func.rem(wasm_typ, typ.is_signed()) }
135+
else { g.w_error('invalid aritmetic op: `${op}`') }
136+
}
137+
}
138+
139+
fn (mut g Gen) emit_comparison_op(wasm_typ wasm.NumType, typ ast.Type, op token.Kind) {
140+
is_signed := typ.is_signed()
141+
match op {
142+
.eq { g.func.eq(wasm_typ) }
143+
.ne { g.func.ne(wasm_typ) }
144+
.gt { g.func.gt(wasm_typ, is_signed) }
145+
.lt { g.func.lt(wasm_typ, is_signed) }
146+
.ge { g.func.ge(wasm_typ, is_signed) }
147+
.le { g.func.le(wasm_typ, is_signed) }
148+
else { g.w_error('invalid comparison op: `${op}`') }
149+
}
150+
}
151+
152+
fn (mut g Gen) emit_bitwise_op(wasm_typ wasm.NumType, typ ast.Type, op token.Kind) {
153+
match op {
154+
.xor { g.func.b_xor(wasm_typ) }
155+
.pipe { g.func.b_or(wasm_typ) }
156+
.amp { g.func.b_and(wasm_typ) }
157+
.left_shift { g.func.b_shl(wasm_typ) }
158+
.right_shift { g.func.b_shr(wasm_typ, true) }
159+
.unsigned_right_shift { g.func.b_shr(wasm_typ, false) }
160+
else { g.w_error('invalid bitwise op: `${op}`') }
161+
}
162+
}

‎vlib/v/gen/wasm/tests/builtin.vv‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,70 @@ fn test() {
33
println('hello!')
44
}
55

6+
fn str_concat() {
7+
a := 'Hello '
8+
b := 'V!'
9+
d := a + b
10+
println(d)
11+
}
12+
13+
fn str_loop_concat() {
14+
mut a := 'Y'
15+
for _ in 0 .. 20 {
16+
a += 'o'
17+
}
18+
a += '!'
19+
println(a)
20+
}
21+
22+
fn str_cmp() {
23+
a := 'Test???'
24+
b := 'TestingMcTestface'
25+
c := 'TestingMcTestface'
26+
27+
println(a != b)
28+
println(a == b)
29+
println(a == c)
30+
println(c != b)
31+
println(b == a)
32+
33+
// Duh
34+
println(b == b)
35+
println(a == a)
36+
println(c == c)
37+
38+
// Dynamically Generated
39+
mut d := ''
40+
e := 'aaaaaaaa'
41+
for _ in 0 .. 8 {
42+
d += 'a'
43+
}
44+
println(d == e)
45+
println(d != e)
46+
47+
// Complex expressions
48+
println(d == e && d == e)
49+
println(d == e && d == a)
50+
51+
println(b < a)
52+
println(b > a)
53+
println(b <= a)
54+
println(b >= a)
55+
56+
// If eval
57+
if b == c {
58+
println('B is C')
59+
}
60+
if a != c {
61+
println('A is not C')
62+
if a == c {
63+
panic('A is C')
64+
} else {
65+
println('A is not C')
66+
}
67+
}
68+
}
69+
670
fn str_methods() {
771
print(128.str())
872
println(i64(-192322).str())
@@ -27,6 +91,9 @@ fn main() {
2791
test()
2892
str_methods()
2993
str_implicit()
94+
str_concat()
95+
str_cmp()
96+
str_loop_concat()
3097
assertions()
3198

3299
// panic('nooo!')

‎vlib/v/gen/wasm/tests/builtin.vv.out‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@ false
44
false
55
true
66
110
7+
Hello V!
8+
true
9+
false
10+
false
11+
false
12+
false
13+
true
14+
true
15+
true
16+
true
17+
false
18+
true
19+
false
20+
false
21+
true
22+
false
23+
true
24+
B is C
25+
A is not C
26+
A is not C
27+
Yoooooooooooooooooooo!
728
wasm builtins
829
1
930
1

0 commit comments

Comments
 (0)