Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Compiler] Compile boolean operators #3796

Merged
merged 1 commit into from
Feb 26, 2025
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
4 changes: 4 additions & 0 deletions bbq/compiler/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ func (g *InstructionCodeGen) PatchJump(offset int, newTarget uint16) {
ins.Target = newTarget
(*g.target)[offset] = ins

case opcode.InstructionJumpIfTrue:
ins.Target = newTarget
(*g.target)[offset] = ins

case opcode.InstructionJumpIfNil:
ins.Target = newTarget
(*g.target)[offset] = ins
Expand Down
46 changes: 46 additions & 0 deletions bbq/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ func (c *Compiler[_]) emitUndefinedJumpIfFalse() int {
return offset
}

func (c *Compiler[_]) emitUndefinedJumpIfTrue() int {
offset := c.codeGen.Offset()
c.codeGen.Emit(opcode.InstructionJumpIfTrue{Target: math.MaxUint16})
return offset
}

func (c *Compiler[_]) emitUndefinedJumpIfNil() int {
offset := c.codeGen.Offset()
c.codeGen.Emit(opcode.InstructionJumpIfNil{Target: math.MaxUint16})
Expand Down Expand Up @@ -1282,6 +1288,44 @@ func (c *Compiler[_]) VisitBinaryExpression(expression *ast.BinaryExpression) (_
// End
c.patchJump(thenJump)

case ast.OperationOr:
// TODO: optimize chains of ors / ands

leftTrueJump := c.emitUndefinedJumpIfTrue()

c.compileExpression(expression.Right)
rightFalseJump := c.emitUndefinedJumpIfFalse()

// Left or right is true
c.patchJump(leftTrueJump)
c.codeGen.Emit(opcode.InstructionTrue{})
trueJump := c.emitUndefinedJump()

// Left and right are false
c.patchJump(rightFalseJump)
c.codeGen.Emit(opcode.InstructionFalse{})

c.patchJump(trueJump)

case ast.OperationAnd:
// TODO: optimize chains of ors / ands

leftFalseJump := c.emitUndefinedJumpIfFalse()

c.compileExpression(expression.Right)
rightFalseJump := c.emitUndefinedJumpIfFalse()

// Left and right are true
c.codeGen.Emit(opcode.InstructionTrue{})
trueJump := c.emitUndefinedJump()

// Left or right is false
c.patchJump(leftFalseJump)
c.patchJump(rightFalseJump)
c.codeGen.Emit(opcode.InstructionFalse{})

c.patchJump(trueJump)

default:
c.compileExpression(expression.Right)

Expand All @@ -1296,10 +1340,12 @@ func (c *Compiler[_]) VisitBinaryExpression(expression *ast.BinaryExpression) (_
c.codeGen.Emit(opcode.InstructionDivide{})
case ast.OperationMod:
c.codeGen.Emit(opcode.InstructionMod{})

case ast.OperationEqual:
c.codeGen.Emit(opcode.InstructionEqual{})
case ast.OperationNotEqual:
c.codeGen.Emit(opcode.InstructionNotEqual{})

case ast.OperationLess:
c.codeGen.Emit(opcode.InstructionLess{})
case ast.OperationLessEqual:
Expand Down
114 changes: 114 additions & 0 deletions bbq/compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3637,3 +3637,117 @@ func TestCompileConditional(t *testing.T) {
program.Constants,
)
}

func TestCompileOr(t *testing.T) {

t.Parallel()

checker, err := ParseAndCheck(t, `
fun test(x: Bool, y: Bool): Bool {
return x || y
}
`)
require.NoError(t, err)

comp := compiler.NewInstructionCompiler(checker)
program := comp.Compile()

const parameterCount = 2

const (
// xIndex is the index of the parameter `x`, which is the first parameter
xIndex = iota
// yIndex is the index of the parameter `y`, which is the second parameter
yIndex
)

// resultIndex is the index of the $result variable
const resultIndex = parameterCount

require.Len(t, program.Functions, 1)

functions := comp.ExportFunctions()
require.Equal(t, len(program.Functions), len(functions))

assert.Equal(t,
[]opcode.Instruction{
// return x || y
opcode.InstructionGetLocal{LocalIndex: xIndex},
opcode.InstructionJumpIfTrue{Target: 4},

opcode.InstructionGetLocal{LocalIndex: yIndex},
opcode.InstructionJumpIfFalse{Target: 6},

opcode.InstructionTrue{},
opcode.InstructionJump{Target: 7},

opcode.InstructionFalse{},

// assign to temp $result
opcode.InstructionTransfer{TypeIndex: 0},
opcode.InstructionSetLocal{LocalIndex: resultIndex},

// return $result
opcode.InstructionGetLocal{LocalIndex: resultIndex},
opcode.InstructionReturnValue{},
},
functions[0].Code,
)
}

func TestCompileAnd(t *testing.T) {

t.Parallel()

checker, err := ParseAndCheck(t, `
fun test(x: Bool, y: Bool): Bool {
return x && y
}
`)
require.NoError(t, err)

comp := compiler.NewInstructionCompiler(checker)
program := comp.Compile()

const parameterCount = 2

const (
// xIndex is the index of the parameter `x`, which is the first parameter
xIndex = iota
// yIndex is the index of the parameter `y`, which is the second parameter
yIndex
)

// resultIndex is the index of the $result variable
const resultIndex = parameterCount

require.Len(t, program.Functions, 1)

functions := comp.ExportFunctions()
require.Equal(t, len(program.Functions), len(functions))

assert.Equal(t,
[]opcode.Instruction{
// return x && y
opcode.InstructionGetLocal{LocalIndex: xIndex},
opcode.InstructionJumpIfFalse{Target: 6},

opcode.InstructionGetLocal{LocalIndex: yIndex},
opcode.InstructionJumpIfFalse{Target: 6},

opcode.InstructionTrue{},
opcode.InstructionJump{Target: 7},

opcode.InstructionFalse{},

// assign to temp $result
opcode.InstructionTransfer{TypeIndex: 0},
opcode.InstructionSetLocal{LocalIndex: resultIndex},

// return $result
opcode.InstructionGetLocal{LocalIndex: resultIndex},
opcode.InstructionReturnValue{},
},
functions[0].Code,
)
}
32 changes: 32 additions & 0 deletions bbq/opcode/instructions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions bbq/opcode/instructions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,19 @@
- name: "value"
type: "value"

- name: "jumpIfTrue"
description:
Pops a value off the stack. If it is `true`, jumps to the target instruction.
operands:
- name: "target"
type: "index"
controlEffects:
- jump: "target"
valueEffects:
pop:
- name: "value"
type: "value"

- name: "jumpIfNil"
description:
Pops a value off the stack. If it is `nil`, jumps to the target instruction.
Expand Down
2 changes: 1 addition & 1 deletion bbq/opcode/opcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ const (
ReturnValue
Jump
JumpIfFalse
JumpIfTrue
JumpIfNil
_
_
_
_
_

// Int operations

Expand Down
9 changes: 5 additions & 4 deletions bbq/opcode/opcode_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading