Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

match for string slices #6202

Merged
merged 10 commits into from
Jul 5, 2024
Merged

match for string slices #6202

merged 10 commits into from
Jul 5, 2024

Conversation

xunilrj
Copy link
Contributor

@xunilrj xunilrj commented Jul 1, 2024

Description

This PR implements match for string slices including radix trie optimization and is a task of #5110.

For example a simple match like

fn return_match_on_str_slice(param: str) -> u64 {
    match param {
        "get_a" => { 1u64 },
        "get_a_b" => { 2u64 },
        "get_b" => { 3u64 },
        _ => { 1000u64 },
    }
}

will generate code following this logic:

     let packed_string = "get_a_b"
     if str.len() == 5
         if str[0..4] == "get_" at packed_string[0]
             if str[4..5] == "b" at packed_string[6]
                 return branch 2
             if str[4..5] == "a" at packed_string[4]
                 return branch 0
             return wildcard branch
         return wildcard branch
     if str.len() == 7
         if str[0..7] == "get_a_b" at packed_string[0]
             return branch 1
         return wildcard branch
     return wildcard branch

In logical terms, this boils down to checking the length and an O(N) check on the string. Albeit the bytecode will be more complex because of all the branches.

Another interesting optimization is the "packed string literal" that coalesces all "match arms string slices" into just one string. In the case above, given that one of the arms contains all the necessary strings for all other comparisons, we will create just one string literal. Saving a lot of bytes in the data section.

The section below describes how rustc deals with this desugaring. I think these choices make more sense to us for two reasons:

1 - Avoid testing common prefixes multiple times will spend less gas in general (needs more testing);
2 - packing all strings will decrease the data section size.

This is the bytecode generated in this case:

