From 3a1d9e20c2a9af602c3e0e51bc5e7821d5480644 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 16 Nov 2023 22:04:56 -0500 Subject: [PATCH] Add type generator --- generator/generator.go | 61 +++++++++++++++++++-- generator/test-common.go | 45 +++++++++++++++ generator/type-multiplex.go | 43 +++++++++++++++ generator/type.go | 106 ++++++++++++++++++++++++++++++++++++ generator/type_test.go | 52 ++++++++++++++++++ 5 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 generator/test-common.go create mode 100644 generator/type-multiplex.go create mode 100644 generator/type.go create mode 100644 generator/type_test.go diff --git a/generator/generator.go b/generator/generator.go index dc34a66..1b8d590 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -1,11 +1,34 @@ package generator import "io" +import "fmt" +import "sort" +import "errors" import "github.com/llir/llvm/ir" import "github.com/llir/llvm/ir/types" import "git.tebibyte.media/sashakoshka/fspl/analyzer" +// Target contains information about the machine the code is being written for. +type Target struct { + // WordSize is the size of the machine word. This determines the width + // of the Word type. + WordSize uint64 + + // Arch specifies the machine architecture + Arch string +} + +// NativeTarget returns a target describing the current system. +func NativeTarget () Target { + // FIXME + return Target { + WordSize: 64, + Arch: "x86", + } +} + type generator struct { + target Target tree analyzer.Tree output io.Writer module ir.Module @@ -14,26 +37,41 @@ type generator struct { // Generate takes in a semantic tree and writes corresponding LLVM IR to the // given io.Writer. It returns an error in case there is something wrong with // the semantic tree that prevents the code generation process from occurring. -func Generate (tree analyzer.Tree, output io.Writer) error { +func (this Target) Generate (tree analyzer.Tree, output io.Writer) error { return (&generator { + target: this, tree: tree, output: output, }).generate() } func (this *generator) generate () error { - for name, ty := range this.tree.Types { - + typedefs := sortMapKeys(this.tree.Types) + for _, key := range typedefs { + _, err := this.typedef(key) + if err != nil { return err } } + + // TODO functions + + // TODO methods + + _, err := this.module.WriteTo(this.output) + return err } -func (this *generator) ty (typeName string) types.Type { +func (this *generator) typedef (typeName string) (types.Type, error) { for _, ty := range this.module.TypeDefs { if ty.Name() == typeName { - return ty + return ty, nil } } - return nil + def, exists := this.tree.Types[typeName] + if !exists { + return nil, + errors.New(fmt.Sprintln("type", typeName, "not found")) + } + return this.generateTypedef(def) } func (this *generator) method (typeName string, name string) *ir.Func { @@ -53,3 +91,14 @@ func (this *generator) function (name string) *ir.Func { } return nil } + +func sortMapKeys[T any] (unsorted map[string] T) []string { + keys := make([]string, len(unsorted)) + index := 0 + for key := range unsorted { + keys[index] = key + index ++ + } + sort.Strings(keys) + return keys +} diff --git a/generator/test-common.go b/generator/test-common.go new file mode 100644 index 0000000..f9ce141 --- /dev/null +++ b/generator/test-common.go @@ -0,0 +1,45 @@ +package generator + +import "io" +import "fmt" +import "testing" +import "strings" +import "git.tebibyte.media/sashakoshka/fspl/parser" +import "git.tebibyte.media/sashakoshka/fspl/analyzer" + +func testString (test *testing.T, correct string, input string) { + testReader(test, correct, strings.NewReader(input)) +} + +func testReader (test *testing.T, correct string, inputs ...io.Reader) { + ast := parser.Tree { } + for index, stream := range inputs { + err := ast.Parse(fmt.Sprintf("stream%d.fspl", index), stream) + if err != nil && err != io.EOF{ + test.Error("parser returned error: ", err) + return + } + } + + tree := analyzer.Tree { } + err := tree.Analyze(ast) + if err != nil { + test.Error("analyzer returned error: ", err) + return + } + + output := new(strings.Builder) + err = NativeTarget().Generate(tree, output) + if err != nil { + test.Error("generator returned error: ", err) + return + } + + got := output.String() + if got != correct { + test.Logf("results do not match") + test.Logf("got:\n%v", got) + test.Logf("correct:\n%v", correct) + test.Fail() + } +} diff --git a/generator/type-multiplex.go b/generator/type-multiplex.go new file mode 100644 index 0000000..3d52f22 --- /dev/null +++ b/generator/type-multiplex.go @@ -0,0 +1,43 @@ +package generator + +import "fmt" +import "github.com/llir/llvm/ir/types" +import "git.tebibyte.media/sashakoshka/fspl/entity" + +func (this *generator) generateTypedef ( + def *entity.Typedef, +) ( + types.Type, + error, +) { + irType, err := this.generateType(def.Type) + if err != nil { return nil, err } + return this.module.NewTypeDef(def.Name, irType), nil +} + +func (this *generator) generateType (ty entity.Type) (types.Type, error) { + switch ty.(type) { + case nil: + return types.Void, nil + case *entity.TypeNamed: + return this.generateTypeNamed(ty.(*entity.TypeNamed)) + case *entity.TypePointer: + return this.generateTypePointer(ty.(*entity.TypePointer)) + case *entity.TypeSlice: + return this.generateTypeSlice(ty.(*entity.TypeSlice)) + case *entity.TypeArray: + return this.generateTypeArray(ty.(*entity.TypeArray)) + case *entity.TypeStruct: + return this.generateTypeStruct(ty.(*entity.TypeStruct)) + case *entity.TypeInterface: + return this.generateTypeInerface(ty.(*entity.TypeInterface)) + case *entity.TypeInt: + return this.generateTypeInt(ty.(*entity.TypeInt)) + case *entity.TypeFloat: + return this.generateTypeFloat(ty.(*entity.TypeFloat)) + case *entity.TypeWord: + return this.generateTypeWord(ty.(*entity.TypeWord)) + default: + panic(fmt.Sprintln("generator doesn't know about type", ty)) + } +} diff --git a/generator/type.go b/generator/type.go new file mode 100644 index 0000000..2c8968a --- /dev/null +++ b/generator/type.go @@ -0,0 +1,106 @@ +package generator + +import "fmt" +import "errors" +import "github.com/llir/llvm/ir/types" +import "git.tebibyte.media/sashakoshka/fspl/entity" + +func (this *generator) generateTypeNamed (ty *entity.TypeNamed) (types.Type, error) { + return this.typedef(ty.Name) +} + +func (this *generator) generateTypePointer (ty *entity.TypePointer) (types.Type, error) { + return types.NewPointer(&types.VoidType { }), nil +} + +func (this *generator) generateTypeSlice (ty *entity.TypeSlice) (types.Type, error) { + return types.NewStruct ( + /* data */ types.NewPointer(types.Void), + /* len */ types.NewInt(this.target.WordSize)), nil +} + +func (this *generator) generateTypeArray (ty *entity.TypeArray) (types.Type, error) { + element, err := this.generateType(ty.Element) + if err != nil { return nil, err } + return types.NewArray(uint64(ty.Length), element), nil +} + +func (this *generator) generateTypeStruct (ty *entity.TypeStruct) (types.Type, error) { + irStruct := &types.StructType { + Fields: make([]types.Type, len(ty.Members)), + } + for index, member := range ty.Members { + field, err := this.generateType(member.Type()) + if err != nil { return nil, err } + irStruct.Fields[index] = field + } + return irStruct, nil +} + +func (this *generator) generateTypeInerface (ty *entity.TypeInterface) (types.Type, error) { + irStruct := &types.StructType { + Fields: make([]types.Type, len(ty.Behaviors) + 1), + } + // object pointer + irStruct.Fields[0] = types.NewPointer(types.Void) + // behaviors + for index, behavior := range ty.Behaviors { + field, err := this.generateTypeFunction(behavior) + if err != nil { return nil, err } + irStruct.Fields[index + 1] = field + } + return irStruct, nil +} + +func (this *generator) generateTypeInt (ty *entity.TypeInt) (types.Type, error) { + return types.NewInt(uint64(ty.Width)), nil +} + +func (this *generator) generateTypeFloat (ty *entity.TypeFloat) (types.Type, error) { + var kind types.FloatKind; switch ty.Width { + case 16: kind = types.FloatKindHalf + case 32: kind = types.FloatKindFloat + case 64: kind = types.FloatKindDouble + case 80: + if this.target.Arch == "x86" { + kind = types.FloatKindX86_FP80 + } else { + return nil, errors.New(fmt.Sprintln ( + ty, "not available on", + this.target.Arch)) + } + case 128: + if this.target.Arch == "PowerPC" { + kind = types.FloatKindPPC_FP128 + } else { + kind = types.FloatKindFP128 + } + default: + return nil, errors.New(fmt.Sprintln (ty, "has invalid width")) + } + return &types.FloatType { Kind: kind }, nil +} + +func (this *generator) generateTypeWord (ty *entity.TypeWord) (types.Type, error) { + return types.NewInt(this.target.WordSize), nil +} + +func (this *generator) generateTypeFunction ( + signature *entity.Signature, +) ( + types.Type, + error, +) { + irFunc := &types.FuncType { + Params: make([]types.Type, len(signature.Arguments)), + } + ret, err := this.generateType(signature.Return) + if err != nil { return nil, err } + irFunc.RetType = ret + for index, argument := range signature.Arguments { + param, err := this.generateType(argument.Type()) + if err != nil { return nil, err } + irFunc.Params[index] = param + } + return irFunc, nil +} diff --git a/generator/type_test.go b/generator/type_test.go new file mode 100644 index 0000000..65a762e --- /dev/null +++ b/generator/type_test.go @@ -0,0 +1,52 @@ +package generator + +import "testing" + +func TestType (test *testing.T) { +testString (test, +`%AllFloats = type { float, double } +%Bool = type i8 +%Byte = type i8 +%Index = type i64 +%Rune = type i32 +%AllInts = type { %Bool, %Byte, %Index, %Rune, i64, i64, i8, i16, i32, i64, i8, i16, i32, i64 } +%Path = type { void*, i64 } +%Pegasus = type { void*, void ({ void*, i64 }), void ({ void*, i64 }), void (i64), %Bool () } +%Point = type { i64, i64 } +%Quadrangle = type [4 x %Point] +%Rectangle = type { %Point, %Point } +%String = type { void*, i64 } +`, +` +Point: (x:Int y:Int) +Pegasus: ( + [clear clouds:*:Point] + [fly rings:*:Point] + [fall distance:Int] + [complete]:Bool) +Rectangle: ( + min:Point + max:Point) +AllInts: ( + bool:Bool + byte:Byte + index:Index + rune:Rune + word:Int + uword:UInt + i8:I8 + i16:I16 + i32:I32 + i64:I64 + u8:U8 + u16:U16 + u32:U32 + u64:U64 +) +AllFloats: ( + f32:F32 + f64:F64) +Path: *:Point +Quadrangle: 4:Point +`) +}