Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
cgp*
*.ll
*.o

venv

tests
!tests/*.choc

Output
.lit_test_times.txt
tests-partial
*.ignore
venv
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
.PHONY: test, compile, clean

test: clean cgp compile
test: clean compile
lit -v tests

cgp:
go build -tags=llvm18 -o cgp

compile:
find tests -type f -name "*.choc" -exec ./cgp -c {} \; >/dev/null 2>&1
compile: cgp
find tests -type f -name "*.choc" -exec ./cgp {} \; >/dev/null 2>&1

clean:
find tests -type f -name "*.ll" -delete
find tests -type f ! \( -name "*.choc" -o -name "lit.cfg" \) -delete
rm -f cgp
22 changes: 14 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package main

import (
"log"
"os"
"os/exec"
"path/filepath"
"strings"

"chogopy/pkg/backend"
"chogopy/pkg/codegen"
"chogopy/pkg/lexer"
"chogopy/pkg/parser"
"chogopy/pkg/scopes"
"chogopy/pkg/typechecks"
"log"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/kr/pretty"
"tinygo.org/x/go-llvm"
Expand Down Expand Up @@ -74,9 +75,9 @@ func main() {
// TODO: To keep the test cases working I am only appending .ll to the filePath
// here but will have to change that in the future and modify the test cases accordingly.
err := os.WriteFile(
filePath+".ll",
replaceFileEnding(filePath, "ll"),
[]byte(codeGenerator.Module.String()),
0644,
0o644,
)
if err != nil {
panic(err)
Expand All @@ -96,7 +97,7 @@ func main() {
err := os.WriteFile(
llFilePath,
[]byte(codeGenerator.Module.String()),
0644,
0o644,
)
if err != nil {
log.Fatalln("Failed to create llvm IR file: ", err)
Expand Down Expand Up @@ -134,6 +135,11 @@ func main() {
log.Fatalln("Failed to link object file: ", err)
}

// TODO: The below should be specifiable with an argument.
// Move the output file into the same directory as the source code
outputPath := filepath.Join(filepath.Dir(filePath), outputFile)
os.Rename(outputFile, outputPath)

os.Remove(objectFilePath)
}
}
Expand Down
29 changes: 28 additions & 1 deletion pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
package codegen

import (
"chogopy/pkg/ast"
"fmt"
"strconv"
"strings"

"chogopy/pkg/ast"

"github.com/llir/llvm/ir"
"github.com/llir/llvm/ir/constant"
Expand All @@ -28,6 +30,7 @@ type VarInfo struct {
name string
elemType types.Type
value value.Value
init constant.Constant
}

type (
Expand All @@ -47,6 +50,7 @@ type CodeGenerator struct {
functions Functions

varContext VarCtx
heapAllocs []value.Value

mainFunction *ir.Func
mainBlock *ir.Block
Expand All @@ -71,6 +75,7 @@ func (cg *CodeGenerator) Generate(program *ast.Program) {
cg.registerFuncs()

cg.varContext = VarCtx{}
cg.heapAllocs = []value.Value{}

cg.mainFunction = cg.Module.NewFunc("main", types.I32)
cg.mainBlock = cg.mainFunction.NewBlock(cg.uniqueNames.get("entry"))
Expand All @@ -92,6 +97,9 @@ func (cg *CodeGenerator) Generate(program *ast.Program) {
statement.Visit(cg)
}

// Add a free() for each call to malloc() at the end of the main function
cg.freeHeap()

cg.currentBlock.NewRet(constant.NewInt(types.I32, 0))
}

Expand Down Expand Up @@ -147,3 +155,22 @@ func (cg *CodeGenerator) setVar(varInfo VarInfo) {
localVars[varInfo.name] = varInfo
}
}

func (cg *CodeGenerator) freeHeap() {
for _, ptr := range cg.heapAllocs {
// First, we need to check whether the pointer SSA value is even defined in the current scope.
// This is relevant when a pointer is heap-allocated and then returned from a function,
// which results in the pointer SSA value being shadowed by the function return SSA value.
inScope := false
for _, inst := range cg.currentBlock.Insts {
if strings.Contains(inst.LLString(), ptr.Ident()) {
inScope = true
break
}
}

if inScope {
cg.currentBlock.NewCall(cg.functions["free"], ptr)
}
}
}
26 changes: 23 additions & 3 deletions pkg/codegen/defvar.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package codegen

import (
"chogopy/pkg/ast"
"strings"

"chogopy/pkg/ast"

"github.com/llir/llvm/ir/constant"
"github.com/llir/llvm/ir/types"
)
Expand All @@ -12,18 +13,37 @@ func (cg *CodeGenerator) VisitVarDef(varDef *ast.VarDef) {
varName := varDef.TypedVar.(*ast.TypedVar).VarName
literalConst := cg.getLiteralConst(varDef)

// TODO: double free for function returning global pointer to static?
switch cg.currentFunction {
case cg.mainFunction:
globalVar := cg.Module.NewGlobalDef(varName, literalConst)
cg.setVar(
VarInfo{name: varName, elemType: globalVar.Typ.ElemType, value: globalVar},
VarInfo{name: varName, elemType: globalVar.Typ.ElemType, value: globalVar, init: literalConst},
)

default:
localVar := cg.currentBlock.NewAlloca(literalConst.Type())
localVar.LocalName = cg.uniqueNames.get("local_var")
cg.currentBlock.NewStore(literalConst, localVar)

// Move string literal to heap
if literalConst.Type().Equal(types.I8Ptr) {
// Store static string into stack-allocated string ptr
strStack := cg.currentBlock.NewLoad(types.I8Ptr, localVar)
strStack.LocalName = cg.uniqueNames.get("str_stack")

// Copy string into heap-allocated string ptr
strLiteral := varDef.Literal.(*ast.LiteralExpr).Value.(string)
strHeap := cg.currentBlock.NewCall(cg.functions["malloc"], constant.NewInt(types.I32, int64(len(strLiteral)+1)))
strHeap.LocalName = cg.uniqueNames.get("str_heap")
cg.heapAllocs = append(cg.heapAllocs, strHeap)
strCopy := cg.currentBlock.NewCall(cg.functions["sprintf"], strHeap, cg.strings["str_format"], strStack)
strCopy.LocalName = cg.uniqueNames.get("strcpy_res")
cg.currentBlock.NewStore(strHeap, localVar)
}

cg.setVar(
VarInfo{name: varName, elemType: localVar.Typ.ElemType, value: localVar},
VarInfo{name: varName, elemType: localVar.Typ.ElemType, value: localVar, init: literalConst},
)
}
}
Expand Down
18 changes: 18 additions & 0 deletions pkg/codegen/exprbinary.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,21 @@ func (cg *CodeGenerator) concat(binaryExpr *ast.BinaryExpr, lhs value.Value, rhs

return false
}

// func (cg CodeGenerator) getStrLiteral(node ast.Node) string {
// if isIdentOrIndex(node) {
// varInfo, _ := cg.getVar(node.(*ast.IdentExpr).Identifier)
// initConst := varInfo.init.(*constant.ExprGetElementPtr)
// charArr := initConst.Src.(*ir.Global).Init.(*constant.CharArray).X
// strLiteral := string(charArr[:len(charArr)-1]) // Remove '/0' from the char array
// return strLiteral
//
// } else {
// return node.(*ast.LiteralExpr).Value.(string)
// }
// }

// lhsString := cg.getStrLiteral(binaryExpr.Lhs)
// rhsString := cg.getStrLiteral(binaryExpr.Rhs)
//
// cg.lastGenerated = cg.concatStrings(lhsString, rhsString)
10 changes: 9 additions & 1 deletion pkg/codegen/exprcall.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package codegen

import (
"chogopy/pkg/ast"
"log"

"chogopy/pkg/ast"

"github.com/llir/llvm/ir"
"github.com/llir/llvm/ir/types"
"github.com/llir/llvm/ir/value"
)
Expand All @@ -22,10 +24,12 @@ func (cg *CodeGenerator) VisitCallExpr(callExpr *ast.CallExpr) {
switch callExpr.FuncName {
case "len":
lenRes := cg.getLen(args[0])
lenRes.(*ir.InstCall).LocalName = cg.uniqueNames.get("call_res")
cg.lastGenerated = lenRes
return
case "print":
printRes := cg.printGeneric(args[0])
printRes.(*ir.InstCall).LocalName = cg.uniqueNames.get("call_res")
cg.lastGenerated = printRes
return
}
Expand All @@ -34,6 +38,10 @@ func (cg *CodeGenerator) VisitCallExpr(callExpr *ast.CallExpr) {
callRes := cg.currentBlock.NewCall(callee, args...)
callRes.LocalName = cg.uniqueNames.get("call_res")

if _, ok := callRes.Type().(*types.PointerType); ok {
cg.heapAllocs = append(cg.heapAllocs, callRes)
}

cg.lastGenerated = callRes
}

Expand Down
77 changes: 38 additions & 39 deletions pkg/codegen/exprlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,10 @@ import (
)

func (cg *CodeGenerator) VisitListExpr(listExpr *ast.ListExpr) {
listPtr := cg.newStaticList(listExpr)

listPtr := cg.newDynamicList(listExpr)
cg.lastGenerated = listPtr
}

// newDynamicList allocates memory for a list expression on the currently
// executing functions' call stack and returns a pointer to this memory.
// This method should be used carefully because it may lead to dangling pointers
// if a function returns a list expression allocated in this way.
func (cg *CodeGenerator) newDynamicList(listExpr *ast.ListExpr) value.Value {
// attrToType will return something like:
//
// list{content: i32*, size: i32, init: i1}*
//
//
// So we want to take out the elemType:
//
// list{content: i32*, size: i32, init: i1}
//
//
// In order for the allocation (listPtr := cg.newList(...)) to have the correct type:
//
// list{content: i32*, size: i32, init: i1}*
listType := cg.attrToType(listExpr.TypeHint).(*types.PointerType).ElemType

listElems := []value.Value{}
for _, elem := range listExpr.Elements {
elem.Visit(cg)
elemVal := cg.lastGenerated

if isIdentOrIndex(elem) {
elemVal = cg.LoadVal(elemVal)
}

listElems = append(listElems, elemVal)
}

listPtr := cg.newList(listElems, listType)

return listPtr
}

// newConstantList assumes that all list literals will contain nothing but
// literal expressions like: [1,2,3] or ["a","b","c"] as opposed to: [var1,var2,var3] or [v[0],v[1],v[3]]
// This is because it will allocate list literals statically at compile time (using global definitions)
Expand Down Expand Up @@ -124,3 +86,40 @@ func (cg *CodeGenerator) getStaticListElems(listExpr *ast.ListExpr) []constant.C

return listElems
}

// newDynamicList allocates memory for a list expression on the currently
// executing functions' call stack and returns a pointer to this memory.
// This method should be used carefully because it may lead to dangling pointers
// if a function returns a list expression allocated in this way.
func (cg *CodeGenerator) newDynamicList(listExpr *ast.ListExpr) value.Value {
// attrToType will return something like:
//
// list{content: i32*, size: i32, init: i1}*
//
//
// So we want to take out the elemType:
//
// list{content: i32*, size: i32, init: i1}
//
//
// In order for the allocation (listPtr := cg.newList(...)) to have the correct type:
//
// list{content: i32*, size: i32, init: i1}*
listType := cg.attrToType(listExpr.TypeHint).(*types.PointerType).ElemType

listElems := []value.Value{}
for _, elem := range listExpr.Elements {
elem.Visit(cg)
elemVal := cg.lastGenerated

if isIdentOrIndex(elem) {
elemVal = cg.LoadVal(elemVal)
}

listElems = append(listElems, elemVal)
}

listPtr := cg.newList(listElems, listType)

return listPtr
}
Loading