fn return_match_on_str_slice(param: str) -> u64 {
              match param {
                  "get_a" => { 1u64 },
                  "get_a_b" => { 2u64 },
                  "get_b" => { 3u64 },
                  _ => { 1000u64 },
              }
          } @ /home/xunilrj/github/sway/test/src/e2e_vm_tests/test_programs/should_pass/language/match_expressions_all/src/main.sw:22:1
              0x0000017c PSHL 0xf                                        ;; [149, 0, 0, 15]
              0x00000180 PSHH 0x80000                                    ;; [150, 8, 0, 0]
              0x00000184 MOVE R59 $sp                                    ;; [26, 236, 80, 0]
              0x00000188 CFEI 0x90                                       ;; [145, 0, 0, 144]
              0x0000018c MOVE $writable R58                              ;; [26, 67, 160, 0]
              0x00000190 MOVE R19 R62                                    ;; [26, 79, 224, 0]
          match param {
                  "get_a" => { 1u64 },
                  "get_a_b" => { 2u64 },
                  "get_b" => { 3u64 },
                  _ => { 1000u64 },
              } @ /home/xunilrj/github/sway/test/src/e2e_vm_tests/test_programs/should_pass/language/match_expressions_all/src/main.sw:23:5
              0x00000194 ADDI R17 R59 0x80                               ;; 
              0x00000198 MOVI R18 0x10                                   ;; 
              0x0000019c MCP R17 $writable R18                           ;; 
              0x000001a0 MOVI R17 0x7                                    ;; 0x7 = "get_a_b".len()
           @ <autogenerated>:1:1
              0x000001a4 LW $writable R59 0x11                           ;; R59 + 0x11 = a.len()
              0x000001a8 EQ $writable $writable R17                      ;; a.len() == 0x7
              0x000001ac JNZF $writable $zero 0x3c                       ;; if false jump to 2a0?
              
              0x000001b0 MOVI R17 0x5                                    ;; we have two arms with length equals 0x5
              0x000001b4 LW $writable R59 0x11                           ;; R59 + 0x11 = a.len()
              0x000001b8 EQ $writable $writable R17                      ;; a.len() == 0x5
              0x000001bc MOVI R17 0x3e8                                  ;; 0x3e8 = 1000 (wildcard return value)
              0x000001c0 JNZF $writable $zero 0x1                        ;; if true jump to 1c8
              0x000001c4 JMPF $zero 0x35                                 ;; if false jump to 29c (will return R17)

              0x000001c8 LW $writable R63 0x3                            ;; R63 = start of data section, will load 13c
              0x000001cc ADD $writable $writable $pc                     ;; $writable = 0x308 = packed strings
              0x000001d0 ADDI R17 R59 0x20                               ;; 
              0x000001d4 SW R59 $writable 0x4                            ;; R59 + 0x4 = packed strings
              0x000001d8 MOVI $writable 0x7                              ;; 
              0x000001dc SW R59 $writable 0x5                            ;; R59 + 0x5 = 0x7
              0x000001e0 ADDI $writable R59 0x30                         ;; 
              0x000001e4 MOVI R18 0x10                                   ;; 
              0x000001e8 MCP $writable R17 R18                           ;; R59 + 0x30 = R59 + 0x20
              0x000001ec MOVI R18 0x4                                    ;; 0x4 = "get_".len()
              0x000001f0 LW $writable R59 0x10                           ;; 
              0x000001f4 ADDI $writable $writable 0x0                    ;; 
              0x000001f8 LW R17 R59 0x6                                  ;; R17 = a.ptr()
              0x000001fc ADDI R17 R17 0x0                                ;; 
              0x00000200 MEQ $writable $writable R17 R18                 ;; a[0..4] = packed[0..4]
              0x00000204 MOVI R17 0x3e8                                  ;; 0x3e8 = 1000 (wildcard return value)
              0x00000208 JNZF $writable $zero 0x1                        ;; if true jump to 210
              0x0000020c JMPF $zero 0x23                                 ;; if false jump to 29c (will return R17)
              ....      
          .data_section:
              0x00000300 .bytes as hex ([]), len i0, as ascii ""
              0x00000300 .word i18446744073709486084, as hex be bytes ([FF, FF, FF, FF, FF, FF, 00, 04])
              0x00000308 .bytes as hex ([67, 65, 74, 5F, 61, 5F, 62]), len i7, as ascii "get_a_b"
              0x00000310 .word i500, as hex be bytes ([00, 00, 00, 00, 00, 00, 01, F4])
              0x00000318 .word i316, as hex be bytes ([00, 00, 00, 00, 00, 00, 01, 3C])
              0x00000320 .word i244, as hex be bytes ([00, 00, 00, 00, 00, 00, 00, F4])
              0x00000328 .word i176, as hex be bytes ([00, 00, 00, 00, 00, 00, 00, B0])
              0x00000330 .word i100, as hex be bytes ([00, 00, 00, 00, 00, 00, 00, 64])

How rustc desugar match

For comparison, this is the generated ASM with comments on how Rust tackles this.
First, this is the function used:

#[inline(never)]
fn f(a: &str) -> u64 {
    match a {
        "get_method" => 0,
        "get_tokens" => 1,
        "get_something_else" => 2,
        "get_tokens_2" => 3,
        "clear" => 4,
        "get_m" => 5,
        _ => 6,
    }
}

This is the LLVM IR generated. There is a match on the length of each string slice arms. The valid range is (5, 18), everything outside of this is the wildcard match arm. This range will be important later.

