Skip to content

Commit

Permalink
asm: implement unused label warning
Browse files Browse the repository at this point in the history
Resolves #4
  • Loading branch information
fjl committed Nov 24, 2024
1 parent dba4f06 commit 410b8f1
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 27 deletions.
21 changes: 15 additions & 6 deletions asm/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,12 @@ func (c *Compiler) errorAt(inst ast.Statement, err error) {
if err == nil {
panic("BUG: errorAt(st, nil)")
}
c.errors.add(&astError{inst: inst, err: err})
c.errors.add(&statementError{inst: inst, err: err})
}

// warnf pushes a warning to the error list.
func (c *Compiler) warnf(inst ast.Statement, format string, args ...any) {
c.errors.add(&simpleWarning{pos: inst.Position(), str: fmt.Sprintf(format, args...)})
}

func (c *Compiler) compileSource(filename string, input []byte) []byte {
Expand Down Expand Up @@ -181,6 +186,15 @@ func (c *Compiler) compileDocument(doc *ast.Document) (output []byte) {
break
}

if c.errors.hasError() {
return nil // no output if source has errors
}

// Run analysis. Note this is also disabled if there are errors because there could
// be lots of useless warnings otherwise.
c.checkLabelsUsed(doc, e)

// Create the bytecode.
return c.generateOutput(prog)
}

Expand Down Expand Up @@ -274,11 +288,6 @@ func (c *Compiler) parseIncludeFile(file string, inst *ast.IncludeSt, depth int)

