// toIrType converts the given uC type to the corresponding LLVM IR type. func toIrType(n uctypes.Type) irtypes.Type { //TODO: implement, placeholder implementation var t irtypes.Type var err error switch ucType := n.(type) { case *uctypes.Basic: switch ucType.Kind { case uctypes.Int: //TODO: Get int width from compile env t, err = irtypes.NewInt(32) case uctypes.Char: t, err = irtypes.NewInt(8) case uctypes.Void: t = irtypes.NewVoid() } case *uctypes.Array: elem := toIrType(ucType.Elem) if ucType.Len == 0 { t, err = irtypes.NewPointer(elem) } else { t, err = irtypes.NewArray(elem, ucType.Len) } case *uctypes.Func: var params []*irtypes.Param variadic := false for _, p := range ucType.Params { //TODO: Add support for variadic if uctypes.IsVoid(p.Type) { break } pt := toIrType(p.Type) dbg.Printf("converting type %#v to %#v", p.Type, pt) params = append(params, irtypes.NewParam(pt, p.Name)) } result := toIrType(ucType.Result) t, err = irtypes.NewFunc(result, params, variadic) default: panic(fmt.Sprintf("support for translating type %T not yet implemented.", ucType)) } if err != nil { panic(errutil.Err(err)) } if t == nil { panic(errutil.Newf("Conversion failed: %#v", n)) } return t }
// check type-checks the given file. func check(file *ast.File, exprTypes map[ast.Expr]types.Type) error { // funcs is a stack of function declarations, where the top-most entry // represents the currently active function. var funcs []*types.Func // check type-checks the given node. check := func(n ast.Node) error { switch n := n.(type) { case *ast.BlockStmt: // Verify that array declarations have an explicit size or an // initializer. for _, item := range n.Items { switch item := item.(type) { case *ast.VarDecl: typ := item.Type() if typ, ok := typ.(*types.Array); ok { if typ.Len == 0 && item.Val == nil { return errors.Newf(item.VarName.NamePos, "array size or initializer missing for %q", item.VarName) } } } } case *ast.VarDecl: typ := n.Type() // TODO: Evaluate if the type-checking of identifiers could be made // more generic. Consider if a new variable declaration type was added // alongside of ast.VarDecl, e.g. ast.Field. Then the type-checking // code would have to be duplicated to ast.Field. A more generic // approach that may be worth exploring is to do the type-checking // directly on *ast.Ident. A naive implementation has been attempted // using *ast.Ident, which failed since "void" refers to itself as a // VarDecl, whos types is "void". if n.VarName != nil && types.IsVoid(typ) { return errors.Newf(n.VarName.NamePos, `%q has invalid type "void"`, n.VarName) } if typ, ok := typ.(*types.Array); ok { if types.IsVoid(typ.Elem) { return errors.Newf(n.VarName.NamePos, `invalid element type "void" of array %q`, n.VarName) } } case *ast.FuncDecl: if astutil.IsDef(n) { // push function declaration. funcs = append(funcs, n.Type().(*types.Func)) // Verify that parameter names are not obmitted in function // definitions. for _, param := range n.FuncType.Params { if !types.IsVoid(param.Type()) && param.VarName == nil { return errors.Newf(param.VarType.Start(), "parameter name obmitted") } } // Verify that non-void functions end with return statement. if !types.IsVoid(n.Type().(*types.Func).Result) { // endsWithReturn reports whether the given block item ends with // a return statement. The following two terminating statements // are supported, recursively. // // return; // // if (cond) { // return; // } else { // return; // } var endsWithReturn func(ast.Node) bool endsWithReturn = func(node ast.Node) bool { last := node if block, ok := node.(*ast.BlockStmt); ok { items := block.Items if len(items) == 0 { // node ends without a return statement. return false } last = items[len(items)-1] } switch last := last.(type) { case *ast.ReturnStmt: // node ends with a return statement. return true case *ast.IfStmt: if last.Else == nil { // node may ends without a return statement if the else- // branch is invoked. return false } return endsWithReturn(last.Body) && endsWithReturn(last.Else) default: // node may end without return statement. return false } } missing := !endsWithReturn(n.Body) // Missing return statements are valid from the main function. // // NOTE: "reaching the } that terminates the main function // returns a value of 0." (see §5.1.2.2.3 in the C11 spec) if missing && n.FuncName.String() != "main" { return errors.Newf(n.Body.Rbrace, "missing return at end of non-void function %q", n.FuncName) } } } case *ast.ReturnStmt: // Verify result type. curFunc := funcs[len(funcs)-1] var resultType types.Type resultType = &types.Basic{Kind: types.Void} if n.Result != nil { resultType = exprTypes[n.Result] } if !isCompatible(resultType, curFunc.Result) { resultPos := n.Start() if n.Result != nil { resultPos = n.Result.Start() } return errors.Newf(resultPos, "returning %q from a function with incompatible result type %q", resultType, curFunc.Result) } case *ast.CallExpr: funcType, ok := n.Name.Decl.Type().(*types.Func) if !ok { return errors.Newf(n.Lparen, "cannot call non-function %q of type %q", n.Name, funcType) } // TODO: Implement support for functions with variable arguments (i.e. // ellipsis). // Verify call without arguments. if len(n.Args) == 0 && len(funcType.Params) == 1 && types.IsVoid(funcType.Params[0].Type) { return nil } // Check number of arguments. if len(n.Args) < len(funcType.Params) { return errors.Newf(n.Lparen, "calling %q with too few arguments; expected %d, got %d", n.Name, len(funcType.Params), len(n.Args)) } if len(n.Args) > len(funcType.Params) { return errors.Newf(n.Lparen, "calling %q with too many arguments; expected %d, got %d", n.Name, len(funcType.Params), len(n.Args)) } // Check that call argument types match the function parameter types. for i, param := range funcType.Params { arg := n.Args[i] argType := exprTypes[arg] paramType := param.Type if !isCompatibleArg(argType, paramType) { return errors.Newf(arg.Start(), "calling %q with incompatible argument type %q to parameter of type %q", n.Name, argType, paramType) } } case *ast.FuncType: for _, param := range n.Params { paramType := param.Type() if len(n.Params) > 1 && types.IsVoid(paramType) { return errors.Newf(n.Lparen, `"void" must be the only parameter`) } } case *ast.IndexExpr: indexType, ok := exprTypes[n.Index] if !ok { panic(fmt.Sprintf("unable to locate type of expression %v", n.Index)) } if !types.IsInteger(indexType) { return errors.Newf(n.Index.Start(), "invalid array index; expected integer, got %q", indexType) } default: // TODO: Implement type-checking for remaining node types. //log.Printf("not type-checked: %T\n", n) } return nil } // after reverts to the outer function after traversing function definitions. after := func(n ast.Node) error { switch n := n.(type) { case *ast.FuncDecl: if astutil.IsDef(n) { // pop function declaration. funcs = funcs[:len(funcs)-1] } } return nil } // Walk the AST of the given file to perform type-checking. if err := astutil.WalkBeforeAfter(file, check, after); err != nil { return errutil.Err(err) } return nil }
// typeOf returns the type of the given expression. func typeOf(n ast.Expr) (types.Type, error) { switch n := n.(type) { case *ast.BasicLit: // "The type of an integer constant is the first of the corresponding // list in which its value can be represented." [C99 draft 6.4.4.1.5] switch n.Kind { case token.CharLit: // "An integer character constant has type int." [C99 draft 6.4.4.4.10] return &types.Basic{Kind: types.Int}, nil case token.IntLit: return &types.Basic{Kind: types.Int}, nil default: panic(fmt.Sprintf("support for basic literal type %v not yet implemented", n.Kind)) } case *ast.BinaryExpr: // See [C99 draft 6.3.1.8 Usual arithmetic conversions] xType, err := typeOf(n.X) if err != nil { return nil, errutil.Err(err) } yType, err := typeOf(n.Y) if err != nil { return nil, errutil.Err(err) } if n.Op == token.Assign { if !isAssignable(n.X) { return nil, errors.Newf(n.OpPos, "cannot assign to %q of type %q", n.X, xType) } if !isCompatible(xType, yType) { return nil, errors.Newf(n.OpPos, "cannot assign to %q (type mismatch between %q and %q)", n.X, xType, yType) } // TODO: !types.Equal(higherPrecision(xType, yType), xType) could be // used for loss of percision warning. return xType, nil } if types.IsVoid(xType) || types.IsVoid(yType) { return nil, errors.Newf(n.OpPos, "invalid operands to binary expression: %v (%q and %q)", n, xType, yType) } if !isCompatible(xType, yType) { return nil, errors.Newf(n.OpPos, "invalid operation: %v (type mismatch between %q and %q)", n, xType, yType) } // TODO: Implement better implicit conversion. Future: Make sure to // promote types early when implementing signed/unsigned types and // types need to be promoted anyway later. Be careful of bug: // https://youtu.be/Ux0YnVEaI6A?t=279 // Implement according to [C99 draft 6.3.1.8 Usual arithmetic conversions] return higherPrecision(xType, yType), nil case *ast.CallExpr: typ := n.Name.Decl.Type() if typ, ok := typ.(*types.Func); ok { return typ.Result, nil } return nil, errors.Newf(n.Lparen, "cannot call non-function %q of type %q", n.Name, typ) case *ast.Ident: return n.Decl.Type(), nil case *ast.IndexExpr: typ := n.Name.Decl.Type() if typ, ok := typ.(*types.Array); ok { return typ.Elem, nil } return nil, errors.Newf(n.Lbracket, "invalid operation: %v (type %q does not support indexing)", n, typ) case *ast.ParenExpr: return typeOf(n.X) case *ast.UnaryExpr: // TODO: Add support for pointers. return typeOf(n.X) default: panic(fmt.Sprintf("support for type %T not yet implemented.", n)) } }