Skip to content

Commit

Permalink
Require explicit register type declaration (#35)
Browse files Browse the repository at this point in the history
* `TargetGenerator` now returns `*RegisterInfo`
* Simplified target generation
* Removed internal partial register information. Now each register must be declared at least once with an explicit type
* Added `TargetInfo`
* Fixed tests to pass
* Added untyped register test
  • Loading branch information
RealA10N authored Feb 8, 2025
1 parent 7a145c5 commit f644df5
Show file tree
Hide file tree
Showing 19 changed files with 174 additions and 242 deletions.
2 changes: 1 addition & 1 deletion gen/argument_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type ArgumentInfo interface {
GetType() *ReferencedTypeInfo

// The location where the argument appears in the source code.
Declaration() core.UnmanagedSourceView
Declaration() *core.UnmanagedSourceView

// Returns the argument string, as it should appear in the code code.
String() string
Expand Down
28 changes: 22 additions & 6 deletions gen/function_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func generateFunctionFromSource(

func TestSimpleFunctionGeneration(t *testing.T) {
src := `func $32 @add $32 %a {
%b = ADD %a $32 #1
%c = ADD %b %a
$32 %b = ADD %a $32 #1
$32 %c = ADD %b %a
RET
}`

Expand Down Expand Up @@ -82,7 +82,10 @@ func TestSimpleFunctionGeneration(t *testing.T) {

assert.EqualValues(t,
[]gen.ReferencedTypeInfo{
{Base: &gen.NamedTypeInfo{Name: "$32", Size: 4}, Descriptors: []gen.TypeDescriptorInfo{}},
{
Base: &gen.NamedTypeInfo{Name: "$32", Size: 4},
Descriptors: []gen.TypeDescriptorInfo{},
},
},
function.Targets,
)
Expand All @@ -94,10 +97,10 @@ func TestIfElseFunctionGeneration(t *testing.T) {
src := `func @toBool $32 %n {
JZ %n .zero
.nonzero
%bool = ADD $32 #1 $32 #0
$32 %bool = ADD $32 #1 $32 #0
JMP .end
.zero
%bool = ADD $32 #0 $32 #0
$32 %bool = ADD $32 #0 $32 #0
.end
RET
}`
Expand Down Expand Up @@ -132,11 +135,24 @@ func TestEmptyFunctionGeneration(t *testing.T) {

func TestNoReturnFunctionGeneration(t *testing.T) {
src := `func @noReturn {
%n = ADD $32 #1 $32 #2
$32 %n = ADD $32 #1 $32 #2
}`
function, results := generateFunctionFromSource(t, src)
assert.False(t, results.IsEmpty())
assert.Nil(t, function)
details := results.Head.Value
assert.Contains(t, details[0].Message, "end a function")
}

func TestNoExplicitRegisterType(t *testing.T) {
src := `func @noExplicitType {
%a = ADD $32 #1 $32 #2
RET
}`

function, results := generateFunctionFromSource(t, src)
assert.False(t, results.IsEmpty())
assert.Nil(t, function)
details := results.Head.Value
assert.Contains(t, details[0].Message, "untyped register")
}
4 changes: 2 additions & 2 deletions gen/immediate_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ func (i *ImmediateInfo) GetType() *ReferencedTypeInfo {
return &i.Type
}

func (i *ImmediateInfo) Declaration() core.UnmanagedSourceView {
return i.declaration
func (i *ImmediateInfo) Declaration() *core.UnmanagedSourceView {
return &i.declaration
}

func (i *ImmediateInfo) String() string {
Expand Down
173 changes: 32 additions & 141 deletions gen/instruction_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

type InstructionGenerator struct {
ArgumentGenerator InstructionContextGenerator[parse.ArgumentNode, ArgumentInfo]
TargetGenerator InstructionContextGenerator[parse.TargetNode, registerPartialInfo]
TargetGenerator InstructionContextGenerator[parse.TargetNode, *TargetInfo]
}

func NewInstructionGenerator() FunctionContextGenerator[
Expand Down Expand Up @@ -45,23 +45,41 @@ func (g *InstructionGenerator) generateArguments(
return arguments, results
}

func (g *InstructionGenerator) generatePartialTargetsInfo(
func (g *InstructionGenerator) generateTargets(
ctx *InstructionGenerationContext,
node parse.InstructionNode,
) ([]registerPartialInfo, core.ResultList) {
targets := make([]registerPartialInfo, len(node.Targets))
) ([]*TargetInfo, core.ResultList) {
targets := make([]*TargetInfo, len(node.Targets))
results := core.ResultList{}

// Different targets should not effect one another.
// Thus, we just collect all of the errors along the way, and return
// them in one chunk.
for i, target := range node.Targets {
typeInfo, curResults := g.TargetGenerator.Generate(ctx, target)
v := target.View()
targetInfo, curResults := g.TargetGenerator.Generate(ctx, target)
results.Extend(&curResults)
targets[i] = typeInfo

if targetInfo.Register == nil {
// TODO: improve error message
results.Append(core.Result{
{
Type: core.ErrorResult,
Message: "Undefined or untyped register",
Location: &v,
},
{
Type: core.HintResult,
Message: "A register must be defined with an explicit type at least once",
},
})
}

targets[i] = targetInfo
}

if !results.IsEmpty() {
return nil, results
}

return targets, results
return targets, core.ResultList{}
}

func (g *InstructionGenerator) generateLabels(
Expand All @@ -87,115 +105,6 @@ func (g *InstructionGenerator) generateLabels(
return labels, core.ResultList{}
}

func partialTargetsToTypes(targets []registerPartialInfo) []*ReferencedTypeInfo {
types := make([]*ReferencedTypeInfo, len(targets))
for i, target := range targets {
types[i] = target.Type
}
return types
}

func argumentsToTypes(arguments []ArgumentInfo) []*ReferencedTypeInfo {
types := make([]*ReferencedTypeInfo, len(arguments))
for i, arg := range arguments {
types[i] = arg.GetType()
}
return types
}

func (g *InstructionGenerator) getTargetRegister(
ctx *InstructionGenerationContext,
node parse.TargetNode,
targetType ReferencedTypeInfo,
) (*RegisterInfo, core.ResultList) {
registerName := nodeToSourceString(ctx.FileGenerationContext, node.Register)
registerInfo := ctx.Registers.GetRegister(registerName)
nodeView := node.View()

if registerInfo == nil {
// register is defined here; we should create the register and define
// it's type.
newRegisterInfo := &RegisterInfo{
Name: registerName,
Type: targetType,
Declaration: nodeView,
}

return newRegisterInfo, ctx.Registers.NewRegister(newRegisterInfo)
}

// register is already defined
if !registerInfo.Type.Equal(targetType) {
// notest: sanity check only
return nil, list.FromSingle(core.Result{{
Type: core.InternalErrorResult,
Message: "Internal register type mismatch",
Location: &nodeView,
}})
}

return registerInfo, core.ResultList{}
}

// Registers can be defined by being a target of an instruction.
// After we have determined 100% of the instruction targets types (either
// if they were explicitly declared or not), we call this function with the
// target types, and here we iterate over all target types and define missing
// registers.
//
// This also returns the full list of register targets for the provided
// instruction.
func (g *InstructionGenerator) defineAndGetTargetRegisters(
ctx *InstructionGenerationContext,
node parse.InstructionNode,
targetTypes []ReferencedTypeInfo,
) ([]*RegisterArgumentInfo, core.ResultList) {
if len(node.Targets) != len(targetTypes) {
// notest: sanity check: ensure lengths match.
v := node.View()
return nil, list.FromSingle(core.Result{{
Type: core.InternalErrorResult,
Message: "Targets length mismatch",
Location: &v,
}})
}

registers := make([]*RegisterArgumentInfo, len(node.Targets))
results := core.ResultList{}
for i, target := range node.Targets {
// register errors should not effect one another, so we collect them.
registerInfo, curResults := g.getTargetRegister(
ctx,
target,
targetTypes[i],
)

if !results.IsEmpty() {
results.Extend(&curResults)
}

if registerInfo == nil {
// notest: sanity check, should not happen.
v := target.View()
results.Append(core.Result{{
Type: core.InternalErrorResult,
Message: "Unexpected nil register",
Location: &v,
}})
}

registerArgument := &RegisterArgumentInfo{
Register: registerInfo,
declaration: node.View(),
}

registerInfo.AddDefinition(ctx.InstructionInfo)
registers[i] = registerArgument
}

return registers, results
}

// Convert an instruction parsed node into an instruction that is in the
// instruction set.
// If new registers are defined in the instruction (by assigning values to
Expand All @@ -209,8 +118,6 @@ func (g *InstructionGenerator) Generate(
// and processing the targets and arguments. We accumulate the results,
// since those processes do not effect each other.

// TODO: tidy this mess.

v := node.View()
instCtx := InstructionGenerationContext{
FunctionGenerationContext: ctx,
Expand All @@ -223,7 +130,7 @@ func (g *InstructionGenerator) Generate(
arguments, curResults := g.generateArguments(&instCtx, node)
results.Extend(&curResults)

partialTargets, curResults := g.generatePartialTargetsInfo(&instCtx, node)
targets, curResults := g.generateTargets(&instCtx, node)
results.Extend(&curResults)

labels, curResults := g.generateLabels(&instCtx, node)
Expand All @@ -234,25 +141,9 @@ func (g *InstructionGenerator) Generate(
return nil, results
}

instCtx.InstructionInfo.Arguments = arguments
instCtx.InstructionInfo.Labels = labels

explicitTargetTypes := partialTargetsToTypes(partialTargets)
argumentTypes := argumentsToTypes(arguments)
targetTypes, results := instDef.InferTargetTypes(ctx, explicitTargetTypes, argumentTypes)
// TODO: validate that the returned target types matches expected constraints.

if !results.IsEmpty() {
return nil, results
}

targets, results := g.defineAndGetTargetRegisters(&instCtx, node, targetTypes)

if !results.IsEmpty() {
return nil, results
}

instCtx.InstructionInfo.Targets = targets
instCtx.InstructionInfo.AppendTarget(targets...)
instCtx.InstructionInfo.AppendArgument(arguments...)
instCtx.InstructionInfo.AppendLabels(labels...)

instruction, results := instDef.BuildInstruction(instCtx.InstructionInfo)
if !results.IsEmpty() {
Expand Down
4 changes: 2 additions & 2 deletions gen/instruction_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func (m *InstructionMap) GetInstructionDefinition(
}

func TestInstructionCreateTarget(t *testing.T) {
src := core.NewSourceView("%c = ADD %a %b\n")
src := core.NewSourceView("$32 %c = ADD %a %b\n")
tkns, err := lex.NewTokenizer().Tokenize(src)
assert.NoError(t, err)

Expand Down Expand Up @@ -221,5 +221,5 @@ func TestInstructionCreateTarget(t *testing.T) {
assert.NotNil(t, target)
assert.Equal(t, "%c", target.Name)
assert.Equal(t, intType, target.Type.Base)
assert.Equal(t, src.Unmanaged().Subview(0, 2), target.Declaration)
assert.Equal(t, src.Unmanaged().Subview(0, 6), target.Declaration)
}
19 changes: 13 additions & 6 deletions gen/instruction_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type InstructionInfo struct {

// The targets of the instruction.
// TODO: is a pointer reference really required here?
Targets []*RegisterArgumentInfo
Targets []*TargetInfo

// The arguments of the instruction.
Arguments []ArgumentInfo
Expand All @@ -29,7 +29,7 @@ func NewEmptyInstructionInfo(
) *InstructionInfo {
return &InstructionInfo{
BasicBlockInfo: nil,
Targets: []*RegisterArgumentInfo{},
Targets: []*TargetInfo{},
Arguments: []ArgumentInfo{},
Labels: []*LabelInfo{},
Instruction: nil,
Expand All @@ -44,11 +44,18 @@ func (i *InstructionInfo) linkToBasicBlock(basicBlock *BasicBlockInfo) {
}
}

// Appends the given register as a target of the instruction,
// Appends the given register(s) as a target(s) of the instruction,
// including updating the required instruction and register information fields.
func (i *InstructionInfo) AppendTarget(target *RegisterArgumentInfo) {
target.Register.AddDefinition(i)
i.Targets = append(i.Targets, target)
func (i *InstructionInfo) AppendTarget(targets ...*TargetInfo) {
for _, target := range targets {
target.Register.AddDefinition(i)
}

i.Targets = append(i.Targets, targets...)
}

func (i *InstructionInfo) AppendArgument(arguments ...ArgumentInfo) {
i.Arguments = append(i.Arguments, arguments...)
}

func (i *InstructionInfo) AppendLabels(labels ...*LabelInfo) {
Expand Down
Loading

0 comments on commit f644df5

Please sign in to comment.