Repository: benhoyt/goawk (go)

Issue:
Add optimization for a[1] -> a["1"]
Based on conversation in https://github.com/benhoyt/goawk/pull/154#discussion_r995566431, I realized we (c|sh)ould actually make GoAWK do this micro-optimization: whenever it sees "array indexed by constant integer" like `a[1]` it could convert that to `a["1"]` for a slight speedup (though we should verify that it's actually faster!).

Pull request description:
Optimize constant integer array indexes to avoid toString() at runtime
When @xonixx was adding code coverage, I asked if he could make the coverage AST output __COVER["1"] instead of __COVER[1] to give a slight optimization. However, it's better to do that as an optimization that all users can benefit from instead.

The speedup for the ArrayOperations benchmark is about 11%, so I think it's worth it:

ArrayOperations-16   312ns ± 3%   276ns ± 3%  -11.54%  (p=0.008 n=5+5)

This commit adds the compiler optimization, but also makes the cover.go output __COVER[1] now that these expressions are optimized.

Fixes #157

Patch:
diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go
index 4687216..9f7758a 100644
--- a/internal/compiler/compiler.go
+++ b/internal/compiler/compiler.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"math"
 	"regexp"
+	"strconv"
 
 	"github.com/benhoyt/goawk/internal/ast"
 	"github.com/benhoyt/goawk/lexer"
@@ -997,6 +998,13 @@ func (c *compiler) binaryOp(op lexer.Token) {
 // Generate an array index, handling multi-indexes properly.
 func (c *compiler) index(index []ast.Expr) {
 	for _, expr := range index {
+		if e, ok := expr.(*ast.NumExpr); ok && e.Value == float64(int(e.Value)) {
+			// If index expression is integer constant, optimize to string "n"
+			// to avoid toString() at runtime.
+			s := strconv.Itoa(int(e.Value))
+			c.expr(&ast.StrExpr{Value: s})
+			continue
+		}
 		c.expr(expr)
 	}
 	if len(index) > 1 {
diff --git a/internal/cover/cover.go b/internal/cover/cover.go
index 024cc52..2834d3c 100644
--- a/internal/cover/cover.go
+++ b/internal/cover/cover.go
@@ -243,7 +243,7 @@ func (cover *Cover) trackStatement(stmts []ast.Stmt) ast.Stmt {
 	})
 	left := &ast.IndexExpr{
 		Array: ast.ArrayRef(ArrayName, lexer.Position{}),
-		Index: []ast.Expr{&ast.StrExpr{Value: strconv.Itoa(len(cover.trackedBlocks))}},
+		Index: []ast.Expr{&ast.NumExpr{Value: float64(len(cover.trackedBlocks))}},
 	}
 	if cover.mode == ModeCount {
 		// AST for __COVER[index]++


Test patch:
diff --git a/testdata/cover/a1_a2_covermode_count.awk b/testdata/cover/a1_a2_covermode_count.awk
index 67b187e..b65c2ea 100644
--- a/testdata/cover/a1_a2_covermode_count.awk
+++ b/testdata/cover/a1_a2_covermode_count.awk
@@ -1,5 +1,5 @@
 BEGIN {
-  __COVER["1"]++
+  __COVER[1]++
   print "hello"
   callF()
   callF()
@@ -8,26 +8,26 @@ BEGIN {
 }
 
 BEGIN {
-  __COVER["3"]++
+  __COVER[3]++
   if (1) {
-    __COVER["2"]++
+    __COVER[2]++
     print "hello"
     print "world"
   }
-  __COVER["5"]++
+  __COVER[5]++
   print "end"
   for (i = 0; i < 7; i++) {
-    __COVER["4"]++
+    __COVER[4]++
     print i
   }
 }
 
 END {
-  __COVER["6"]++
+  __COVER[6]++
   print "END"
 }
 
 function callF() {
-  __COVER["7"]++
+  __COVER[7]++
   print "world"
 }
\ No newline at end of file
diff --git a/testdata/cover/a1_covermode_set.awk b/testdata/cover/a1_covermode_set.awk
index 09e052a..9bc4009 100644
--- a/testdata/cover/a1_covermode_set.awk
+++ b/testdata/cover/a1_covermode_set.awk
@@ -1,5 +1,5 @@
 BEGIN {
-  __COVER["1"] = 1
+  __COVER[1] = 1
   print "hello"
   callF()
   callF()
@@ -8,11 +8,11 @@ BEGIN {
 }
 
 END {
-  __COVER["2"] = 1
+  __COVER[2] = 1
   print "END"
 }
 
 function callF() {
-  __COVER["3"] = 1
+  __COVER[3] = 1
   print "world"
 }
\ No newline at end of file
diff --git a/testdata/cover/a2_covermode_count.awk b/testdata/cover/a2_covermode_count.awk
index 26c3ce8..6d7fb2b 100644
--- a/testdata/cover/a2_covermode_count.awk
+++ b/testdata/cover/a2_covermode_count.awk
@@ -1,14 +1,14 @@
 BEGIN {
-  __COVER["2"]++
+  __COVER[2]++
   if (1) {
-    __COVER["1"]++
+    __COVER[1]++
     print "hello"
     print "world"
   }
-  __COVER["4"]++
+  __COVER[4]++
   print "end"
   for (i = 0; i < 7; i++) {
-    __COVER["3"]++
+    __COVER[3]++
     print i
   }
 }
\ No newline at end of file
diff --git a/testdata/cover/a3_covermode_set.awk b/testdata/cover/a3_covermode_set.awk
index f5da5c8..c31175a 100644
--- a/testdata/cover/a3_covermode_set.awk
+++ b/testdata/cover/a3_covermode_set.awk
@@ -1,29 +1,29 @@
 BEGIN {
-  __COVER["10"] = 1
+  __COVER[10] = 1
   if (1) {
-    __COVER["9"] = 1
+    __COVER[9] = 1
     for (i = 0; i < 10; i++) {
-      __COVER["8"] = 1
+      __COVER[8] = 1
       print i
       while (1) {
-        __COVER["7"] = 1
+        __COVER[7] = 1
         do {
-          __COVER["6"] = 1
+          __COVER[6] = 1
           for (j in A) {
-            __COVER["5"] = 1
+            __COVER[5] = 1
             print j
             if (2) {
-              __COVER["3"] = 1
+              __COVER[3] = 1
               print 2
               {
-                __COVER["2"] = 1
+                __COVER[2] = 1
                 if (3) {
-                  __COVER["1"] = 1
+                  __COVER[1] = 1
                   print 3
                 }
               }
             } else {
-              __COVER["4"] = 1
+              __COVER[4] = 1
               continue
             }
           }