From 2ca551cdc648dab5648a61e2b848d74434643fe1 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 11 Jul 2020 21:08:19 +0900 Subject: [PATCH] shader: Implement 'for' statement Fixes #1230 --- internal/shader/shader.go | 9 +- internal/shader/stmt.go | 167 ++++++++++++++++++++++- internal/shader/testdata/for.expected.vs | 14 ++ internal/shader/testdata/for.go | 13 ++ internal/shaderir/glsl.go | 5 +- 5 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 internal/shader/testdata/for.expected.vs create mode 100644 internal/shader/testdata/for.go diff --git a/internal/shader/shader.go b/internal/shader/shader.go index ee5fd37dc..4f63e1380 100644 --- a/internal/shader/shader.go +++ b/internal/shader/shader.go @@ -24,8 +24,9 @@ import ( ) type variable struct { - name string - typ shaderir.Type + name string + typ shaderir.Type + forLoopCounter bool } type constant struct { @@ -645,6 +646,10 @@ func (cs *compileState) parseBlock(outer *block, stmts []ast.Stmt, inParams, out offset = len(inParams) + len(outParams) } for _, v := range block.vars[offset:] { + if v.forLoopCounter { + block.ir.LocalVars = append(block.ir.LocalVars, shaderir.Type{}) + continue + } block.ir.LocalVars = append(block.ir.LocalVars, v.typ) } }() diff --git a/internal/shader/stmt.go b/internal/shader/stmt.go index cb09e1c51..0d18c26c3 100644 --- a/internal/shader/stmt.go +++ b/internal/shader/stmt.go @@ -113,6 +113,163 @@ func (cs *compileState) parseStmt(block *block, stmt ast.Stmt, inParams []variab return nil, false } stmts = append(stmts, ss...) + + case *ast.ForStmt: + msg := "for-statement must follow this format: for (varname) := (constant); (varname) (op) (constant); (varname) (op) (constant) { ..." + if stmt.Init == nil { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if stmt.Cond == nil { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if stmt.Post == nil { + cs.addError(stmt.Pos(), msg) + return nil, false + } + + // Create a new pseudo block for the initial statement, so that the counter variable belongs to the + // new pseudo block for each for-loop. Without this, the samely named counter variables in different + // for-loops confuses the parser. + pseudoBlock, ok := cs.parseBlock(block, []ast.Stmt{stmt.Init}, inParams, nil) + if !ok { + return nil, false + } + ss := pseudoBlock.ir.Stmts + + if len(ss) != 1 { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if ss[0].Type != shaderir.Assign { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if ss[0].Exprs[0].Type != shaderir.LocalVariable { + cs.addError(stmt.Pos(), msg) + return nil, false + } + varidx := ss[0].Exprs[0].Index + if ss[0].Exprs[1].Type != shaderir.NumberExpr { + cs.addError(stmt.Pos(), msg) + return nil, false + } + + vartype := pseudoBlock.vars[0].typ + init := ss[0].Exprs[1].Const + + exprs, ts, ss, ok := cs.parseExpr(pseudoBlock, stmt.Cond) + if !ok { + return nil, false + } + if len(exprs) != 1 { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if len(ts) != 1 || ts[0].Main != shaderir.Bool { + cs.addError(stmt.Pos(), "for-statement's condition must be bool") + return nil, false + } + if len(ss) != 0 { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if exprs[0].Type != shaderir.Binary { + cs.addError(stmt.Pos(), msg) + return nil, false + } + op := exprs[0].Op + if op != shaderir.LessThanOp && op != shaderir.LessThanEqualOp && op != shaderir.GreaterThanOp && op != shaderir.GreaterThanEqualOp && op != shaderir.EqualOp && op != shaderir.NotEqualOp { + cs.addError(stmt.Pos(), "for-statement's condition must have one of these operators: <, <=, >, >=, ==, !=") + return nil, false + } + if exprs[0].Exprs[0].Type != shaderir.LocalVariable { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if exprs[0].Exprs[0].Index != varidx { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if exprs[0].Exprs[1].Type != shaderir.NumberExpr { + cs.addError(stmt.Pos(), msg) + return nil, false + } + end := exprs[0].Exprs[1].Const + + postSs, ok := cs.parseStmt(pseudoBlock, stmt.Post, inParams) + if !ok { + return nil, false + } + if len(postSs) != 1 { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if postSs[0].Type != shaderir.Assign { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if postSs[0].Exprs[0].Type != shaderir.LocalVariable { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if postSs[0].Exprs[0].Index != varidx { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if postSs[0].Exprs[1].Type != shaderir.Binary { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if postSs[0].Exprs[1].Exprs[0].Type != shaderir.LocalVariable { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if postSs[0].Exprs[1].Exprs[0].Index != varidx { + cs.addError(stmt.Pos(), msg) + return nil, false + } + if postSs[0].Exprs[1].Exprs[1].Type != shaderir.NumberExpr { + cs.addError(stmt.Pos(), msg) + return nil, false + } + delta := postSs[0].Exprs[1].Exprs[1].Const + switch postSs[0].Exprs[1].Op { + case shaderir.Add: + case shaderir.Sub: + delta = gconstant.UnaryOp(token.SUB, delta, 0) + default: + cs.addError(stmt.Pos(), "for-statement's post statement must have one of these operators: +=, -=, ++, --") + return nil, false + } + + b, ok := cs.parseBlock(pseudoBlock, []ast.Stmt{stmt.Body}, inParams, nil) + if !ok { + return nil, false + } + bodyir := b.ir + for len(bodyir.Stmts) == 1 && bodyir.Stmts[0].Type == shaderir.BlockStmt { + bodyir = bodyir.Stmts[0].Blocks[0] + } + + // As the pseudo block is not actually used, copy the variable part to the actual block. + // This must be done after parsing the for-loop is done, or the duplicated variables confuses the + // parsing. + block.vars = append(block.vars, pseudoBlock.vars[0]) + block.vars[len(block.vars)-1].forLoopCounter = true + + stmts = append(stmts, shaderir.Stmt{ + Type: shaderir.For, + Blocks: []shaderir.Block{bodyir}, + ForVarType: vartype, + ForVarIndex: varidx, + ForInit: init, + ForEnd: end, + ForOp: op, + ForDelta: delta, + }) + case *ast.IfStmt: if stmt.Init != nil { init := stmt.Init @@ -123,10 +280,8 @@ func (cs *compileState) parseStmt(block *block, stmt ast.Stmt, inParams []variab } stmts = append(stmts, shaderir.Stmt{ - Type: shaderir.BlockStmt, - Blocks: []shaderir.Block{ - b.ir, - }, + Type: shaderir.BlockStmt, + Blocks: []shaderir.Block{b.ir}, }) return stmts, true } @@ -174,6 +329,7 @@ func (cs *compileState) parseStmt(block *block, stmt ast.Stmt, inParams []variab Exprs: exprs, Blocks: bs, }) + case *ast.IncDecStmt: exprs, _, ss, ok := cs.parseExpr(block, stmt.X) if !ok { @@ -205,6 +361,7 @@ func (cs *compileState) parseStmt(block *block, stmt ast.Stmt, inParams []variab }, }, }) + case *ast.ReturnStmt: for i, r := range stmt.Results { exprs, _, ss, ok := cs.parseExpr(block, r) @@ -233,6 +390,7 @@ func (cs *compileState) parseStmt(block *block, stmt ast.Stmt, inParams []variab stmts = append(stmts, shaderir.Stmt{ Type: shaderir.Return, }) + case *ast.ExprStmt: exprs, _, ss, ok := cs.parseExpr(block, stmt.X) if !ok { @@ -249,6 +407,7 @@ func (cs *compileState) parseStmt(block *block, stmt ast.Stmt, inParams []variab Exprs: []shaderir.Expr{expr}, }) } + default: cs.addError(stmt.Pos(), fmt.Sprintf("unexpected statement: %#v", stmt)) return nil, false diff --git a/internal/shader/testdata/for.expected.vs b/internal/shader/testdata/for.expected.vs new file mode 100644 index 000000000..50e8f67cf --- /dev/null +++ b/internal/shader/testdata/for.expected.vs @@ -0,0 +1,14 @@ +void F0(out vec2 l0) { + vec2 l1 = vec2(0); + vec2 l3 = vec2(0); + l1 = vec2(0.0); + for (int l2 = 0; l2 < 100; l2++) { + (l1).x = ((l1).x) + (1); + } + l3 = vec2(0.0); + for (float l4 = 10.0; l4 >= 0.0; l4 -= 2.0) { + (l3).x = ((l3).x) - (1); + } + l0 = l1; + return; +} diff --git a/internal/shader/testdata/for.go b/internal/shader/testdata/for.go new file mode 100644 index 000000000..911ac3cf9 --- /dev/null +++ b/internal/shader/testdata/for.go @@ -0,0 +1,13 @@ +package main + +func Foo() vec2 { + v := vec2(0) + for i := 0; i < 100; i++ { + v.x++ + } + v2 := vec2(0) + for i := 10.0; i >= 0; i -= 2 { + v2.x-- + } + return v +} diff --git a/internal/shaderir/glsl.go b/internal/shaderir/glsl.go index caca9bd22..03e2f01f2 100644 --- a/internal/shaderir/glsl.go +++ b/internal/shaderir/glsl.go @@ -265,7 +265,10 @@ func (p *Program) glslBlock(topBlock, block *Block, level int, localVarIndex int var lines []string for _, t := range block.LocalVars { - lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, p.glslVarDecl(&t, fmt.Sprintf("l%d", localVarIndex)), p.glslVarInit(&t))) + // The type is None e.g., when the variable is a for-loop counter. + if t.Main != None { + lines = append(lines, fmt.Sprintf("%s%s = %s;", idt, p.glslVarDecl(&t, fmt.Sprintf("l%d", localVarIndex)), p.glslVarInit(&t))) + } localVarIndex++ }