// generateOutput creates the bytecode. This is also where instruction names get resolved.
func (c *Compiler) generateOutput(prog *compilerProg) []byte {
if c.errors.hasError() {
// Refuse to output if source had errors.
return nil
}

var output []byte
for _, inst := range prog.iterInstructions() {
if len(output) != inst.pc {
Expand Down
42 changes: 42 additions & 0 deletions asm/compiler_analysis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package asm

import (
"github.com/fjl/geas/internal/ast"
)

// checkLabelsUsed warns about label definitions that were not hit by the evaluator.
func (c *Compiler) checkLabelsUsed(doc *ast.Document, e *evaluator) {
stack := []*ast.Document{doc}
for len(stack) > 0 {
top := stack[len(stack)-1]
for _, st := range top.Statements {
switch st := st.(type) {
case *ast.LabelDefSt:
if !e.isLabelUsed(st) {
c.warnf(st, "label %s unused in program", st)
}
case *ast.IncludeSt:
if incdoc := c.includes[st]; incdoc != nil {
stack = append(stack, incdoc)
}
}
}
stack = stack[1:]
}
}
24 changes: 19 additions & 5 deletions asm/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,24 +114,38 @@ func (e compilerError) Error() string {
}
}

// astError is an error related to an assembler instruction.
type astError struct {
// statementError is an error related to an assembler instruction.
type statementError struct {
inst ast.Statement
err error
}

func (e *astError) Position() ast.Position {
func (e *statementError) Position() ast.Position {
return e.inst.Position()
}

func (e *astError) Unwrap() error {
func (e *statementError) Unwrap() error {
return e.err
}

func (e *astError) Error() string {
func (e *statementError) Error() string {
return fmt.Sprintf("%v: %s", e.inst.Position(), e.err.Error())
}

// simpleWarning is a warning issued by the compiler.
type simpleWarning struct {
pos ast.Position
str string
}

func (e *simpleWarning) Error() string {
return fmt.Sprintf("%v: warning: %s", e.pos, e.str)
}

func (e *simpleWarning) IsWarning() bool {
return true
}

// unassignedLabelError signals use of a label that doesn't have a valid PC.
type unassignedLabelError struct {
lref *ast.LabelRefExpr
Expand Down
22 changes: 16 additions & 6 deletions asm/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ import (

// evaluator is for evaluating expressions.
type evaluator struct {
inStack map[*ast.ExpressionMacroDef]struct{}
labelPC map[evalLabelKey]int
globals *globalScope
inStack map[*ast.ExpressionMacroDef]struct{}
labelPC map[evalLabelKey]int
usedLabels map[*ast.LabelDefSt]struct{}
globals *globalScope
}

type evalLabelKey struct {
Expand All @@ -46,9 +47,10 @@ type evalEnvironment struct {

func newEvaluator(gs *globalScope) *evaluator {
return &evaluator{
inStack: make(map[*ast.ExpressionMacroDef]struct{}),
labelPC: make(map[evalLabelKey]int),
globals: gs,
inStack: make(map[*ast.ExpressionMacroDef]struct{}),
labelPC: make(map[evalLabelKey]int),
usedLabels: make(map[*ast.LabelDefSt]struct{}),
globals: gs,
}
}

Expand Down Expand Up @@ -95,9 +97,17 @@ func (e *evaluator) lookupLabel(doc *ast.Document, lref *ast.LabelRefExpr) (pc i
if li.Dotted != lref.Dotted {
return 0, false, fmt.Errorf("undefined label %v (but %v exists)", lref, li)
}
// mark label used (for unused label analysis)
e.usedLabels[li] = struct{}{}
return pc, pcValid, nil
}

// isLabelUsed reports whether the given label definition was used during expression evaluation.
func (e *evaluator) isLabelUsed(li *ast.LabelDefSt) bool {
_, ok := e.usedLabels[li]
return ok
}

func (e *evaluator) eval(expr ast.Expr, env *evalEnvironment) (*big.Int, error) {
switch expr := expr.(type) {
case *ast.LiteralExpr:
Expand Down
4 changes: 2 additions & 2 deletions asm/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (gs *globalScope) registerLabel(def *ast.LabelDefSt, doc *ast.Document) {
func (gs *globalScope) registerInstrMacro(name string, def globalDef[*ast.InstructionMacroDef]) error {
firstDef, found := gs.instrMacro[name]
if found {
return &astError{
return &statementError{
inst: def.def,
err: fmt.Errorf("macro %%%s already defined%s", name, firstDef.doc.CreationString()),
}
Expand All @@ -91,7 +91,7 @@ func (gs *globalScope) registerInstrMacro(name string, def globalDef[*ast.Instru
func (gs *globalScope) registerExprMacro(name string, def globalDef[*ast.ExpressionMacroDef]) error {
firstDef, found := gs.exprMacro[name]
if found {
return &astError{
return &statementError{
inst: def.def,
err: fmt.Errorf("macro %s already defined%s", name, firstDef.doc.CreationString()),
}
Expand Down
53 changes: 45 additions & 8 deletions asm/testdata/compiler-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@ instr-macro-inner-label:
theLabel: stop
output:
bytecode: "6005 56 6002 5b 6001 5b 00"
warnings:
- ':9: warning: label @theLabel unused in program'

instr-macro-outer-label:
input:
Expand Down Expand Up @@ -686,34 +688,37 @@ macro-call:
input:
code: |
#define myMacro(a, b) = (100 + $a) / $b
start: PUSH myMacro(4, 2)
ADD
PUSH myMacro(4, 2)
output:
bytecode: "5b 6034"
bytecode: "01 6034"

macro-ref:
input:
code: |
#define myMacro = 100
start:
ADD
PUSH myMacro
output:
bytecode: "5b 60 64"
bytecode: "01 60 64"

macro-ref-call-empty:
input:
code: |
#define myMacro() = 100
start: PUSH myMacro
ADD
PUSH myMacro
output:
bytecode: "5b 6064"
bytecode: "01 6064"

macro-ref-call-empty-2:
input:
code: |
#define myMacro = 100
start: PUSH myMacro()
ADD
PUSH myMacro()
output:
bytecode: 5b 6064
bytecode: 01 6064

push-expression:
input:
Expand Down Expand Up @@ -822,3 +827,35 @@ pragma-unknown:
output:
errors:
- ':1: unknown #pragma something'

unused-label-warning:
input:
code: |
top:
push 1
push 2
add
jump @top
end:
output:
bytecode: "5b60016002016000565b"
warnings:
- ':6: warning: label @end unused in program'

unused-label-in-include:
input:
code: |
top:
push 1
push 2
add
#include "label.evm"
jump @top
files:
label.evm: |
push 3
label:
output:
bytecode: "5b600160020160035b600056"
warnings:
- 'label.evm:2: warning: label @label unused in program'

0 comments on commit 410b8f1

Please sign in to comment.