~mna/snow

b3395e18fd5ab00521c672c0fcbd680bee3de7c8 — Martin Angers 1 year, 10 months ago d4ea6f9
pkg/codegen: work on name mangling
M .golangci.toml => .golangci.toml +1 -1
@@ 36,7 36,7 @@
    "`commands` is a global variable",

    # pkg/codegen
    "`basicKindToGo` is a global variable",
    "`basicIdentsToGo` is a global variable",
    "`goKeywords` is a global variable",
    "`tokenToGoToken` is a global variable",
    "`testUpdateCodegenTests` is a global variable",

M pkg/codegen/mangle.go => pkg/codegen/mangle.go +126 -148
@@ 1,59 1,115 @@
package codegen

import (
	"fmt"
	"strconv"
	"strings"

	"git.sr.ht/~mna/snow/pkg/scanner"
	"git.sr.ht/~mna/snow/pkg/semantic"
)

type mangler struct {
	unit  *semantic.Unit
	names map[semantic.Node]string
}

func (m *mangler) mangle() {
	ruses := make(map[semantic.Decl]bool, len(m.unit.ValueUses))
	// TODO: if object is public/exported and top-level, do not mangle as "_" if unused
	for _, decl := range m.unit.ValueUses {
// mangle mangles all identifiers declared in decls. The logic is as follows.
// For each declared identifier:
//   - If it is a basic type or identifier in the universe, mangle it to its
//     corresponding Go identifier and stop here.
//   - If it is the main function, mangle it to its corresponding Go identifier
//     and stop here.
//   - If it is an unused variable (not struct property or function parameter),
//     mangle it as "_" so that it doesn't generate unused error, and stop here.
//   Otherwise, go through all of the following that apply, where `$` stands for the name
//   mangling separator:
//   - If it is any top-level identifier, a struct, a struct variable or a struct
//     method, prefix it as `_$` or `X$` (unexported or exported)
//   - If it is a non-top-level struct, add the ID of the scope to the prefix,
//     followed with `$`
//   - Append the original identifier
//   - If at this point the resulting identifier is a Go reserved keyword,
//     prefix it with `$`
//
func mangle(unit *semantic.Unit) *nameResolver {
	ruses := make(map[semantic.Decl]bool, len(unit.ValueUses))
	for _, decl := range unit.ValueUses {
		ruses[decl] = true
	}

	semantic.Inspect(m.unit, func(n semantic.Node) bool {
	names := make(map[semantic.Decl]string)
	semantic.Inspect(unit, func(n semantic.Node) bool {
		// first, process the generic cases for all decls
		if decl, ok := n.(semantic.Decl); ok {
			// if the scope is Universe and the type is BasicType, mangle as its
			// corresponding Go basic type or identifier.
			if decl.Scope().IsUniverse() {
				if _, ok := decl.Type().(*semantic.BasicType); ok {
					names[decl] = basicIdentsToGo[decl.Ident()]
					return true
				}
			}

			// if this is the main function, mangle it as "main" and that's it.
			if decl == unit.Main {
				names[decl] = basicIdentsToGo[decl.Ident()]
				return true
			}
		}

		switch n := n.(type) {
		case semantic.Decl:
			// if *Fn and external, special mangling for extern params
		case *semantic.Ident:
		case *semantic.Fn:
			var b fmtBuilder
			if n.Scope().IsTopLevel() || n.MethodOf != nil {
				b.withExport(false)
			}
			names[n] = b.get(n.Ident())
			// TODO: if *Fn and external, special mangling for extern params? or not?

		case *semantic.Var:
			// if this is a variable (not a struct field nor a function parameter)
			// and its value is unused, mangle as "_".
			if !ruses[n] && n.ParamOf == nil && n.PropOf == nil {
				// TODO: unless public/exported
				names[n] = "_"
				break
			}

			var b fmtBuilder
			if n.Scope().IsTopLevel() || n.PropOf != nil {
				b.withExport(false)
			}
			names[n] = b.get(n.Ident())

		case *semantic.Struct:
			var b fmtBuilder
			b.withExport(false)
			if !n.Scope().IsTopLevel() {
				b.withScope(n.Scope())
			}
			names[n] = b.get(n.Ident())
		}
		return true
	})

	return &nameResolver{names: names}
}

/*
// mangledNameFor returns the Go name used for the corresponding Snow declared
// identifier. It is properly prefixed according to its exported or not state.
func mangledNameFor(mangling map[typecheck.Object]string, obj typecheck.Object) string {
	if s := mangling[obj]; s != "" {
		return s
	}
	ident := obj.IdentNode()
	return ident.Name
type nameResolver struct {
	names map[semantic.Decl]string
}

// mangledNameForExternParam returns the Go name for an external function's
// parameter name, so it only has to be unique for that function's body, and it
// cannot be exported.
func mangledNameForExternParam(index int) string {
	return "_" + strconv.Itoa(index)
func (r *nameResolver) NameForDecl(n semantic.Decl) string {
	nm := r.names[n]
	if nm == "" {
		panic(fmt.Sprintf("no name recorded for %s", semantic.TypeIdentString(n)))
	}
	return nm
}

// mangledNameForTuple returns the Go name for a tuple's field, which is
// generated as an anonymous struct in Go. It only has to be unique within that
// struct, and it has the right prefix should it be exported.
func mangledNameForTuple(index int, exported bool) string {
	var buf strings.Builder
	mangleWriteExported(&buf, exported)
	buf.WriteString(strconv.Itoa(index))
	return buf.String()
func (r *nameResolver) NameForTuple(index int, exported bool) string {
	var b fmtBuilder
	b.withExport(exported)
	return b.get(strconv.Itoa(index))
}

/*
// mangledNameForConversionHelper returns the Go name for an automatically-
// generated function to convert tuples for type-reconciliation. As it is not
// an identifier in the Snow code, care must be taken to avoid conflicts, so


@@ 78,131 134,53 @@ func mangledNameForStructInit(mangledStructName string) string {
	buf.WriteString("init")
	return buf.String()
}
*/

// mangleIdents mangles all identifiers declared in decls. The logic is as follows.
// For each declared identifier:
//   - If it is unused, mangle it as "_" so that it doesn't generate unused error, and
//     stop here.
//   Otherwise, follow all of the following that apply, where `$` stands for the name
//   mangling separator:
//   - If it is any top-level identifier, a struct, a struct variable or a struct
//     method, prefix it as `_$` or `X$` (unexported or exported) - with the exception
//     of the main function in the top-level.
//   - If it is a struct, add the ID of the scope to the prefix, followed with `$`
//   - Append the original identifier
//   - If at this point the resulting identifier is a Go reserved keyword,
//     prefix it with `$`
//
// Although not part of mangling per se, function parameters of external functions (functions
// without a body) do not use the snow identifiers, they use _0, _1, ... _n instead. Those
// identifiers cannot be referenced by snow code anyway, and the original parameter names
// are not declared in any scope.
func mangleIdents(mangling map[typecheck.Object]string, decls, vuses map[*ast.Ident]typecheck.Object) {
	reversedUses := make(map[typecheck.Object]bool, len(decls))
	// TODO: if object is public/exported and top-level, do not mangle as "_" if unused
	for _, obj := range vuses {
		reversedUses[obj] = true
	}

	var buf strings.Builder
	for id, obj := range decls {
		scope := obj.ParentScope()
		name := id.Name

		if scope.IsUniverse() {
			mangleUniverse(mangling, obj)
			continue
		}

		buf.Reset()
		var (
			tno      *typecheck.TypeNameObj
			isStruct bool
		)
		if typecheck.AsObj(obj, &tno) {
			var sd *ast.StructDecl
			isStruct = typecheck.AsNode(tno.Node, &sd)
		}
		var fno *typecheck.FnObj
		isFn := typecheck.AsObj(obj, &fno)

		if !isStruct && !reversedUses[obj] {
			if scope.IsTopLevel() && isFn && name == typecheck.MainFnName {
				// exception: main function in top-level
			} else {
				// unused declaration, mangle as "_"
				mangling[obj] = "_"
				continue
			}
		}

		var sd *ast.StructDecl
		isStructBody := typecheck.AsNode(scope.Node, &sd)
		if scope.IsTopLevel() || isStruct || isStructBody {
			if scope.IsTopLevel() && isFn && name == typecheck.MainFnName {
				// exception: main function in top-level
			} else {
				mangleWriteExported(&buf, false)
			}
		}
		if !scope.IsTopLevel() && isStruct {
			// no need to add scope label if struct is at top level
			mangleWriteScope(&buf, scope)
		}
		if buf.Len() == 0 && isGoKeyword(name) {
			mangleWriteEscKeyword(&buf)
		}
		if buf.Len() > 0 {
			buf.WriteString(name)
			mangling[obj] = buf.String()
		}
	}
type fmtBuilder struct {
	name strings.Builder
}

func mangleWriteExported(buf *strings.Builder, exported bool) {
	first := "_"
func (b *fmtBuilder) withExport(exported bool) {
	if exported {
		first = "X"
		b.name.WriteByte('X')
	} else {
		b.name.WriteByte('_')
	}
	buf.WriteString(first)
	buf.WriteRune(scanner.NameManglingSeparator)
	b.name.WriteRune(scanner.NameManglingSeparator)
}

func mangleWriteScope(buf *strings.Builder, scope *typecheck.Scope) {
	buf.WriteString(strings.ReplaceAll(scope.Label, ".", "_"))
	buf.WriteRune(scanner.NameManglingSeparator)
func (b *fmtBuilder) withScope(s *semantic.Scope) {
	b.name.WriteString(strconv.Itoa(s.ID))
	b.name.WriteRune(scanner.NameManglingSeparator)
}

func mangleWriteEscKeyword(buf *strings.Builder) {
	buf.WriteRune(scanner.NameManglingSeparator)
}

func mangleUniverse(mangling map[typecheck.Object]string, obj typecheck.Object) {
	switch obj := obj.(type) {
	case *typecheck.TypeNameObj:
		if mangled := basicKindToGo[obj.Basic]; mangled != "" {
			mangling[obj] = mangled
		}
func (b *fmtBuilder) get(nm string) string {
	if b.name.Len() == 0 && goKeywords[nm] {
		b.name.WriteRune(scanner.NameManglingSeparator)
	}
	b.name.WriteString(nm)
	return b.name.String()
}
*/

var basicKindToGo = map[semantic.BasicKind]string{
	semantic.Bool:   "bool",
	semantic.Int:    "int",
	semantic.I8:     "int8",
	semantic.I16:    "int16",
	semantic.I32:    "int32",
	semantic.I64:    "int64",
	semantic.Uint:   "uint",
	semantic.U8:     "uint8",
	semantic.U16:    "uint16",
	semantic.U32:    "uint32",
	semantic.U64:    "uint64",
	semantic.Float:  "float64",
	semantic.F32:    "float32",
	semantic.F64:    "float64",
	semantic.String: "string",
var basicIdentsToGo = map[string]string{
	semantic.Bool.String():   "bool",
	semantic.Int.String():    "int",
	semantic.I8.String():     "int8",
	semantic.I16.String():    "int16",
	semantic.I32.String():    "int32",
	semantic.I64.String():    "int64",
	semantic.Uint.String():   "uint",
	semantic.U8.String():     "uint8",
	semantic.U16.String():    "uint16",
	semantic.U32.String():    "uint32",
	semantic.U64.String():    "uint64",
	semantic.Float.String():  "float64",
	semantic.F32.String():    "float32",
	semantic.F64.String():    "float64",
	semantic.String.String(): "string",
	semantic.TrueLitName:     "true",
	semantic.FalseLitName:    "false",
	semantic.MainFnName:      "main",
}

var goKeywords = map[string]bool{

M pkg/semantic/scope.go => pkg/semantic/scope.go +6 -1
@@ 41,6 41,9 @@ func NewUniverse(owner *Unit) *Scope {
	s.Parent = s
	defer func() { s.Parent = nil }()

	// TODO: not super happy with how the pre-declared universe identifiers
	// are handled, refactor this...

	for id, decl := range universeIdents {
		vv := decl.(*Var)
		vv.scope = s


@@ 49,6 52,8 @@ func NewUniverse(owner *Unit) *Scope {

	// add the @extern attribute as a struct - added here as it needs
	// to be able to create its child scope.
	// TODO: nothing prevents using this struct in non-attribute situations.
	// Is that ok? If so it should get codegen'd.
	varNms := []string{"import", "pkg", "symbol"}
	str := &Struct{Vars: make([]*Var, len(varNms))}
	str.ident = ExternAttrName


@@ 217,7 222,7 @@ var universeIdents = func() map[string]Decl {
	}

	// add true and false constants
	for _, id := range []string{"true", "false"} {
	for _, id := range []string{TrueLitName, FalseLitName} {
		v := &Var{Ctx: Immutable}
		v.typ = &BasicType{Kind: Bool}
		v.ident = id

M pkg/semantic/semantic.go => pkg/semantic/semantic.go +27 -2
@@ 1,11 1,17 @@
package semantic

import "git.sr.ht/~mna/snow/pkg/token"
import (
	"fmt"

	"git.sr.ht/~mna/snow/pkg/token"
)

const (
	SelfVarName    = "self"
	MainFnName     = "main"
	ExternAttrName = "extern"
	TrueLitName    = "true"
	FalseLitName   = "false"
)

// Node is the common interface for the abstract semantic graph. Unlike the


@@ 127,7 133,8 @@ type Var struct {
	Ctx      TypeContext // only mutable or immutable for Var, set during translation pass
	TypeExpr Expr        // possibly nil, not a resolved Type, but the Expr assigned to var in `var x: int`
	Value    Expr        // possibly nil
	PropOf   *Struct     // set during translation pass
	PropOf   *Struct     // set during translation pass if the Var is a Struct property
	ParamOf  *Fn         // set during translation pass if the Var is a function parameter
	commonDecl
}



@@ 287,3 294,21 @@ type ImplicitConv struct {
	Value Expr
	commonExpr
}

// TypeIdentString returns a string representation of n where the type is
// printed as well as the identifier if it is a Decl or an *Ident.
func TypeIdentString(n Node) string {
	var lbl string
	switch n := n.(type) {
	case Decl:
		lbl = n.Ident()
	case *Ident:
		lbl = n.Name
	case *Selector:
		lbl = "_." + n.Sel.Name
	}
	if lbl == "" {
		return fmt.Sprintf("%T", n)
	}
	return fmt.Sprintf("%T %s", n, lbl)
}

M pkg/semantic/translate_pass.go => pkg/semantic/translate_pass.go +3 -1
@@ 112,7 112,9 @@ func (t *translateVisitor) Visit(n ast.Node) ast.Visitor {
		}
		for i, param := range n.Signature.Params {
			ast.Walk(tt, param)
			fn.Params[i] = tt.generated.(*Var)
			v := tt.generated.(*Var)
			v.ParamOf = &fn
			fn.Params[i] = v
		}
		if n.Body != nil {
			ast.Walk(tt, n.Body)

M pkg/semantic/translate_pass_test.go => pkg/semantic/translate_pass_test.go +1 -13
@@ 3,7 3,6 @@ package semantic_test
import (
	"bytes"
	"flag"
	"fmt"
	"path/filepath"
	"testing"



@@ 57,7 56,7 @@ func TestScopes(t *testing.T) {
				}
				if n.Scope() == nil {
					lpos := unit.FileSet.Position(n.Pos())
					t.Fatalf("%s: no scope for %s", lpos, bestLabelFor(n))
					t.Fatalf("%s: no scope for %s", lpos, semantic.TypeIdentString(n))
				}
				return hasScope
			}


@@ 71,14 70,3 @@ type visitorFunc func(semantic.Node) semantic.Visitor
func (v visitorFunc) Visit(n semantic.Node) semantic.Visitor {
	return v(n)
}

func bestLabelFor(n semantic.Node) string {
	switch n := n.(type) {
	case semantic.Decl:
		return n.Ident()
	case *semantic.Ident:
		return n.Name
	default:
		return fmt.Sprintf("%T", n)
	}
}

M pkg/semantic/typeassign_pass_test.go => pkg/semantic/typeassign_pass_test.go +1 -1
@@ 50,7 50,7 @@ func TestTypes(t *testing.T) {
				case semantic.Typed:
					if n.Type() == nil {
						lpos := unit.FileSet.Position(n.Pos())
						t.Fatalf("%s: no type for %s", lpos, bestLabelFor(n))
						t.Fatalf("%s: no type for %s", lpos, semantic.TypeIdentString(n))
					}
					if id, ok := n.(*semantic.Ident); ok && id.Type().Valid() && id.Index < 0 {
						if id.Ref == nil {