efine internal fastcc noundef i64 @example::f::hdb860bcd6d383112(ptr noalias nocapture noundef nonnull readonly align 1 %a.0, i64 noundef %a.1) unnamed_addr {
start:
  switch i64 %a.1, label %bb13 [
    i64 10, label %"_ZN73_$LT$$u5b$A$u5d$$u20$as$u20$core..slice..cmp..SlicePartialEq$LT$B$GT$$GT$5equal17h510120b4d3581de7E.exit"
    i64 18, label %"_ZN73_$LT$$u5b$A$u5d$$u20$as$u20$core..slice..cmp..SlicePartialEq$LT$B$GT$$GT$5equal17h510120b4d3581de7E.exit30"
    i64 12, label %"_ZN73_$LT$$u5b$A$u5d$$u20$as$u20$core..slice..cmp..SlicePartialEq$LT$B$GT$$GT$5equal17h510120b4d3581de7E.exit35"
    i64 5, label %"_ZN73_$LT$$u5b$A$u5d$$u20$as$u20$core..slice..cmp..SlicePartialEq$LT$B$GT$$GT$5equal17h510120b4d3581de7E.exit40"
  ]

this is how "f" is called

	mov	rbx, qword ptr [rsp + 32]
	mov	r14, qword ptr [rsp + 40]
	mov	rsi, qword ptr [rsp + 48]     <- length of the string slice
	mov	rdi, r14                      <- ptr to string slice
	call	_ZN4main1f17h126a5dfd4e318ebcE

this is f body.

ja .LBB8_12 jumps into a simple return, returning EAX as 6. It is the wildcard return value. The cleverness of this is that when RSI is smaller than 5, it will become negative (because of add rsi, -5, wrapping into huge unsigned ints, and will also trigger JA (which stands for Jump Above), effectively jumping when the slice length is outside of the expected range which is (5, 18).

After that, it uses a jump table based on the string length minus 5. Everywhere the string length is invalid, the jump address is LBB8_12., still returning EAX as 6.

_ZN4main1f17h126a5dfd4e318ebcE:
	.cfi_startproc
	mov	eax, 6
	add	rsi, -5
	cmp	rsi, 13
	ja	.LBB8_12

	lea	rcx, [rip + .LJTI8_0]
	movsxd	rdx, dword ptr [rcx + 4*rsi]
	add	rdx, rcx
	jmp	rdx
.LBB8_12:
	ret

This is the jump table used:

.LJTI8_0:
	.long	.LBB8_9-.LJTI8_0
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_2-.LJTI8_0        <- 5th entry is length = 10 (remember we add -5 to the length)
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_8-.LJTI8_0
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_12-.LJTI8_0
	.long	.LBB8_6-.LJTI8_0

The interesting entry is entry 5, which has two strings: "get_method" and "get_tokens". Here we can see that rust actually compares the complete string slice twice. Even though they have an intersection.

.LBB8_2:
	movabs	rcx, 7526752397670245735=6874656D5F746567="htem_teg" (inverted "get_meth")
	xor	rcx, qword ptr [rdi]
	movzx	edx, word ptr [rdi + 8]
	xor	rdx, 25711=646F="do" (inverted "od")
	or	rdx, rcx
	je	.LBB8_3
	movabs	rcx, 7308057365947114855=656B6F745F746567="ekot_teg" (inverted "get_toke")
	xor	rcx, qword ptr [rdi]
	movzx	edx, word ptr [rdi + 8]
	xor	rdx, 29550=736E="sn" (inverted "ns")
	or	rdx, rcx
	je	.LBB8_5
.LBB8_3:
	xor	eax, eax    <- returns 0
	ret
.LBB8_5:
	mov	eax, 1     <- returns 1
	ret

This is comparable to what clang is doing: rust-lang/rust#61961

Code and Bytecode

This PR also implements code printing when printing bytecode. For now this is only enable for tests. It gnerates something like:

  match param {
                  "get_a" => { 1u64 },
                  "get_a_b" => { 2u64 },
                  "get_b" => { 3u64 },
                  _ => { 1000u64 },
              } @ /home/xunilrj/github/sway/test/src/e2e_vm_tests/test_programs/should_pass/language/match_expressions_all/src/main.sw:23:5
              0x00000194 ADDI R17 R59 0x80                               ;; 
              0x00000198 MOVI R18 0x10                                   ;; 
              0x0000019c MCP R17 $writable R18                           ;; 
              0x000001a0 MOVI R17 0x7                                    ;; 0x7 = "get_a_b".len()
           @ <autogenerated>:1:1
              0x000001a4 LW $writable R59 0x11                           ;; R59 + 0x11 = a.len()
              0x000001a8 EQ $writable $writable R17                      ;; a.len() == 0x7  

As we can see, not great, but helpful nonetheless. We can (should?) improve this by better "carrying" spans in all transformations and lowerings.

Checklist

  • I have linked to any relevant issues.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have updated the documentation where relevant (API docs, the reference, and the Sway book).
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added (or requested a maintainer to add) the necessary Breaking* or New Feature labels where relevant.
  • I have done my best to ensure that my PR adheres to the Fuel Labs Code Review Standards.
  • I have requested a review from the relevant team or maintainers.

@xunilrj xunilrj changed the title Xunilrj/string slice match match for string slices Jul 1, 2024
@xunilrj xunilrj self-assigned this Jul 1, 2024
Copy link

github-actions bot commented Jul 1, 2024

Benchmark for fcf2380

Click to view benchmark
Test Base PR %
code_action 5.2±0.06ms 5.2±0.40ms 0.00%
code_lens 283.1±10.19ns 350.7±11.32ns +23.88%
compile 2.5±0.03s 2.6±0.03s +4.00%
completion 4.7±0.03ms 4.7±0.02ms 0.00%
did_change_with_caching 2.5±0.04s 2.5±0.02s 0.00%
document_symbol 895.2±51.69µs 903.1±52.00µs +0.88%
format 73.7±1.00ms 73.8±1.30ms +0.14%
goto_definition 338.3±5.87µs 339.2±4.29µs +0.27%
highlight 9.1±0.31ms 9.0±0.15ms -1.10%
hover 500.0±8.29µs 486.8±6.90µs -2.64%
idents_at_position 118.6±0.45µs 117.1±0.26µs -1.26%
inlay_hints 636.0±8.94µs 638.5±26.13µs +0.39%
on_enter 472.7±12.28ns 481.0±17.32ns +1.76%
parent_decl_at_position 3.7±0.02ms 3.7±0.05ms 0.00%
prepare_rename 337.0±6.71µs 338.8±9.86µs +0.53%
rename 9.3±0.09ms 9.3±0.25ms 0.00%
semantic_tokens 1227.4±12.07µs 1292.1±20.72µs +5.27%
token_at_position 336.7±1.96µs 348.7±3.32µs +3.56%
tokens_at_position 3.7±0.03ms 3.7±0.03ms 0.00%
tokens_for_file 396.7±1.97µs 444.1±5.25µs +11.95%
traverse 37.6±0.82ms 38.2±0.77ms +1.60%

Copy link

github-actions bot commented Jul 1, 2024

Benchmark for f90f755

Click to view benchmark
Test Base PR %
code_action 5.2±0.09ms 5.4±0.10ms +3.85%
code_lens 293.8±15.94ns 350.1±35.85ns +19.16%
compile 2.5±0.04s 2.6±0.03s +4.00%
completion 4.8±0.17ms 4.7±0.11ms -2.08%
did_change_with_caching 2.5±0.03s 2.5±0.04s 0.00%
document_symbol 876.2±28.43µs 853.0±19.73µs -2.65%
format 73.5±1.10ms 74.1±1.33ms +0.82%
goto_definition 339.1±9.47µs 341.9±7.20µs +0.83%
highlight 9.1±0.09ms 9.1±0.24ms 0.00%
hover 493.6±8.29µs 499.1±5.24µs +1.11%
idents_at_position 120.3±0.74µs 118.2±1.15µs -1.75%
inlay_hints 641.7±26.30µs 648.9±18.24µs +1.12%
on_enter 464.6±16.31ns 461.0±11.31ns -0.77%
parent_decl_at_position 3.7±0.05ms 3.7±0.08ms 0.00%
prepare_rename 332.8±6.88µs 343.4±7.48µs +3.19%
rename 9.3±0.12ms 9.2±0.06ms -1.08%
semantic_tokens 1321.6±15.61µs 1275.3±19.85µs -3.50%
token_at_position 339.1±2.02µs 337.0±2.02µs -0.62%
tokens_at_position 3.7±0.05ms 3.7±0.07ms 0.00%
tokens_for_file 404.7±2.40µs 402.4±3.86µs -0.57%
traverse 37.8±0.36ms 40.0±0.97ms +5.82%

Copy link

github-actions bot commented Jul 3, 2024

Benchmark for 69ca4b3

Click to view benchmark
Test Base PR %
code_action 5.0±0.12ms 5.2±0.07ms +4.00%
code_lens 327.5±9.57ns 282.2±8.21ns -13.83%
compile 2.6±0.04s 2.6±0.04s 0.00%
completion 4.5±0.01ms 4.7±0.13ms +4.44%
did_change_with_caching 2.5±0.02s 2.5±0.04s 0.00%
document_symbol 910.4±39.82µs 885.7±53.22µs -2.71%
format 82.4±1.32ms 82.5±1.00ms +0.12%
goto_definition 338.2±9.00µs 339.4±6.85µs +0.35%
highlight 8.7±0.12ms 8.9±0.03ms +2.30%
hover 495.7±9.76µs 496.2±12.37µs +0.10%
idents_at_position 119.5±0.44µs 120.7±1.02µs +1.00%
inlay_hints 620.2±13.16µs 642.7±23.06µs +3.63%
on_enter 451.5±17.02ns 471.6±11.32ns +4.45%
parent_decl_at_position 3.6±0.02ms 3.7±0.04ms +2.78%
prepare_rename 332.7±8.23µs 338.7±8.94µs +1.80%
rename 8.9±0.17ms 9.2±0.15ms +3.37%
semantic_tokens 1243.8±13.86µs 1307.8±13.17µs +5.15%
token_at_position 331.4±2.80µs 344.4±4.09µs +3.92%
tokens_at_position 3.6±0.02ms 3.7±0.07ms +2.78%
tokens_for_file 395.1±2.56µs 406.8±2.00µs +2.96%
traverse 38.2±0.66ms 37.6±0.76ms -1.57%

@xunilrj xunilrj force-pushed the xunilrj/string-slice-match branch from 9e7c56b to 98e704d Compare July 4, 2024 10:29
Copy link

github-actions bot commented Jul 4, 2024

Benchmark for 3cd9fe7

Click to view benchmark
Test Base PR %
code_action 5.3±0.11ms 5.0±0.05ms -5.66%
code_lens 281.0±8.62ns 330.6±12.65ns +17.65%
compile 2.7±0.04s 2.6±0.05s -3.70%
completion 4.7±0.05ms 4.5±0.07ms -4.26%
did_change_with_caching 2.5±0.06s 2.5±0.06s 0.00%
document_symbol 882.7±45.71µs 934.2±21.15µs +5.83%
format 71.7±0.87ms 71.6±1.28ms -0.14%
goto_definition 363.0±6.76µs 341.2±6.33µs -6.01%
highlight 9.0±0.09ms 8.6±0.05ms -4.44%
hover 497.2±10.31µs 494.3±5.43µs -0.58%
idents_at_position 121.7±0.32µs 121.1±0.43µs -0.49%
inlay_hints 647.1±21.26µs 631.2±25.27µs -2.46%
on_enter 463.9±6.90ns 483.1±17.75ns +4.14%
parent_decl_at_position 3.7±0.08ms 3.6±0.04ms -2.70%
prepare_rename 339.0±10.33µs 342.7±8.36µs +1.09%
rename 9.3±0.14ms 8.9±0.10ms -4.30%
semantic_tokens 1228.5±16.01µs 1245.0±16.26µs +1.34%
token_at_position 334.2±2.96µs 336.4±2.77µs +0.66%
tokens_at_position 3.7±0.02ms 3.6±0.05ms -2.70%
tokens_for_file 393.3±6.38µs 402.5±3.03µs +2.34%
traverse 37.7±0.39ms 38.3±0.61ms +1.59%

@xunilrj xunilrj marked this pull request as ready for review July 4, 2024 11:03
@xunilrj xunilrj requested review from a team as code owners July 4, 2024 11:03
@JoshuaBatty JoshuaBatty requested review from a team July 5, 2024 00:12
Copy link

github-actions bot commented Jul 5, 2024

Benchmark for 30ff363

Click to view benchmark
Test Base PR %
code_action 5.2±0.03ms 5.2±0.02ms 0.00%
code_lens 279.2±8.18ns 332.4±9.61ns +19.05%
compile 2.5±0.04s 2.5±0.03s 0.00%
completion 4.6±0.06ms 4.7±0.07ms +2.17%
did_change_with_caching 2.5±0.03s 2.5±0.02s 0.00%
document_symbol 897.2±29.53µs 851.8±14.85µs -5.06%
format 71.1±1.18ms 71.5±1.49ms +0.56%
goto_definition 338.9±7.40µs 332.2±7.62µs -1.98%
highlight 8.9±0.15ms 9.0±0.03ms +1.12%
hover 493.6±6.84µs 489.8±6.71µs -0.77%
idents_at_position 118.3±0.51µs 120.6±0.39µs +1.94%
inlay_hints 632.2±14.18µs 644.6±42.22µs +1.96%
on_enter 477.7±17.27ns 466.3±15.88ns -2.39%
parent_decl_at_position 3.7±0.04ms 3.7±0.02ms 0.00%
prepare_rename 338.5±5.67µs 332.1±6.08µs -1.89%
rename 9.2±0.21ms 9.3±0.25ms +1.09%
semantic_tokens 1188.4±21.99µs 1254.9±17.24µs +5.60%
token_at_position 341.1±7.89µs 328.8±3.83µs -3.61%
tokens_at_position 3.7±0.02ms 3.7±0.03ms 0.00%
tokens_for_file 397.6±6.98µs 393.5±4.47µs -1.03%
traverse 37.1±0.61ms 37.5±0.59ms +1.08%

@IGI-111 IGI-111 enabled auto-merge (squash) July 5, 2024 20:45
@IGI-111 IGI-111 merged commit bcd1cf9 into master Jul 5, 2024
36 of 37 checks passed
@IGI-111 IGI-111 deleted the xunilrj/string-slice-match branch July 5, 2024 20:59
Copy link

github-actions bot commented Jul 5, 2024

Benchmark for b4f0ac7

Click to view benchmark
Test Base PR %
code_action 5.1±0.14ms 5.3±0.09ms +3.92%
code_lens 327.5±10.09ns 310.8±8.22ns -5.10%
compile 2.7±0.05s 2.7±0.02s 0.00%
completion 4.7±0.13ms 4.7±0.11ms 0.00%
did_change_with_caching 2.6±0.04s 2.7±0.03s +3.85%
document_symbol 899.0±45.15µs 941.3±51.68µs +4.71%
format 72.1±1.14ms 72.4±1.04ms +0.42%
goto_definition 341.7±9.31µs 343.3±5.49µs +0.47%
highlight 8.9±0.04ms 8.8±0.15ms -1.12%
hover 497.2±7.41µs 506.2±4.19µs +1.81%
idents_at_position 118.1±0.32µs 118.5±1.44µs +0.34%
inlay_hints 639.7±12.23µs 648.9±9.40µs +1.44%
on_enter 449.6±25.66ns 453.1±9.49ns +0.78%
parent_decl_at_position 3.6±0.03ms 3.6±0.05ms 0.00%
prepare_rename 340.1±6.55µs 345.1±13.93µs +1.47%
rename 9.0±0.09ms 9.4±0.17ms +4.44%
semantic_tokens 1260.9±15.31µs 1285.1±13.85µs +1.92%
token_at_position 337.3±1.85µs 335.1±1.66µs -0.65%
tokens_at_position 3.6±0.05ms 3.6±0.03ms 0.00%
tokens_for_file 396.3±1.88µs 408.4±3.38µs +3.05%
traverse 39.7±0.82ms 40.8±1.75ms +2.77%

@xunilrj xunilrj mentioned this pull request Jul 8, 2024
17 tasks
ironcev added a commit that referenced this pull request Jul 8, 2024
)

## Description

This PR adds `string slices` to `CURRENTLY_SUPPORTED_TYPES_MESSAGE` in
`match` expressions. The support for string slices is added in #6202.

## Checklist

- [x] I have linked to any relevant issues.
- [ ] I have commented my code, particularly in hard-to-understand
areas.
- [ ] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [ ] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [ ] I have added tests that prove my fix is effective or that my
feature works.
- [ ] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants