From 650289f03e0cf46e7b099902c3aff2841fa43a94 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 26 Mar 2024 00:42:27 -0400 Subject: [PATCH] Generate switch statements --- compiler/compiler_test.go | 7 ++ .../data/match-default-print/fspl.mod | 2 +- .../test-data/data/switch-exit-code/fspl.mod | 1 + .../test-data/data/switch-exit-code/main.fspl | 10 +++ generator/control-flow_test.go | 53 ++++++++++++ generator/expression-multiplex.go | 8 ++ generator/expression.go | 82 +++++++++++++++++++ 7 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 compiler/test-data/data/switch-exit-code/fspl.mod create mode 100644 compiler/test-data/data/switch-exit-code/main.fspl diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 9370dde..2327744 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -92,6 +92,13 @@ testUnit (test, 0, )} +func TestSwitchExitCode (test *testing.T) { +testUnit (test, +"/test-data/data/switch-exit-code", nil, +"", "", +4, +)} + func TestReturnAssign (test *testing.T) { dependencies := []string { compileDependency(test, "io"), diff --git a/compiler/test-data/data/match-default-print/fspl.mod b/compiler/test-data/data/match-default-print/fspl.mod index bd8d3c7..81f8c53 100644 --- a/compiler/test-data/data/match-default-print/fspl.mod +++ b/compiler/test-data/data/match-default-print/fspl.mod @@ -1,2 +1,2 @@ -'624d4557-5291-4ad7-9283-7c200b9c2942' +'4f3d6ccb-c233-4648-abd2-72e3f9a82efd' + 'io' diff --git a/compiler/test-data/data/switch-exit-code/fspl.mod b/compiler/test-data/data/switch-exit-code/fspl.mod new file mode 100644 index 0000000..99dea8e --- /dev/null +++ b/compiler/test-data/data/switch-exit-code/fspl.mod @@ -0,0 +1 @@ +'433ed4ee-be14-4d0f-baef-a13919ec3b6c' diff --git a/compiler/test-data/data/switch-exit-code/main.fspl b/compiler/test-data/data/switch-exit-code/main.fspl new file mode 100644 index 0000000..5dde082 --- /dev/null +++ b/compiler/test-data/data/switch-exit-code/main.fspl @@ -0,0 +1,10 @@ +[main]: I32 'main' = { + x:I32 = 1 + [switch x] +} + +[switch x:I32]:I32 = switch x + | 0 5 + | 1 4 + | 2 3 + * 0 diff --git a/generator/control-flow_test.go b/generator/control-flow_test.go index e21c736..a443945 100644 --- a/generator/control-flow_test.go +++ b/generator/control-flow_test.go @@ -426,6 +426,59 @@ U: (| Int F64 UInt) `) } + +func TestSwitchReturn (test *testing.T) { +testString (test, +`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1 +define %"AAAAAAAAAAAAAAAAAAAAAA==::Bool" @"0zNZN147MN2wzMAQ6NS2dQ==::is5"(i64 %x) { +0: + %1 = alloca i64 + store i64 %x, ptr %1 + %2 = load i64, ptr %1 + switch i64 %2, label %3 [ + i64 5, label %4 + ] +3: + ret i1 false +4: + ret i1 true +} +`, +` +[is5 x:Int]:Bool = { + switch x | 5 [return true] + false +} +`) +} + +func TestSwitchReturnValueUsed (test *testing.T) { +testString (test, +`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1 +define %"AAAAAAAAAAAAAAAAAAAAAA==::Bool" @"0zNZN147MN2wzMAQ6NS2dQ==::is5"(i64 %x) { +0: + %1 = alloca i64 + store i64 %x, ptr %1 + %2 = load i64, ptr %1 + switch i64 %2, label %6 [ + i64 5, label %5 + ] +3: + %4 = phi i1 [ false, %6 ] + ret i1 %4 +5: + ret i1 true +6: + br label %3 +} +`, +` +[is5 x:Int]:Bool = switch x + | 5 [return true] + * false +`) +} + func TestIfElseReturnValueUsed (test *testing.T) { testString (test, `%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1 diff --git a/generator/expression-multiplex.go b/generator/expression-multiplex.go index 5f40b70..4a4045e 100644 --- a/generator/expression-multiplex.go +++ b/generator/expression-multiplex.go @@ -95,6 +95,8 @@ func (this *generator) generateExpressionAny (expression entity.Expression) (reg return this.generateIfElse(expression, resultModeAny) case *entity.Match: return this.generateMatch(expression, resultModeAny) + case *entity.Switch: + return this.generateSwitch(expression, resultModeAny) case *entity.Loop: return this.generateLoop(expression, resultModeAny) case *entity.For: @@ -160,6 +162,9 @@ func (this *generator) generateExpressionVal (expression entity.Expression) (llv case *entity.Match: val, _, err := this.generateMatch(expression, resultModeVal) return val, err + case *entity.Switch: + val, _, err := this.generateSwitch(expression, resultModeVal) + return val, err case *entity.Loop: val, _, err := this.generateLoop(expression, resultModeVal) return val, err @@ -235,6 +240,9 @@ func (this *generator) generateExpressionLoc (expression entity.Expression) (llv case *entity.Match: loc, _, err := this.generateMatch(expression, resultModeLoc) return loc, err + case *entity.Switch: + loc, _, err := this.generateSwitch(expression, resultModeLoc) + return loc, err case *entity.Loop: loc, _, err := this.generateLoop(expression, resultModeLoc) return loc, err diff --git a/generator/expression.go b/generator/expression.go index 254efb7..3cd7fe7 100644 --- a/generator/expression.go +++ b/generator/expression.go @@ -214,6 +214,88 @@ func (this *generator) generateMatch (match *entity.Match, mode resultMode) (llv } } +func (this *generator) generateSwitch (match *entity.Switch, mode resultMode) (llvm.Value, bool, error) { + irValue, err := this.generateExpressionVal(match.Value) + if err != nil { return nil, false, err } + irValueType, err := this.generateType(match.Value.Type()) + if err != nil { return nil, false, err } + + previousBlock := this.blockManager.Block + exitBlock := this.blockManager.newBlock() + + irCases := make([]*llvm.Case, len(match.Cases)) + irIncomings := []*llvm.Incoming { } + loc := false + first := true + + generateCaseExpression := func (expression entity.Expression) error { + thisMode := mode + if !first && mode != resultModeAny { + if loc { thisMode = resultModeLoc + } else { thisMode = resultModeVal } + } + result, thisLoc, err := this.generateExpression(expression, thisMode) + if err != nil { return err } + if first { loc = thisLoc } + if !this.blockManager.Terminated() { + if expression != nil { + irIncomings = append(irIncomings, &llvm.Incoming { + X: result, + Predecessor: this.blockManager.Block, + }) + } + this.blockManager.NewBr(exitBlock) + } + return nil + } + + // generate value cases + for index, cas := range match.Cases { + caseBlock := this.blockManager.newBlock() + key := match.CaseOrder[index] + irCases[index] = &llvm.Case { + X: llvm.NewConstInt ( + llvm.ReduceToBase(irValueType).(*llvm.TypeInt), + key), + Target: caseBlock, + } + err = generateCaseExpression(cas.Expression) + if err != nil { return nil, false, err } + } + + // generate default case + var defaultBlock llvm.Value + if match.Default != nil { + defaultBlock = this.blockManager.newBlock() + err = generateCaseExpression(match.Default.Expression) + if err != nil { return nil, false, err } + } + + // create switch branch + this.blockManager.Block = previousBlock + if defaultBlock == nil { + this.blockManager.NewSwitch(irValue, exitBlock, irCases...) + } else { + this.blockManager.NewSwitch(irValue, defaultBlock, irCases...) + } + + // discard/obtain results + this.blockManager.Block = exitBlock + if mode == resultModeAny { + return nil, false, nil + } else { + irType, err := this.generateType(match.Type()) + if err != nil { return nil, false, err } + if defaultBlock == nil { + irIncomings = append(irIncomings, &llvm.Incoming { + X: llvm.NewConstZeroInitializer(irType), + Predecessor: previousBlock, + }) + } + return this.blockManager.NewPhi(irIncomings...), loc, nil + } +} + func (this *generator) generateLoop (loop *entity.Loop, mode resultMode) (llvm.Value, bool, error) { irResultType, err := this.generateType(loop.Type()) if err != nil { return nil, false, err }