diff --git a/examples/empty.usm b/examples/empty.usm new file mode 100644 index 0000000..24df406 --- /dev/null +++ b/examples/empty.usm @@ -0,0 +1,3 @@ +func @main { + +} \ No newline at end of file diff --git a/examples/loop.usm b/examples/loop.usm new file mode 100644 index 0000000..aead41c --- /dev/null +++ b/examples/loop.usm @@ -0,0 +1,4 @@ +func @main { +.loop + j .loop +} \ No newline at end of file diff --git a/examples/terminate.usm b/examples/terminate.usm new file mode 100644 index 0000000..b5e9787 --- /dev/null +++ b/examples/terminate.usm @@ -0,0 +1,9 @@ +func @main { + %n = $64 #0 +.loop + put %n + %n = add %n $64 #1 + %cond = add %n $64 #-10 + jnz %cond .loop + term +} \ No newline at end of file diff --git a/gen/argument.go b/gen/argument.go deleted file mode 100644 index 94d222f..0000000 --- a/gen/argument.go +++ /dev/null @@ -1,58 +0,0 @@ -package gen - -import ( - "alon.kr/x/list" - "alon.kr/x/usm/core" - "alon.kr/x/usm/parse" -) - -// MARK: Info - -type ArgumentInfo interface { - // A pointer to the ReferencedTypeInfo instance that corresponds to the - // type of the argument. Nil if the argument does not have a type (for - // example, a label). - GetType() *ReferencedTypeInfo - - // The location where the argument appears in the source code. - Declaration() core.UnmanagedSourceView -} - -// MARK: Generator - -type ArgumentGenerator[InstT BaseInstruction] struct { - RegisterArgumentGenerator FunctionContextGenerator[InstT, parse.RegisterNode, ArgumentInfo] - ImmediateArgumentGenerator FunctionContextGenerator[InstT, parse.ImmediateNode, ArgumentInfo] - LabelArgumentGenerator FunctionContextGenerator[InstT, parse.LabelNode, ArgumentInfo] -} - -func NewArgumentGenerator[InstT BaseInstruction]() FunctionContextGenerator[InstT, parse.ArgumentNode, ArgumentInfo] { - return FunctionContextGenerator[InstT, parse.ArgumentNode, ArgumentInfo]( - &ArgumentGenerator[InstT]{ - RegisterArgumentGenerator: NewRegisterArgumentGenerator[InstT](), - ImmediateArgumentGenerator: NewImmediateArgumentGenerator[InstT](), - LabelArgumentGenerator: NewLabelArgumentGenerator[InstT](), - }, - ) -} - -func (g *ArgumentGenerator[InstT]) Generate( - ctx *FunctionGenerationContext[InstT], - node parse.ArgumentNode, -) (ArgumentInfo, core.ResultList) { - switch typedNode := node.(type) { - case parse.RegisterNode: - return g.RegisterArgumentGenerator.Generate(ctx, typedNode) - case parse.ImmediateNode: - return g.ImmediateArgumentGenerator.Generate(ctx, typedNode) - case parse.LabelNode: - return g.LabelArgumentGenerator.Generate(ctx, typedNode) - default: - v := node.View() - return nil, list.FromSingle(core.Result{{ - Type: core.InternalErrorResult, - Message: "Unsupported argument type", - Location: &v, - }}) - } -} diff --git a/gen/argument_generator.go b/gen/argument_generator.go new file mode 100644 index 0000000..c01a917 --- /dev/null +++ b/gen/argument_generator.go @@ -0,0 +1,44 @@ +package gen + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/parse" +) + +type ArgumentGenerator struct { + RegisterArgumentGenerator InstructionContextGenerator[parse.RegisterNode, ArgumentInfo] + ImmediateArgumentGenerator InstructionContextGenerator[parse.ImmediateNode, ArgumentInfo] + LabelArgumentGenerator InstructionContextGenerator[parse.LabelNode, ArgumentInfo] +} + +func NewArgumentGenerator() InstructionContextGenerator[parse.ArgumentNode, ArgumentInfo] { + return InstructionContextGenerator[parse.ArgumentNode, ArgumentInfo]( + &ArgumentGenerator{ + RegisterArgumentGenerator: NewRegisterArgumentGenerator(), + ImmediateArgumentGenerator: NewImmediateArgumentGenerator(), + LabelArgumentGenerator: NewLabelArgumentGenerator(), + }, + ) +} + +func (g *ArgumentGenerator) Generate( + ctx *InstructionGenerationContext, + node parse.ArgumentNode, +) (ArgumentInfo, core.ResultList) { + switch typedNode := node.(type) { + case parse.RegisterNode: + return g.RegisterArgumentGenerator.Generate(ctx, typedNode) + case parse.ImmediateNode: + return g.ImmediateArgumentGenerator.Generate(ctx, typedNode) + case parse.LabelNode: + return g.LabelArgumentGenerator.Generate(ctx, typedNode) + default: + v := node.View() + return nil, list.FromSingle(core.Result{{ + Type: core.InternalErrorResult, + Message: "Unsupported argument type", + Location: &v, + }}) + } +} diff --git a/gen/argument_test.go b/gen/argument_generator_test.go similarity index 56% rename from gen/argument_test.go rename to gen/argument_generator_test.go index 8c5a2b7..d4a9fb9 100644 --- a/gen/argument_test.go +++ b/gen/argument_generator_test.go @@ -14,19 +14,22 @@ func TestUndefinedRegisterArgument(t *testing.T) { unmanaged := src.Unmanaged() node := parse.RegisterNode{TokenNode: parse.TokenNode{UnmanagedSourceView: unmanaged}} - ctx := gen.FunctionGenerationContext[Instruction]{ - FileGenerationContext: &gen.FileGenerationContext[Instruction]{ - GenerationContext: &gen.GenerationContext[Instruction]{ - Instructions: &InstructionMap{}, - PointerSize: 8, + ctx := gen.InstructionGenerationContext{ + FunctionGenerationContext: &gen.FunctionGenerationContext{ + FileGenerationContext: &gen.FileGenerationContext{ + GenerationContext: &gen.GenerationContext{ + Instructions: &InstructionMap{}, + PointerSize: 8, + }, + SourceContext: src.Ctx(), + Types: &TypeMap{}, }, - SourceContext: src.Ctx(), - Types: &TypeMap{}, + Registers: &RegisterMap{}, }, - Registers: &RegisterMap{}, + InstructionInfo: gen.NewEmptyInstructionInfo(&unmanaged), } - generator := gen.NewArgumentGenerator[Instruction]() + generator := gen.NewArgumentGenerator() _, results := generator.Generate(&ctx, node) assert.EqualValues(t, 1, results.Len()) diff --git a/gen/argument_info.go b/gen/argument_info.go new file mode 100644 index 0000000..3a96214 --- /dev/null +++ b/gen/argument_info.go @@ -0,0 +1,13 @@ +package gen + +import "alon.kr/x/usm/core" + +type ArgumentInfo interface { + // A pointer to the ReferencedTypeInfo instance that corresponds to the + // type of the argument. Nil if the argument does not have a type (for + // example, a label). + GetType() *ReferencedTypeInfo + + // The location where the argument appears in the source code. + Declaration() core.UnmanagedSourceView +} diff --git a/gen/basic_block_info.go b/gen/basic_block_info.go new file mode 100644 index 0000000..ba92d06 --- /dev/null +++ b/gen/basic_block_info.go @@ -0,0 +1,31 @@ +package gen + +type BasicBlockInfo struct { + Instructions []*InstructionInfo + + ForwardEdges []*BasicBlockInfo + BackwardEdges []*BasicBlockInfo + + // All basic blocks in a function have a defined ordering between them. + // The initial ordering that the USM engine produces is the order in which + // the basic blocks appear in the source code. + // the `NextBlock` field points to the next block that follows this block + // in the ordering, or nil if this is the last basic block in the function. + NextBlock *BasicBlockInfo +} + +func NewEmptyBasicBlockInfo() *BasicBlockInfo { + return &BasicBlockInfo{ + Instructions: []*InstructionInfo{}, + + ForwardEdges: []*BasicBlockInfo{}, + BackwardEdges: []*BasicBlockInfo{}, + + NextBlock: nil, + } +} + +func (b *BasicBlockInfo) AppendInstruction(instruction *InstructionInfo) { + b.Instructions = append(b.Instructions, instruction) + instruction.LinkToBasicBlock(b) +} diff --git a/gen/file.go b/gen/file_generator.go similarity index 51% rename from gen/file.go rename to gen/file_generator.go index 19965e3..7cd4728 100644 --- a/gen/file.go +++ b/gen/file_generator.go @@ -5,35 +5,31 @@ import ( "alon.kr/x/usm/parse" ) -type FileInfo[InstT BaseInstruction] struct { - Functions []*FunctionInfo[InstT] +type FileGenerator struct { + NamedTypeGenerator FileContextGenerator[parse.TypeDeclarationNode, *NamedTypeInfo] + FunctionGenerator FileContextGenerator[parse.FunctionNode, *FunctionInfo] } -type FileGenerator[InstT BaseInstruction] struct { - NamedTypeGenerator FileContextGenerator[InstT, parse.TypeDeclarationNode, *NamedTypeInfo] - FunctionGenerator FileContextGenerator[InstT, parse.FunctionNode, *FunctionInfo[InstT]] -} - -func NewFileGenerator[InstT BaseInstruction]() FileGenerator[InstT] { - return FileGenerator[InstT]{ - NamedTypeGenerator: NewNamedTypeGenerator[InstT](), - FunctionGenerator: NewFunctionGenerator[InstT](), +func NewFileGenerator() FileGenerator { + return FileGenerator{ + NamedTypeGenerator: NewNamedTypeGenerator(), + FunctionGenerator: NewFunctionGenerator(), } } -func (g *FileGenerator[InstT]) createFileContext( - ctx *GenerationContext[InstT], +func (g *FileGenerator) createFileContext( + ctx *GenerationContext, source core.SourceContext, -) *FileGenerationContext[InstT] { - return &FileGenerationContext[InstT]{ +) *FileGenerationContext { + return &FileGenerationContext{ GenerationContext: ctx, SourceContext: source, Types: ctx.TypeManagerCreator(), } } -func (g *FileGenerator[InstT]) generateTypesFromDeclarations( - ctx *FileGenerationContext[InstT], +func (g *FileGenerator) generateTypesFromDeclarations( + ctx *FileGenerationContext, nodes []parse.TypeDeclarationNode, ) (types []*NamedTypeInfo, results core.ResultList) { types = make([]*NamedTypeInfo, len(nodes)) @@ -45,11 +41,11 @@ func (g *FileGenerator[InstT]) generateTypesFromDeclarations( return types, results } -func (g *FileGenerator[InstT]) generateFunctions( - ctx *FileGenerationContext[InstT], +func (g *FileGenerator) generateFunctions( + ctx *FileGenerationContext, nodes []parse.FunctionNode, -) (functions []*FunctionInfo[InstT], results core.ResultList) { - functions = make([]*FunctionInfo[InstT], len(nodes)) +) (functions []*FunctionInfo, results core.ResultList) { + functions = make([]*FunctionInfo, len(nodes)) for i, node := range nodes { functionInfo, curResults := g.FunctionGenerator.Generate(ctx, node) functions[i] = functionInfo @@ -58,11 +54,11 @@ func (g *FileGenerator[InstT]) generateFunctions( return functions, results } -func (g *FileGenerator[InstT]) Generate( - ctx *GenerationContext[InstT], +func (g *FileGenerator) Generate( + ctx *GenerationContext, source core.SourceContext, node parse.FileNode, -) (*FileInfo[InstT], core.ResultList) { +) (*FileInfo, core.ResultList) { var results core.ResultList fileCtx := g.createFileContext(ctx, source) @@ -76,7 +72,7 @@ func (g *FileGenerator[InstT]) Generate( return nil, results } - file := &FileInfo[InstT]{ + file := &FileInfo{ Functions: functions, } diff --git a/gen/file_info.go b/gen/file_info.go new file mode 100644 index 0000000..b0c8b88 --- /dev/null +++ b/gen/file_info.go @@ -0,0 +1,5 @@ +package gen + +type FileInfo struct { + Functions []*FunctionInfo +} diff --git a/gen/function.go b/gen/function.go deleted file mode 100644 index e52d9ad..0000000 --- a/gen/function.go +++ /dev/null @@ -1,125 +0,0 @@ -package gen - -import ( - "alon.kr/x/usm/core" - "alon.kr/x/usm/parse" -) - -type FunctionInfo[InstT BaseInstruction] struct { - Instructions []*InstructionInfo[InstT] - Parameters []*RegisterInfo - // TODO: add targets -} - -type FunctionGenerator[InstT BaseInstruction] struct { - InstructionGenerator FunctionContextGenerator[InstT, parse.InstructionNode, *InstructionInfo[InstT]] - ParameterGenerator FunctionContextGenerator[InstT, parse.ParameterNode, *RegisterInfo] - LabelDefinitionGenerator LabelContextGenerator[InstT, parse.LabelNode, LabelInfo] -} - -func NewFunctionGenerator[InstT BaseInstruction]() FileContextGenerator[InstT, parse.FunctionNode, *FunctionInfo[InstT]] { - return FileContextGenerator[InstT, parse.FunctionNode, *FunctionInfo[InstT]]( - &FunctionGenerator[InstT]{ - InstructionGenerator: NewInstructionGenerator[InstT](), - ParameterGenerator: NewParameterGenerator[InstT](), - LabelDefinitionGenerator: NewLabelDefinitionGenerator[InstT](), - }, - ) -} - -func (g *FunctionGenerator[InstT]) createFunctionContext( - ctx *FileGenerationContext[InstT], -) *FunctionGenerationContext[InstT] { - return &FunctionGenerationContext[InstT]{ - FileGenerationContext: ctx, - Registers: ctx.RegisterManagerCreator(), - Labels: ctx.LabelManagerCreator(), - } -} - -func (g *FunctionGenerator[InstT]) createParameterRegisters( - ctx *FunctionGenerationContext[InstT], - parameters []parse.ParameterNode, -) (registers []*RegisterInfo, results core.ResultList) { - registers = make([]*RegisterInfo, 0, len(parameters)) - - for _, parameter := range parameters { - register, curResults := g.ParameterGenerator.Generate(ctx, parameter) - results.Extend(&curResults) - registers = append(registers, register) - } - - return registers, results -} - -func (g *FunctionGenerator[InstT]) collectLabelDefinitions( - ctx *FunctionGenerationContext[InstT], - instructions []parse.InstructionNode, -) (results core.ResultList) { - - labelCtx := LabelGenerationContext[InstT]{ - FunctionGenerationContext: ctx, - CurrentInstructionIndex: 0, - } - - for _, instruction := range instructions { - for _, label := range instruction.Labels { - _, curResults := g.LabelDefinitionGenerator.Generate(&labelCtx, label) - results.Extend(&curResults) - } - - labelCtx.CurrentInstructionIndex++ - } - - return results -} - -func (g *FunctionGenerator[InstT]) generateFunctionBody( - ctx *FunctionGenerationContext[InstT], - instNodes []parse.InstructionNode, -) ([]*InstructionInfo[InstT], core.ResultList) { - instructions := make([]*InstructionInfo[InstT], 0, len(instNodes)) - - for _, instNode := range instNodes { - inst, results := g.InstructionGenerator.Generate(ctx, instNode) - if !results.IsEmpty() { - // If encountered an error in the middle of the function, it might - // effect the rest of the function (for example, a register might - // not be defined correctly, which can cause other errors further - // down the function). Thus, we return early. - return nil, results - } - - instructions = append(instructions, inst) - } - - return instructions, core.ResultList{} -} - -func (g *FunctionGenerator[InstT]) Generate( - ctx *FileGenerationContext[InstT], - node parse.FunctionNode, -) (*FunctionInfo[InstT], core.ResultList) { - var results core.ResultList - funcCtx := g.createFunctionContext(ctx) - - parameters, paramResults := g.createParameterRegisters(funcCtx, node.Signature.Parameters) - results.Extend(¶mResults) - - labelResults := g.collectLabelDefinitions(funcCtx, node.Instructions.Nodes) - results.Extend(&labelResults) - - if !results.IsEmpty() { - return nil, results - } - - instructions, results := g.generateFunctionBody(funcCtx, node.Instructions.Nodes) - if !results.IsEmpty() { - return nil, results - } - - return &FunctionInfo[InstT]{ - Instructions: instructions, - Parameters: parameters, - }, core.ResultList{} -} diff --git a/gen/function_generator.go b/gen/function_generator.go new file mode 100644 index 0000000..07a43a2 --- /dev/null +++ b/gen/function_generator.go @@ -0,0 +1,254 @@ +package gen + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/graph" + "alon.kr/x/usm/parse" +) + +type FunctionGenerator struct { + InstructionGenerator FunctionContextGenerator[parse.InstructionNode, *InstructionInfo] + ParameterGenerator FunctionContextGenerator[parse.ParameterNode, *RegisterInfo] + LabelDefinitionGenerator LabelContextGenerator[parse.LabelNode, *LabelInfo] +} + +func NewFunctionGenerator() FileContextGenerator[parse.FunctionNode, *FunctionInfo] { + return FileContextGenerator[parse.FunctionNode, *FunctionInfo]( + &FunctionGenerator{ + InstructionGenerator: NewInstructionGenerator(), + ParameterGenerator: NewParameterGenerator(), + LabelDefinitionGenerator: NewLabelDefinitionGenerator(), + }, + ) +} + +func (g *FunctionGenerator) createFunctionContext( + ctx *FileGenerationContext, +) *FunctionGenerationContext { + return &FunctionGenerationContext{ + FileGenerationContext: ctx, + Registers: ctx.RegisterManagerCreator(), + Labels: ctx.LabelManagerCreator(), + } +} + +func (g *FunctionGenerator) createParameterRegisters( + ctx *FunctionGenerationContext, + parameters []parse.ParameterNode, +) (registers []*RegisterInfo, results core.ResultList) { + registers = make([]*RegisterInfo, 0, len(parameters)) + + for _, parameter := range parameters { + register, curResults := g.ParameterGenerator.Generate(ctx, parameter) + results.Extend(&curResults) + registers = append(registers, register) + } + + return registers, results +} + +func (g *FunctionGenerator) collectLabelDefinitions( + ctx *FunctionGenerationContext, + instructions []parse.InstructionNode, +) (map[*LabelInfo]uint, core.ResultList) { + results := core.ResultList{} + labelToInstructionIndex := make(map[*LabelInfo]uint) + + labelCtx := LabelGenerationContext{ + FunctionGenerationContext: ctx, + CurrentInstructionIndex: 0, + } + + for i, instruction := range instructions { + for _, label := range instruction.Labels { + info, curResults := g.LabelDefinitionGenerator.Generate(&labelCtx, label) + labelToInstructionIndex[info] = uint(i) + results.Extend(&curResults) + } + + labelCtx.CurrentInstructionIndex++ + } + + return labelToInstructionIndex, results +} + +func (g *FunctionGenerator) generateInstructions( + ctx *FunctionGenerationContext, + instNodes []parse.InstructionNode, +) ([]*InstructionInfo, core.ResultList) { + instructions := make([]*InstructionInfo, 0, len(instNodes)) + + for _, instNode := range instNodes { + inst, results := g.InstructionGenerator.Generate(ctx, instNode) + if !results.IsEmpty() { + // If encountered an error in the middle of the function, it might + // effect the rest of the function (for example, a register might + // not be defined correctly, which can cause other errors further + // down the function). Thus, we return early. + return nil, results + } + + instructions = append(instructions, inst) + } + + return instructions, core.ResultList{} +} + +func (g *FunctionGenerator) generateInstructionsGraph( + instructions []*InstructionInfo, + labelToInstructionIndex map[*LabelInfo]uint, +) (graph.Graph, core.ResultList) { + instructionCount := uint(len(instructions)) + instructionsGraph := graph.NewEmptyGraph(instructionCount) + results := core.ResultList{} + + for i := uint(0); i < instructionCount; i++ { + info := instructions[i] + possibleNextSteps, curResults := info.Instruction.PossibleNextSteps() + if !results.IsEmpty() { + results.Extend(&curResults) + continue + } + + for _, nextStep := range possibleNextSteps { + switch typedNextStep := nextStep.(type) { + case ContinueToNextInstruction: + if i+1 >= instructionCount { + results.Append(core.Result{ + { + Type: core.ErrorResult, + Message: "Unexpected instruction to end a function", + Location: info.Declaration, + }, + { + Type: core.HintResult, + Message: "Perhaps you forgot a return instruction?", + }, + }) + continue + } + instructionsGraph.AddEdge(i, i+1) + + case JumpToLabel: + j := labelToInstructionIndex[typedNextStep.Label] + instructionsGraph.AddEdge(i, j) + + case ReturnFromFunction: + // Don't add an edge. + + default: + // notest + results.Append(core.Result{{ + Type: core.InternalErrorResult, + Message: "Unknown next step type", + Location: info.Declaration, + }}) + } + } + } + + if !results.IsEmpty() { + return graph.Graph{}, results + } + + return instructionsGraph, core.ResultList{} +} + +func (g *FunctionGenerator) generateBasicBlocks( + cfg graph.ControlFlowGraph, + instructions []*InstructionInfo, +) (blocks []*BasicBlockInfo, results core.ResultList) { + blocksCount := cfg.Size() + blocks = make([]*BasicBlockInfo, blocksCount) + + // first, initialize ("malloc") blocks so we can take references to them. + // on the way, also compute and fill any trivial fields that do not require + // references to other blocks. + for i := uint(0); i < blocksCount; i++ { + blockInstructionIndices := cfg.BasicBlockToNodes[i] + blocks[i] = NewEmptyBasicBlockInfo() + + for _, instructionIndex := range blockInstructionIndices { + blocks[i].AppendInstruction(instructions[instructionIndex]) + } + } + + // now fill in the missing edges fields. + for i := uint(0); i < blocksCount; i++ { + node := cfg.Nodes[i] + for _, j := range node.ForwardEdges { + blocks[i].ForwardEdges = append(blocks[i].ForwardEdges, blocks[j]) + } + + for _, j := range node.BackwardEdges { + blocks[i].BackwardEdges = append(blocks[i].BackwardEdges, blocks[j]) + } + } + + // finally, fill in the NextBlock field. + // We do it this way, to guarantee that the order of the blocks in the + // `NextBlock` list matches the order of them in the source code. + for i := uint(0); i < blocksCount; i++ { + lastBlockInstructionIndex := cfg.BasicBlockToNodes[i][len(cfg.BasicBlockToNodes[i])-1] + firstNextBlockInstructionIndex := lastBlockInstructionIndex + 1 + if firstNextBlockInstructionIndex >= uint(len(instructions)) { + continue // This is the last block in the function. + } + + nextBlockIndex := cfg.NodeToBasicBlock[firstNextBlockInstructionIndex] + blocks[i].NextBlock = blocks[nextBlockIndex] + } + + return blocks, core.ResultList{} +} + +func (g *FunctionGenerator) Generate( + ctx *FileGenerationContext, + node parse.FunctionNode, +) (*FunctionInfo, core.ResultList) { + var results core.ResultList + funcCtx := g.createFunctionContext(ctx) + + parameters, paramResults := g.createParameterRegisters(funcCtx, node.Signature.Parameters) + results.Extend(¶mResults) + + labelToInstructionIndex, labelResults := g.collectLabelDefinitions(funcCtx, node.Instructions.Nodes) + results.Extend(&labelResults) + + if !results.IsEmpty() { + return nil, results + } + + instructions, results := g.generateInstructions(funcCtx, node.Instructions.Nodes) + if !results.IsEmpty() { + return nil, results + } + + graph, results := g.generateInstructionsGraph(instructions, labelToInstructionIndex) + if !results.IsEmpty() { + return nil, results + } + + if graph.Size() == 0 { + v := node.View() + return nil, list.FromSingle(core.Result{{ + Type: core.ErrorResult, + Message: "Function must contain at least one instruction", + Location: &v, + }}) + } + + cfg := graph.ControlFlowGraph(0) + + blocks, results := g.generateBasicBlocks(cfg, instructions) + if !results.IsEmpty() { + return nil, results + } + + return &FunctionInfo{ + EntryBlock: blocks[0], + Parameters: parameters, + Registers: funcCtx.Registers.GetAllRegisters(), + }, core.ResultList{} +} diff --git a/gen/function_generator_test.go b/gen/function_generator_test.go new file mode 100644 index 0000000..901fb19 --- /dev/null +++ b/gen/function_generator_test.go @@ -0,0 +1,127 @@ +package gen_test + +import ( + "testing" + + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" + "alon.kr/x/usm/lex" + "alon.kr/x/usm/parse" + "github.com/stretchr/testify/assert" +) + +func generateFunctionFromSource( + t *testing.T, + source string, +) (*gen.FunctionInfo, core.ResultList) { + t.Helper() + + sourceView := core.NewSourceView(source) + + tkns, err := lex.NewTokenizer().Tokenize(sourceView) + assert.NoError(t, err) + + tknView := parse.NewTokenView(tkns) + node, result := parse.NewFunctionParser().Parse(&tknView) + assert.Nil(t, result) + + intType := &gen.NamedTypeInfo{Name: "$32", Size: 4} + + ctx := &gen.FileGenerationContext{ + GenerationContext: &testGenerationContext, + SourceContext: sourceView.Ctx(), + Types: &TypeMap{intType.Name: intType}, + } + + generator := gen.NewFunctionGenerator() + return generator.Generate(ctx, node) +} + +func TestSimpleFunctionGeneration(t *testing.T) { + src := `func $32 @add $32 %a { + %b = ADD %a $32 #1 + %c = ADD %b %a + RET + }` + + function, results := generateFunctionFromSource(t, src) + assert.True(t, results.IsEmpty()) + + assert.NotNil(t, function.EntryBlock) + assert.Nil(t, function.EntryBlock.NextBlock) + + registers := function.Registers + assert.Len(t, registers, 3) + + assert.ElementsMatch( + t, + [][]*gen.InstructionInfo{ + nil, // TODO: make this not implementation dependent. + {function.EntryBlock.Instructions[0]}, + {function.EntryBlock.Instructions[1]}, + }, + [][]*gen.InstructionInfo{ + registers[0].Definitions, + registers[1].Definitions, + registers[2].Definitions, + }, + ) + + assert.ElementsMatch( + t, + [][]*gen.InstructionInfo{ + {function.EntryBlock.Instructions[0], function.EntryBlock.Instructions[1]}, + {function.EntryBlock.Instructions[1]}, + nil, // TODO: make this not implementation dependent. + }, + [][]*gen.InstructionInfo{ + registers[0].Usages, + registers[1].Usages, + registers[2].Usages, + }, + ) +} + +func TestIfElseFunctionGeneration(t *testing.T) { + src := `func @toBool $32 %n { + JZ %n .zero + .nonzero + %bool = ADD $32 #1 $32 #0 + JMP .end + .zero + %bool = ADD $32 #0 $32 #0 + .end + RET + }` + + function, results := generateFunctionFromSource(t, src) + assert.True(t, results.IsEmpty()) + + entryBlock := function.EntryBlock + nonzeroBlock := entryBlock.NextBlock + zeroBlock := nonzeroBlock.NextBlock + endBlock := zeroBlock.NextBlock + + assert.Nil(t, endBlock.NextBlock) + + assert.ElementsMatch(t, entryBlock.ForwardEdges, []*gen.BasicBlockInfo{nonzeroBlock, zeroBlock}) +} + +func TestEmptyFunctionGeneration(t *testing.T) { + function, results := generateFunctionFromSource(t, `func @empty { }`) + assert.False(t, results.IsEmpty()) + assert.Nil(t, function) + details := results.Head.Value + assert.Contains(t, details[0].Message, "at least one instruction") +} + +func TestNoReturnFunctionGeneration(t *testing.T) { + src := `func @noReturn { + %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") +} diff --git a/gen/function_info.go b/gen/function_info.go new file mode 100644 index 0000000..a83ec53 --- /dev/null +++ b/gen/function_info.go @@ -0,0 +1,8 @@ +package gen + +type FunctionInfo struct { + EntryBlock *BasicBlockInfo + Registers []*RegisterInfo + Parameters []*RegisterInfo + // TODO: add targets +} diff --git a/gen/function_test.go b/gen/function_test.go deleted file mode 100644 index 4e38097..0000000 --- a/gen/function_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package gen_test - -import ( - "testing" - - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" - "alon.kr/x/usm/lex" - "alon.kr/x/usm/parse" - "github.com/stretchr/testify/assert" -) - -func TestFunctionGeneration(t *testing.T) { - src := core.NewSourceView( - `func $32 @add $32 %a { - %b = ADD %a $32 #1 - %c = ADD %b %a - }`, - ) - tkns, err := lex.NewTokenizer().Tokenize(src) - assert.NoError(t, err) - - // TODO: do no use parser here? test only the instruction set unit. - tknView := parse.NewTokenView(tkns) - node, result := parse.NewFunctionParser().Parse(&tknView) - assert.Nil(t, result) - - intType := &gen.NamedTypeInfo{Name: "$32", Size: 4} - - ctx := &gen.FileGenerationContext[Instruction]{ - GenerationContext: &testGenerationContext, - SourceContext: src.Ctx(), - Types: &TypeMap{intType.Name: intType}, - } - - generator := gen.NewFunctionGenerator[Instruction]() - _, results := generator.Generate(ctx, node) - assert.True(t, results.IsEmpty()) -} diff --git a/gen/generator.go b/gen/generator.go index a9b160f..f8d00f8 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -18,7 +18,7 @@ type ManagerCreators struct { // and which are essential across the whole pipeline. // // It mainly contains information about the compilation target. -type GenerationContext[InstT BaseInstruction] struct { +type GenerationContext struct { ManagerCreators // An instruction (definition) manager, which contains all instruction @@ -27,7 +27,7 @@ type GenerationContext[InstT BaseInstruction] struct { // When processing a new instruction from the source code, the compiler // talks with the instruction manager, retrieves the relevant instruction // definition, and uses it to farther process the instruction. - Instructions InstructionManager[InstT] + Instructions InstructionManager // The size of a pointer type in the current target architecture. // @@ -36,8 +36,8 @@ type GenerationContext[InstT BaseInstruction] struct { PointerSize core.UsmUint } -type FileGenerationContext[InstT BaseInstruction] struct { - *GenerationContext[InstT] +type FileGenerationContext struct { + *GenerationContext // The source code of the file that we are currently processing. core.SourceContext @@ -50,8 +50,8 @@ type FileGenerationContext[InstT BaseInstruction] struct { // TODO: add globals, variables, constants. } -type FunctionGenerationContext[InstT BaseInstruction] struct { - *FileGenerationContext[InstT] +type FunctionGenerationContext struct { + *FileGenerationContext // A register manager, which contains all active registers in the function. // The compiler can query register information from the register manager @@ -63,8 +63,16 @@ type FunctionGenerationContext[InstT BaseInstruction] struct { Labels LabelManager } -type LabelGenerationContext[InstT BaseInstruction] struct { - *FunctionGenerationContext[InstT] +type InstructionGenerationContext struct { + *FunctionGenerationContext + + // A (partial) instruction info type for which we are currently working on + // generating. + InstructionInfo *InstructionInfo +} + +type LabelGenerationContext struct { + *FunctionGenerationContext // The index of the instruction which is currently being iterated upon. // @@ -76,45 +84,52 @@ type LabelGenerationContext[InstT BaseInstruction] struct { // MARK: Generator -type Generator[InstT BaseInstruction, NodeT parse.Node, InfoT any] interface { +type Generator[NodeT parse.Node, InfoT any] interface { + Generate( + ctx *GenerationContext, + node NodeT, + ) (info InfoT, results core.ResultList) +} + +type FileContextGenerator[NodeT parse.Node, InfoT any] interface { Generate( - ctx *GenerationContext[InstT], + ctx *FileGenerationContext, node NodeT, ) (info InfoT, results core.ResultList) } -type FileContextGenerator[InstT BaseInstruction, NodeT parse.Node, InfoT any] interface { +type FunctionContextGenerator[NodeT parse.Node, InfoT any] interface { Generate( - ctx *FileGenerationContext[InstT], + ctx *FunctionGenerationContext, node NodeT, ) (info InfoT, results core.ResultList) } -type FunctionContextGenerator[InstT BaseInstruction, NodeT parse.Node, InfoT any] interface { +type InstructionContextGenerator[NodeT parse.Node, InfoT any] interface { Generate( - ctx *FunctionGenerationContext[InstT], + ctx *InstructionGenerationContext, node NodeT, ) (info InfoT, results core.ResultList) } -type LabelContextGenerator[InstT BaseInstruction, NodeT parse.Node, InfoT any] interface { +type LabelContextGenerator[NodeT parse.Node, InfoT any] interface { Generate( - ctx *LabelGenerationContext[InstT], + ctx *LabelGenerationContext, node NodeT, ) (info InfoT, results core.ResultList) } // MARK: Utils -func viewToSourceString[InstT BaseInstruction]( - ctx *FileGenerationContext[InstT], +func viewToSourceString( + ctx *FileGenerationContext, view core.UnmanagedSourceView, ) string { return string(view.Raw(ctx.SourceContext)) } -func nodeToSourceString[InstT BaseInstruction]( - ctx *FileGenerationContext[InstT], +func nodeToSourceString( + ctx *FileGenerationContext, node parse.Node, ) string { return viewToSourceString(ctx, node.View()) diff --git a/gen/immediate.go b/gen/immediate_generator.go similarity index 52% rename from gen/immediate.go rename to gen/immediate_generator.go index 4feb105..e3dd47e 100644 --- a/gen/immediate.go +++ b/gen/immediate_generator.go @@ -8,39 +8,20 @@ import ( "alon.kr/x/usm/parse" ) -// MARK: Info - -type ImmediateInfo struct { - Type ReferencedTypeInfo - Value *big.Int // TODO: Add floating types - declaration core.UnmanagedSourceView - // TODO: more complex and complete representation of immediate structs. -} - -func (i *ImmediateInfo) GetType() *ReferencedTypeInfo { - return &i.Type -} - -func (i *ImmediateInfo) Declaration() core.UnmanagedSourceView { - return i.declaration -} - -// MARK: Generator - -type ImmediateArgumentGenerator[InstT BaseInstruction] struct { - ReferencedTypeGenerator FileContextGenerator[InstT, parse.TypeNode, ReferencedTypeInfo] +type ImmediateArgumentGenerator struct { + ReferencedTypeGenerator FileContextGenerator[parse.TypeNode, ReferencedTypeInfo] } -func NewImmediateArgumentGenerator[InstT BaseInstruction]() FunctionContextGenerator[InstT, parse.ImmediateNode, ArgumentInfo] { - return FunctionContextGenerator[InstT, parse.ImmediateNode, ArgumentInfo]( - &ImmediateArgumentGenerator[InstT]{ - ReferencedTypeGenerator: NewReferencedTypeGenerator[InstT](), +func NewImmediateArgumentGenerator() InstructionContextGenerator[parse.ImmediateNode, ArgumentInfo] { + return InstructionContextGenerator[parse.ImmediateNode, ArgumentInfo]( + &ImmediateArgumentGenerator{ + ReferencedTypeGenerator: NewReferencedTypeGenerator(), }, ) } -func (g *ImmediateArgumentGenerator[InstT]) Generate( - ctx *FunctionGenerationContext[InstT], +func (g *ImmediateArgumentGenerator) Generate( + ctx *InstructionGenerationContext, node parse.ImmediateNode, ) (ArgumentInfo, core.ResultList) { typeInfo, results := g.ReferencedTypeGenerator.Generate( diff --git a/gen/immediate_test.go b/gen/immediate_generator_test.go similarity index 67% rename from gen/immediate_test.go rename to gen/immediate_generator_test.go index 8e0cdbd..0785833 100644 --- a/gen/immediate_test.go +++ b/gen/immediate_generator_test.go @@ -25,16 +25,19 @@ func TestImmediateValueArgument(t *testing.T) { intType := &gen.NamedTypeInfo{Name: "$32", Size: 4} types := TypeMap{intType.Name: intType} - ctx := gen.FunctionGenerationContext[Instruction]{ - FileGenerationContext: &gen.FileGenerationContext[Instruction]{ - GenerationContext: &testGenerationContext, - SourceContext: src.Ctx(), - Types: &types, + ctx := gen.InstructionGenerationContext{ + FunctionGenerationContext: &gen.FunctionGenerationContext{ + FileGenerationContext: &gen.FileGenerationContext{ + GenerationContext: &testGenerationContext, + SourceContext: src.Ctx(), + Types: &types, + }, + Registers: &RegisterMap{}, }, - Registers: &RegisterMap{}, + InstructionInfo: gen.NewEmptyInstructionInfo(&unmanaged), } - generator := gen.NewImmediateArgumentGenerator[Instruction]() + generator := gen.NewImmediateArgumentGenerator() argument, results := generator.Generate(&ctx, node) assert.True(t, results.IsEmpty()) diff --git a/gen/immediate_info.go b/gen/immediate_info.go new file mode 100644 index 0000000..8ee5edd --- /dev/null +++ b/gen/immediate_info.go @@ -0,0 +1,22 @@ +package gen + +import ( + "math/big" + + "alon.kr/x/usm/core" +) + +type ImmediateInfo struct { + Type ReferencedTypeInfo + Value *big.Int // TODO: Add floating types + declaration core.UnmanagedSourceView + // TODO: more complex and complete representation of immediate structs. +} + +func (i *ImmediateInfo) GetType() *ReferencedTypeInfo { + return &i.Type +} + +func (i *ImmediateInfo) Declaration() core.UnmanagedSourceView { + return i.declaration +} diff --git a/gen/instruction_definition.go b/gen/instruction_definition.go new file mode 100644 index 0000000..75ad68e --- /dev/null +++ b/gen/instruction_definition.go @@ -0,0 +1,39 @@ +package gen + +import "alon.kr/x/usm/core" + +type BaseInstruction interface { + // This method is usd by the USM engine to generate the internal control + // flow graph representation. + // + // Should return a non-empty slice. If the instruction does not have any + // consecutive steps in the function (for example, a return statement), + // then a special dedicated return step should be returned. + PossibleNextSteps() ([]StepInfo, core.ResultList) +} + +// A basic instruction definition. This defines the logic that converts the +// generic, architecture / instruction set independent instruction AST nodes +// into a format instruction which is part of a specific instruction set. +type InstructionDefinition interface { + // Build an instruction from the provided instruction information. + BuildInstruction(info *InstructionInfo) (BaseInstruction, core.ResultList) + + // Provided a list a list of types that correspond to argument types, + // and a (possibly partial) list of target types, return a complete list + // of target types which is implicitly inferred from the argument types, + // and possibly the explicit target types, or an error if the target types + // can not be inferred. + // + // On success, the length of the returned type slice should be equal to the + // provided (partial) targets length. The non nil provided target types + // should not be modified. + // + // TODO: perhaps we should not pass the bare generation context to the "public" + // instruction set definition API, and should wrap it with a limited interface. + InferTargetTypes( + ctx *FunctionGenerationContext, + targets []*ReferencedTypeInfo, + arguments []*ReferencedTypeInfo, + ) ([]ReferencedTypeInfo, core.ResultList) +} diff --git a/gen/instruction.go b/gen/instruction_generator.go similarity index 52% rename from gen/instruction.go rename to gen/instruction_generator.go index 5cd7f1f..b801f31 100644 --- a/gen/instruction.go +++ b/gen/instruction_generator.go @@ -6,83 +6,28 @@ import ( "alon.kr/x/usm/parse" ) -// TODO: add basic interface methods for instruction. -type BaseInstruction interface{} - -// A basic instruction definition. This defines the logic that converts the -// generic, architecture / instruction set independent instruction AST nodes -// into a format instruction which is part of a specific instruction set. -type InstructionDefinition[InstT BaseInstruction] interface { - // Build an instruction from the provided targets and arguments. - BuildInstruction( - targets []*RegisterArgumentInfo, - arguments []ArgumentInfo, - ) (InstT, core.ResultList) - - // Provided a list a list of types that correspond to argument types, - // and a (possibly partial) list of target types, return a complete list - // of target types which is implicitly inferred from the argument types, - // and possibly the explicit target types, or an error if the target types - // can not be inferred. - // - // On success, the length of the returned type slice should be equal to the - // provided (partial) targets length. The non nil provided target types - // should not be modified. - // - // TODO: perhaps we should not pass the bare generation context to the "public" - // instruction set definition API, and should wrap it with a limited interface. - InferTargetTypes( - ctx *FunctionGenerationContext[InstT], - targets []*ReferencedTypeInfo, - arguments []*ReferencedTypeInfo, - ) ([]ReferencedTypeInfo, core.ResultList) +type InstructionGenerator struct { + ArgumentGenerator InstructionContextGenerator[parse.ArgumentNode, ArgumentInfo] + TargetGenerator InstructionContextGenerator[parse.TargetNode, registerPartialInfo] } -type InstructionInfo[InstT BaseInstruction] struct { - // The actual instruction instance, which is part of the instruction set, - // and not part of this package. - Instruction InstT - - // The targets of the instruction. - Targets []*RegisterArgumentInfo - - // The arguments of the instruction. - Arguments []ArgumentInfo -} - -// MARK: Manager - -type InstructionManager[InstT BaseInstruction] interface { - // Get the instruction definition that corresponds to the provided name. - GetInstructionDefinition(name string) (InstructionDefinition[InstT], core.ResultList) -} - -// MARK: Generator - -type InstructionGenerator[InstT BaseInstruction] struct { - ArgumentGenerator FunctionContextGenerator[InstT, parse.ArgumentNode, ArgumentInfo] - TargetGenerator FunctionContextGenerator[InstT, parse.TargetNode, partialRegisterInfo] -} - -func NewInstructionGenerator[InstT BaseInstruction]() FunctionContextGenerator[ - InstT, +func NewInstructionGenerator() FunctionContextGenerator[ parse.InstructionNode, - *InstructionInfo[InstT], + *InstructionInfo, ] { return FunctionContextGenerator[ - InstT, parse.InstructionNode, - *InstructionInfo[InstT], + *InstructionInfo, ]( - &InstructionGenerator[InstT]{ - ArgumentGenerator: NewArgumentGenerator[InstT](), - TargetGenerator: NewTargetGenerator[InstT](), + &InstructionGenerator{ + ArgumentGenerator: NewArgumentGenerator(), + TargetGenerator: NewTargetGenerator(), }, ) } -func (g *InstructionGenerator[InstT]) generateArguments( - ctx *FunctionGenerationContext[InstT], +func (g *InstructionGenerator) generateArguments( + ctx *InstructionGenerationContext, node parse.InstructionNode, ) ([]ArgumentInfo, core.ResultList) { arguments := make([]ArgumentInfo, len(node.Arguments)) @@ -100,11 +45,11 @@ func (g *InstructionGenerator[InstT]) generateArguments( return arguments, results } -func (g *InstructionGenerator[InstT]) generatePartialTargetsInfo( - ctx *FunctionGenerationContext[InstT], +func (g *InstructionGenerator) generatePartialTargetsInfo( + ctx *InstructionGenerationContext, node parse.InstructionNode, -) ([]partialRegisterInfo, core.ResultList) { - targets := make([]partialRegisterInfo, len(node.Targets)) +) ([]registerPartialInfo, core.ResultList) { + targets := make([]registerPartialInfo, len(node.Targets)) results := core.ResultList{} // Different targets should not effect one another. @@ -119,7 +64,30 @@ func (g *InstructionGenerator[InstT]) generatePartialTargetsInfo( return targets, results } -func partialTargetsToTypes(targets []partialRegisterInfo) []*ReferencedTypeInfo { +func (g *InstructionGenerator) generateLabels( + ctx *InstructionGenerationContext, + node parse.InstructionNode, +) ([]*LabelInfo, core.ResultList) { + labels := make([]*LabelInfo, 0, len(node.Labels)) + for _, node := range node.Labels { + name := nodeToSourceString(ctx.FileGenerationContext, node) + label := ctx.Labels.GetLabel(name) + if label == nil { + v := node.View() + return nil, list.FromSingle(core.Result{{ + Type: core.ErrorResult, + Message: "Label does not exist", + Location: &v, + }}) + } + + labels = append(labels, label) + } + + return labels, core.ResultList{} +} + +func partialTargetsToTypes(targets []registerPartialInfo) []*ReferencedTypeInfo { types := make([]*ReferencedTypeInfo, len(targets)) for i, target := range targets { types[i] = target.Type @@ -135,11 +103,11 @@ func argumentsToTypes(arguments []ArgumentInfo) []*ReferencedTypeInfo { return types } -func (g *InstructionGenerator[InstT]) getTargetRegister( - ctx *FunctionGenerationContext[InstT], +func (g *InstructionGenerator) getTargetRegister( + ctx *InstructionGenerationContext, node parse.TargetNode, targetType ReferencedTypeInfo, -) (*RegisterArgumentInfo, core.Result) { +) (*RegisterInfo, core.Result) { registerName := nodeToSourceString(ctx.FileGenerationContext, node.Register) registerInfo := ctx.Registers.GetRegister(registerName) nodeView := node.View() @@ -153,16 +121,11 @@ func (g *InstructionGenerator[InstT]) getTargetRegister( Declaration: nodeView, } - registerArgument := &RegisterArgumentInfo{ - Register: newRegisterInfo, - declaration: nodeView, - } - - return registerArgument, ctx.Registers.NewRegister(newRegisterInfo) + return newRegisterInfo, ctx.Registers.NewRegister(newRegisterInfo) } // register is already defined - if !registerInfo.Type.Equals(targetType) { + if !registerInfo.Type.Equal(targetType) { // notest: sanity check only return nil, core.Result{{ Type: core.InternalErrorResult, @@ -171,12 +134,7 @@ func (g *InstructionGenerator[InstT]) getTargetRegister( }} } - registerArgument := &RegisterArgumentInfo{ - Register: registerInfo, - declaration: nodeView, - } - - return registerArgument, nil + return registerInfo, nil } // Registers can be defined by being a target of an instruction. @@ -187,8 +145,8 @@ func (g *InstructionGenerator[InstT]) getTargetRegister( // // This also returns the full list of register targets for the provided // instruction. -func (g *InstructionGenerator[InstT]) defineAndGetTargetRegisters( - ctx *FunctionGenerationContext[InstT], +func (g *InstructionGenerator) defineAndGetTargetRegisters( + ctx *InstructionGenerationContext, node parse.InstructionNode, targetTypes []ReferencedTypeInfo, ) ([]*RegisterArgumentInfo, core.ResultList) { @@ -206,7 +164,12 @@ func (g *InstructionGenerator[InstT]) defineAndGetTargetRegisters( results := core.ResultList{} for i, target := range node.Targets { // register errors should not effect one another, so we collect them. - registerInfo, result := g.getTargetRegister(ctx, target, targetTypes[i]) + registerInfo, result := g.getTargetRegister( + ctx, + target, + targetTypes[i], + ) + if result != nil { results.Append(result) } @@ -221,7 +184,13 @@ func (g *InstructionGenerator[InstT]) defineAndGetTargetRegisters( }}) } - registers[i] = registerInfo + registerArgument := &RegisterArgumentInfo{ + Register: registerInfo, + declaration: node.View(), + } + + registerInfo.AddDefinition(ctx.InstructionInfo) + registers[i] = registerArgument } return registers, results @@ -232,52 +201,63 @@ func (g *InstructionGenerator[InstT]) defineAndGetTargetRegisters( // If new registers are defined in the instruction (by assigning values to // instruction targets), the register is created and added to the generation // context. -func (g *InstructionGenerator[InstT]) Generate( - ctx *FunctionGenerationContext[InstT], +func (g *InstructionGenerator) Generate( + ctx *FunctionGenerationContext, node parse.InstructionNode, -) (info *InstructionInfo[InstT], results core.ResultList) { +) (*InstructionInfo, core.ResultList) { // We start generating the instruction, by getting the definition interface, // 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, + InstructionInfo: NewEmptyInstructionInfo(&v), + } + instName := viewToSourceString(ctx.FileGenerationContext, node.Operator) - instDef, results := ctx.Instructions.GetInstructionDefinition(instName) + instDef, results := ctx.Instructions.GetInstructionDefinition(instName, node) - arguments, curResults := g.generateArguments(ctx, node) + arguments, curResults := g.generateArguments(&instCtx, node) results.Extend(&curResults) - partialTargets, curResults := g.generatePartialTargetsInfo(ctx, node) + partialTargets, curResults := g.generatePartialTargetsInfo(&instCtx, node) results.Extend(&curResults) + labels, curResults := g.generateLabels(&instCtx, node) + // Now it's time to check if we have any errors so far. if !results.IsEmpty() { - return + 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 + return nil, results } - targets, results := g.defineAndGetTargetRegisters(ctx, node, targetTypes) - if !results.IsEmpty() { - return - } + targets, results := g.defineAndGetTargetRegisters(&instCtx, node, targetTypes) - instruction, results := instDef.BuildInstruction(targets, arguments) if !results.IsEmpty() { - return + return nil, results } - info = &InstructionInfo[InstT]{ - Instruction: instruction, - Targets: targets, - Arguments: arguments, + instCtx.InstructionInfo.Targets = targets + + instruction, results := instDef.BuildInstruction(instCtx.InstructionInfo) + if !results.IsEmpty() { + return nil, results } - return info, core.ResultList{} + instCtx.InstructionInfo.Instruction = instruction + return instCtx.InstructionInfo, core.ResultList{} } diff --git a/gen/instruction_generator_test.go b/gen/instruction_generator_test.go new file mode 100644 index 0000000..0b1ef6c --- /dev/null +++ b/gen/instruction_generator_test.go @@ -0,0 +1,206 @@ +package gen_test + +import ( + "testing" + + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" + "alon.kr/x/usm/lex" + "alon.kr/x/usm/parse" + "github.com/stretchr/testify/assert" +) + +// MARK: Add + +type AddInstruction struct{} + +func (i *AddInstruction) PossibleNextSteps() ([]gen.StepInfo, core.ResultList) { + return []gen.StepInfo{gen.ContinueToNextInstruction{}}, core.ResultList{} +} + +type AddInstructionDefinition struct{} + +func (AddInstructionDefinition) BuildInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&AddInstruction{}), core.ResultList{} +} + +func (AddInstructionDefinition) InferTargetTypes( + ctx *gen.FunctionGenerationContext, + targets []*gen.ReferencedTypeInfo, + arguments []*gen.ReferencedTypeInfo, +) ([]gen.ReferencedTypeInfo, core.ResultList) { + if len(arguments) != 2 { + return nil, list.FromSingle(core.Result{{ + Type: core.ErrorResult, + Message: "expected exactly 2 arguments", + }}) + } + + if len(targets) != 1 { + return nil, list.FromSingle(core.Result{{ + Type: core.ErrorResult, + Message: "expected exactly 1 target", + }}) + } + + // TODO: possible panic? + if !arguments[0].Equal(*arguments[1]) { + return nil, list.FromSingle(core.Result{{ + Type: core.ErrorResult, + Message: "expected both arguments to be of the same type", + }}) + } + + return []gen.ReferencedTypeInfo{ + { + Base: arguments[0].Base, + Descriptors: arguments[0].Descriptors, + }, + }, core.ResultList{} +} + +// MARK: Ret + +type RetInstruction struct{} + +func (i *RetInstruction) PossibleNextSteps() ([]gen.StepInfo, core.ResultList) { + return []gen.StepInfo{gen.ReturnFromFunction{}}, core.ResultList{} +} + +type RetInstructionDefinition struct{} + +func (RetInstructionDefinition) BuildInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&RetInstruction{}), core.ResultList{} +} + +func (RetInstructionDefinition) InferTargetTypes( + ctx *gen.FunctionGenerationContext, + targets []*gen.ReferencedTypeInfo, + arguments []*gen.ReferencedTypeInfo, +) ([]gen.ReferencedTypeInfo, core.ResultList) { + return []gen.ReferencedTypeInfo{}, core.ResultList{} +} + +// MARK: Jump + +type JumpInstruction struct { + *gen.InstructionInfo +} + +func (i *JumpInstruction) PossibleNextSteps() ([]gen.StepInfo, core.ResultList) { + return []gen.StepInfo{gen.JumpToLabel{ + Label: i.Arguments[0].(*gen.LabelArgumentInfo).Label, + }}, core.ResultList{} +} + +type JumpInstructionDefinition struct{} + +func (JumpInstructionDefinition) BuildInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&JumpInstruction{info}), core.ResultList{} +} + +func (JumpInstructionDefinition) InferTargetTypes( + ctx *gen.FunctionGenerationContext, + targets []*gen.ReferencedTypeInfo, + arguments []*gen.ReferencedTypeInfo, +) ([]gen.ReferencedTypeInfo, core.ResultList) { + return []gen.ReferencedTypeInfo{}, core.ResultList{} +} + +// MARK: Jump Zero + +// JZ %condition .label +type JumpZeroInstruction struct { + *gen.InstructionInfo +} + +func (i *JumpZeroInstruction) PossibleNextSteps() ([]gen.StepInfo, core.ResultList) { + return []gen.StepInfo{ + gen.JumpToLabel{Label: i.Arguments[1].(*gen.LabelArgumentInfo).Label}, + gen.ContinueToNextInstruction{}, + }, core.ResultList{} +} + +type JumpZeroInstructionDefinition struct{} + +func (JumpZeroInstructionDefinition) BuildInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&JumpZeroInstruction{info}), core.ResultList{} +} + +func (JumpZeroInstructionDefinition) InferTargetTypes( + ctx *gen.FunctionGenerationContext, + targets []*gen.ReferencedTypeInfo, + arguments []*gen.ReferencedTypeInfo, +) ([]gen.ReferencedTypeInfo, core.ResultList) { + return []gen.ReferencedTypeInfo{}, core.ResultList{} +} + +// MARK: Instruction Map + +type InstructionMap map[string]gen.InstructionDefinition + +func (m *InstructionMap) GetInstructionDefinition( + name string, + node parse.InstructionNode, +) (gen.InstructionDefinition, core.ResultList) { + instDef, ok := (*m)[name] + if !ok { + return nil, list.FromSingle(core.Result{{ + Type: core.ErrorResult, + Message: "undefined instruction", + Location: &node.Operator, + }}) + } + return instDef, core.ResultList{} +} + +func TestInstructionCreateTarget(t *testing.T) { + src := core.NewSourceView("%c = ADD %a %b\n") + tkns, err := lex.NewTokenizer().Tokenize(src) + assert.NoError(t, err) + + // TODO: do no use parser here? test only the instruction set unit. + tknView := parse.NewTokenView(tkns) + node, result := parse.NewInstructionParser().Parse(&tknView) + assert.Nil(t, result) + + intType := &gen.NamedTypeInfo{Name: "$32", Size: 4} + types := TypeMap{intType.Name: intType} + + intTypeRef := gen.ReferencedTypeInfo{ + Base: intType, + } + + registers := RegisterMap{ + "%a": &gen.RegisterInfo{Name: "%a", Type: intTypeRef}, + "%b": &gen.RegisterInfo{Name: "%b", Type: intTypeRef}, + } + + ctx := &gen.FunctionGenerationContext{ + FileGenerationContext: &gen.FileGenerationContext{ + GenerationContext: &testGenerationContext, + SourceContext: src.Ctx(), + Types: &types, + }, + Registers: ®isters, + } + + generator := gen.NewInstructionGenerator() + _, results := generator.Generate(ctx, node) + assert.True(t, results.IsEmpty()) + + target := registers.GetRegister("%c") + 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) +} diff --git a/gen/instruction_info.go b/gen/instruction_info.go new file mode 100644 index 0000000..1c9c471 --- /dev/null +++ b/gen/instruction_info.go @@ -0,0 +1,44 @@ +package gen + +import "alon.kr/x/usm/core" + +type InstructionInfo struct { + *BasicBlockInfo + + // The targets of the instruction. + Targets []*RegisterArgumentInfo + + // The arguments of the instruction. + Arguments []ArgumentInfo + + // The labels that point directly to this instruction. + Labels []*LabelInfo + + // The actual instruction information, which is ISA specific. + Instruction BaseInstruction + + // The location in which the instruction was defined in the source code. + // Can be nil if the instruction was defined internally, for example, + // in an optimization. + Declaration *core.UnmanagedSourceView +} + +func NewEmptyInstructionInfo( + declaration *core.UnmanagedSourceView, +) *InstructionInfo { + return &InstructionInfo{ + BasicBlockInfo: nil, + Targets: []*RegisterArgumentInfo{}, + Arguments: []ArgumentInfo{}, + Labels: []*LabelInfo{}, + Instruction: nil, + Declaration: declaration, + } +} + +func (i *InstructionInfo) LinkToBasicBlock(basicBlock *BasicBlockInfo) { + i.BasicBlockInfo = basicBlock + for _, label := range i.Labels { + label.LinkToBasicBlock(basicBlock) + } +} diff --git a/gen/instruction_manager.go b/gen/instruction_manager.go new file mode 100644 index 0000000..c4f5f6b --- /dev/null +++ b/gen/instruction_manager.go @@ -0,0 +1,17 @@ +package gen + +import ( + "alon.kr/x/usm/core" + "alon.kr/x/usm/parse" +) + +type InstructionManager interface { + // Get the instruction definition that corresponds to the provided name. + // + // Instruction node is for extra context, if needed, especially for + // generating nice error messages. + GetInstructionDefinition( + name string, + node parse.InstructionNode, + ) (InstructionDefinition, core.ResultList) +} diff --git a/gen/instruction_test.go b/gen/instruction_test.go deleted file mode 100644 index ddf9905..0000000 --- a/gen/instruction_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package gen_test - -import ( - "testing" - - "alon.kr/x/list" - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" - "alon.kr/x/usm/lex" - "alon.kr/x/usm/parse" - "github.com/stretchr/testify/assert" -) - -type Instruction struct{} - -type AddInstructionDefinition struct{} - -func (AddInstructionDefinition) BuildInstruction( - targets []*gen.RegisterArgumentInfo, - arguments []gen.ArgumentInfo, -) (Instruction, core.ResultList) { - return Instruction{}, core.ResultList{} -} - -func (AddInstructionDefinition) InferTargetTypes( - ctx *gen.FunctionGenerationContext[Instruction], - targets []*gen.ReferencedTypeInfo, - arguments []*gen.ReferencedTypeInfo, -) ([]gen.ReferencedTypeInfo, core.ResultList) { - if len(arguments) != 2 { - return nil, list.FromSingle(core.Result{{ - Type: core.ErrorResult, - Message: "expected exactly 2 arguments", - }}) - } - - if len(targets) != 1 { - return nil, list.FromSingle(core.Result{{ - Type: core.ErrorResult, - Message: "expected exactly 1 target", - }}) - } - - // TODO: possible panic? - if !arguments[0].Equals(*arguments[1]) { - return nil, list.FromSingle(core.Result{{ - Type: core.ErrorResult, - Message: "expected both arguments to be of the same type", - }}) - } - - return []gen.ReferencedTypeInfo{ - { - Base: arguments[0].Base, - Descriptors: arguments[0].Descriptors, - }, - }, core.ResultList{} -} - -type InstructionMap map[string]gen.InstructionDefinition[Instruction] - -func (m *InstructionMap) GetInstructionDefinition( - name string, -) (gen.InstructionDefinition[Instruction], core.ResultList) { - instDef, ok := (*m)[name] - if !ok { - return nil, list.FromSingle(core.Result{{ - Type: core.ErrorResult, - Message: "undefined instruction", - }}) - } - return instDef, core.ResultList{} -} - -func TestInstructionCreateTarget(t *testing.T) { - src := core.NewSourceView("%c = ADD %a %b\n") - tkns, err := lex.NewTokenizer().Tokenize(src) - assert.NoError(t, err) - - // TODO: do no use parser here? test only the instruction set unit. - tknView := parse.NewTokenView(tkns) - node, result := parse.NewInstructionParser().Parse(&tknView) - assert.Nil(t, result) - - intType := &gen.NamedTypeInfo{Name: "$32", Size: 4} - types := TypeMap{intType.Name: intType} - - intTypeRef := gen.ReferencedTypeInfo{ - Base: intType, - } - - registers := RegisterMap{ - "%a": &gen.RegisterInfo{Name: "%a", Type: intTypeRef}, - "%b": &gen.RegisterInfo{Name: "%b", Type: intTypeRef}, - } - - ctx := &gen.FunctionGenerationContext[Instruction]{ - FileGenerationContext: &gen.FileGenerationContext[Instruction]{ - GenerationContext: &testGenerationContext, - SourceContext: src.Ctx(), - Types: &types, - }, - Registers: ®isters, - } - - generator := gen.NewInstructionGenerator[Instruction]() - _, results := generator.Generate(ctx, node) - assert.True(t, results.IsEmpty()) - - target := registers.GetRegister("%c") - 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) -} diff --git a/gen/label.go b/gen/label.go deleted file mode 100644 index 36eab89..0000000 --- a/gen/label.go +++ /dev/null @@ -1,83 +0,0 @@ -// Labels are abstracted from the "backend". -// The "frontend" (gen module) iterates over all local function labels, -// and provides the labels interface (arguments to instructions) as pointers -// to other instructions in the same function scope. - -package gen - -import ( - "alon.kr/x/list" - "alon.kr/x/usm/core" - "alon.kr/x/usm/parse" -) - -// MARK: Info - -type LabelInfo struct { - // The name of the label, as it appears in the source code. - Name string - - // The index of the instruction that the label is referencing. - InstructionIndex core.UsmUint - - // A view of the label declaration in the source code. - Declaration core.UnmanagedSourceView -} - -// MARK: Manager - -type LabelManager interface { - GetLabel(name string) *LabelInfo - NewLabel(info LabelInfo) core.Result -} - -// MARK: Generator - -// Generator for label *definition* nodes. -// -// Will create and add the label to the function context, -// and return an error if a label with the same name already exists. -type LabelDefinitionGenerator[InstT BaseInstruction] struct{} - -func NewLabelDefinitionGenerator[InstT BaseInstruction]() LabelContextGenerator[InstT, parse.LabelNode, LabelInfo] { - return LabelContextGenerator[InstT, parse.LabelNode, LabelInfo]( - &LabelDefinitionGenerator[InstT]{}, - ) -} - -func (g *LabelDefinitionGenerator[InstT]) Generate( - ctx *LabelGenerationContext[InstT], - node parse.LabelNode, -) (LabelInfo, core.ResultList) { - name := nodeToSourceString(ctx.FileGenerationContext, node) - labelInfo := ctx.Labels.GetLabel(name) - declaration := node.View() - - if labelInfo != nil { - return LabelInfo{}, list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: "Label already defined", - Location: &declaration, - }, - { - Type: core.HintResult, - Message: "Previous definition here", - Location: &labelInfo.Declaration, - }, - }) - } - - newLabelInfo := LabelInfo{ - Name: name, - InstructionIndex: ctx.CurrentInstructionIndex, - Declaration: declaration, - } - - result := ctx.Labels.NewLabel(newLabelInfo) - if result != nil { - return LabelInfo{}, list.FromSingle(result) - } - - return newLabelInfo, core.ResultList{} -} diff --git a/gen/label_argument.go b/gen/label_argument.go index 511cdc5..89bb0b7 100644 --- a/gen/label_argument.go +++ b/gen/label_argument.go @@ -9,7 +9,7 @@ import ( // MARK: Info type LabelArgumentInfo struct { - Label LabelInfo + Label *LabelInfo declaration core.UnmanagedSourceView } @@ -23,16 +23,16 @@ func (i *LabelArgumentInfo) Declaration() core.UnmanagedSourceView { // MARK: Generator -type LabelArgumentGenerator[InstT BaseInstruction] struct{} +type LabelArgumentGenerator struct{} -func NewLabelArgumentGenerator[InstT BaseInstruction]() FunctionContextGenerator[InstT, parse.LabelNode, ArgumentInfo] { - return FunctionContextGenerator[InstT, parse.LabelNode, ArgumentInfo]( - &LabelArgumentGenerator[InstT]{}, +func NewLabelArgumentGenerator() InstructionContextGenerator[parse.LabelNode, ArgumentInfo] { + return InstructionContextGenerator[parse.LabelNode, ArgumentInfo]( + &LabelArgumentGenerator{}, ) } -func (g *LabelArgumentGenerator[InstT]) Generate( - ctx *FunctionGenerationContext[InstT], +func (g *LabelArgumentGenerator) Generate( + ctx *InstructionGenerationContext, node parse.LabelNode, ) (ArgumentInfo, core.ResultList) { name := nodeToSourceString(ctx.FileGenerationContext, node) @@ -50,7 +50,7 @@ func (g *LabelArgumentGenerator[InstT]) Generate( } argument := &LabelArgumentInfo{ - Label: *labelInfo, + Label: labelInfo, declaration: node.View(), } diff --git a/gen/label_definition_generator.go b/gen/label_definition_generator.go new file mode 100644 index 0000000..dc787bc --- /dev/null +++ b/gen/label_definition_generator.go @@ -0,0 +1,59 @@ +package gen + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/parse" +) + +// Generator for label *definition* nodes. +// +// Will create and add the label to the function context, +// and return an error if a label with the same name already exists. +type LabelDefinitionGenerator struct{} + +func NewLabelDefinitionGenerator() LabelContextGenerator[ + parse.LabelNode, + *LabelInfo, +] { + return LabelContextGenerator[parse.LabelNode, *LabelInfo]( + &LabelDefinitionGenerator{}, + ) +} + +func (g *LabelDefinitionGenerator) Generate( + ctx *LabelGenerationContext, + node parse.LabelNode, +) (*LabelInfo, core.ResultList) { + name := nodeToSourceString(ctx.FileGenerationContext, node) + labelInfo := ctx.Labels.GetLabel(name) + declaration := node.View() + + if labelInfo != nil { + return nil, list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Label already defined", + Location: &declaration, + }, + { + Type: core.HintResult, + Message: "Previous definition here", + Location: &labelInfo.Declaration, + }, + }) + } + + newLabelInfo := &LabelInfo{ + Name: name, + BasicBlock: nil, // Defined later in compilation. + Declaration: declaration, + } + + result := ctx.Labels.NewLabel(newLabelInfo) + if result != nil { + return nil, list.FromSingle(result) + } + + return newLabelInfo, core.ResultList{} +} diff --git a/gen/label_info.go b/gen/label_info.go new file mode 100644 index 0000000..607f06f --- /dev/null +++ b/gen/label_info.go @@ -0,0 +1,18 @@ +package gen + +import "alon.kr/x/usm/core" + +type LabelInfo struct { + // The name of the label, as it appears in the source code. + Name string + + // The basic block to which the instruction points. + BasicBlock *BasicBlockInfo + + // A view of the label declaration in the source code. + Declaration core.UnmanagedSourceView +} + +func (l *LabelInfo) LinkToBasicBlock(basicBlock *BasicBlockInfo) { + l.BasicBlock = basicBlock +} diff --git a/gen/label_manager.go b/gen/label_manager.go new file mode 100644 index 0000000..2c77322 --- /dev/null +++ b/gen/label_manager.go @@ -0,0 +1,8 @@ +package gen + +import "alon.kr/x/usm/core" + +type LabelManager interface { + GetLabel(name string) *LabelInfo + NewLabel(info *LabelInfo) core.Result +} diff --git a/gen/parameter.go b/gen/parameter_generator.go similarity index 70% rename from gen/parameter.go rename to gen/parameter_generator.go index cab4acf..fac9ce7 100644 --- a/gen/parameter.go +++ b/gen/parameter_generator.go @@ -6,19 +6,19 @@ import ( "alon.kr/x/usm/parse" ) -type ParameterGenerator[InstT BaseInstruction] struct { - ReferencedTypeGenerator FileContextGenerator[InstT, parse.TypeNode, ReferencedTypeInfo] +type ParameterGenerator struct { + ReferencedTypeGenerator FileContextGenerator[parse.TypeNode, ReferencedTypeInfo] } -func NewParameterGenerator[InstT BaseInstruction]() FunctionContextGenerator[InstT, parse.ParameterNode, *RegisterInfo] { - return FunctionContextGenerator[InstT, parse.ParameterNode, *RegisterInfo]( - &ParameterGenerator[InstT]{ - ReferencedTypeGenerator: NewReferencedTypeGenerator[InstT](), +func NewParameterGenerator() FunctionContextGenerator[parse.ParameterNode, *RegisterInfo] { + return FunctionContextGenerator[parse.ParameterNode, *RegisterInfo]( + &ParameterGenerator{ + ReferencedTypeGenerator: NewReferencedTypeGenerator(), }, ) } -func NewRegisterAlreadyDefinedResult( +func newRegisterAlreadyDefinedResult( NewDeclaration core.UnmanagedSourceView, FirstDeclaration core.UnmanagedSourceView, ) core.ResultList { @@ -39,8 +39,8 @@ func NewRegisterAlreadyDefinedResult( // Asserts that a register with the same name does not exist yet, // creates the new register, registers it to the register manager, // and returns the unique register info structure pointer. -func (g *ParameterGenerator[InstT]) Generate( - ctx *FunctionGenerationContext[InstT], +func (g *ParameterGenerator) Generate( + ctx *FunctionGenerationContext, node parse.ParameterNode, ) (*RegisterInfo, core.ResultList) { results := core.ResultList{} @@ -54,7 +54,7 @@ func (g *ParameterGenerator[InstT]) Generate( registerName := nodeToSourceString(ctx.FileGenerationContext, node.Register) registerInfo := ctx.Registers.GetRegister(registerName) if registerInfo != nil { - registerResults := NewRegisterAlreadyDefinedResult( + registerResults := newRegisterAlreadyDefinedResult( node.View(), registerInfo.Declaration, ) diff --git a/gen/regisrer_partial_info.go b/gen/regisrer_partial_info.go new file mode 100644 index 0000000..c0ba63b --- /dev/null +++ b/gen/regisrer_partial_info.go @@ -0,0 +1,15 @@ +package gen + +import "alon.kr/x/usm/core" + +// This represents partial register information, possibly without an associated +// type (yet). This is used internally before the compiler has finally determined +// the type of the register, if the type is implicit. +type registerPartialInfo struct { + Name string + + // Possibly nil, if type is implicitly defined. + Type *ReferencedTypeInfo + + Declaration core.UnmanagedSourceView +} diff --git a/gen/register.go b/gen/register.go deleted file mode 100644 index 33f0f58..0000000 --- a/gen/register.go +++ /dev/null @@ -1,85 +0,0 @@ -package gen - -import ( - "alon.kr/x/list" - "alon.kr/x/usm/core" - "alon.kr/x/usm/parse" -) - -// MARK: Info -type RegisterInfo struct { - // The name of the register, as it appears in the source code. - Name string - - // The type of the register. - Type ReferencedTypeInfo - - // The first location in the source code in which the register is declared - // or assigned a value. - Declaration core.UnmanagedSourceView -} - -func (i RegisterInfo) toPartialRegisterInfo() partialRegisterInfo { - return partialRegisterInfo{ - Name: i.Name, - Type: &i.Type, - Declaration: i.Declaration, - } -} - -// This represents partial register information, possibly without an associated -// type (yet). This is used internally before the compiler has finally determined -// the type of the register, if the type is implicit. -type partialRegisterInfo struct { - Name string - - // Possibly nil, if type is implicitly defined. - Type *ReferencedTypeInfo - - Declaration core.UnmanagedSourceView -} - -// MARK: Manager - -type RegisterManager interface { - GetRegister(name string) *RegisterInfo - NewRegister(reg *RegisterInfo) core.Result -} - -// MARK: Generator - -// Used to convert parse.RegisterNode instances to *existing* register instances. -// Returns an error on generation if the provided register node references a -// register that does not exist. -type RegisterGenerator[InstT BaseInstruction] struct{} - -func NewRegisterGenerator[InstT BaseInstruction]() FunctionContextGenerator[InstT, parse.RegisterNode, *RegisterInfo] { - return FunctionContextGenerator[InstT, parse.RegisterNode, *RegisterInfo]( - &RegisterGenerator[InstT]{}, - ) -} - -func UndefinedRegisterResult(node parse.RegisterNode) core.ResultList { - v := node.View() - return list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: "Undefined register", - Location: &v, - }, - }) -} - -func (g *RegisterGenerator[InstT]) Generate( - ctx *FunctionGenerationContext[InstT], - node parse.RegisterNode, -) (*RegisterInfo, core.ResultList) { - name := nodeToSourceString(ctx.FileGenerationContext, node) - register := ctx.Registers.GetRegister(name) - - if register == nil { - return nil, UndefinedRegisterResult(node) - } - - return register, core.ResultList{} -} diff --git a/gen/register_argument.go b/gen/register_argument.go index 57d7515..b2e7276 100644 --- a/gen/register_argument.go +++ b/gen/register_argument.go @@ -22,23 +22,23 @@ func (i *RegisterArgumentInfo) Declaration() core.UnmanagedSourceView { // MARK: Generator -type RegisterArgumentGenerator[InstT BaseInstruction] struct { - RegisterGenerator FunctionContextGenerator[InstT, parse.RegisterNode, *RegisterInfo] +type RegisterArgumentGenerator struct { + RegisterGenerator FunctionContextGenerator[parse.RegisterNode, *RegisterInfo] } -func NewRegisterArgumentGenerator[InstT BaseInstruction]() FunctionContextGenerator[InstT, parse.RegisterNode, ArgumentInfo] { - return FunctionContextGenerator[InstT, parse.RegisterNode, ArgumentInfo]( - &RegisterArgumentGenerator[InstT]{ - RegisterGenerator: NewRegisterGenerator[InstT](), +func NewRegisterArgumentGenerator() InstructionContextGenerator[parse.RegisterNode, ArgumentInfo] { + return InstructionContextGenerator[parse.RegisterNode, ArgumentInfo]( + &RegisterArgumentGenerator{ + RegisterGenerator: NewRegisterGenerator(), }, ) } -func (g *RegisterArgumentGenerator[InstT]) Generate( - ctx *FunctionGenerationContext[InstT], +func (g *RegisterArgumentGenerator) Generate( + ctx *InstructionGenerationContext, node parse.RegisterNode, ) (ArgumentInfo, core.ResultList) { - register, results := g.RegisterGenerator.Generate(ctx, node) + register, results := g.RegisterGenerator.Generate(ctx.FunctionGenerationContext, node) if !results.IsEmpty() { return nil, results } @@ -48,5 +48,6 @@ func (g *RegisterArgumentGenerator[InstT]) Generate( declaration: node.View(), } + register.AddUsage(ctx.InstructionInfo) return &argument, core.ResultList{} } diff --git a/gen/register_generator.go b/gen/register_generator.go new file mode 100644 index 0000000..49b481b --- /dev/null +++ b/gen/register_generator.go @@ -0,0 +1,43 @@ +package gen + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/parse" +) + +// Used to convert parse.RegisterNode instances to *existing* register instances. +// Returns an error on generation if the provided register node references a +// register that does not exist. +type RegisterGenerator struct{} + +func NewRegisterGenerator() FunctionContextGenerator[parse.RegisterNode, *RegisterInfo] { + return FunctionContextGenerator[parse.RegisterNode, *RegisterInfo]( + &RegisterGenerator{}, + ) +} + +func UndefinedRegisterResult(node parse.RegisterNode) core.ResultList { + v := node.View() + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Undefined register", + Location: &v, + }, + }) +} + +func (g *RegisterGenerator) Generate( + ctx *FunctionGenerationContext, + node parse.RegisterNode, +) (*RegisterInfo, core.ResultList) { + name := nodeToSourceString(ctx.FileGenerationContext, node) + register := ctx.Registers.GetRegister(name) + + if register == nil { + return nil, UndefinedRegisterResult(node) + } + + return register, core.ResultList{} +} diff --git a/gen/register_info.go b/gen/register_info.go new file mode 100644 index 0000000..ff505b2 --- /dev/null +++ b/gen/register_info.go @@ -0,0 +1,47 @@ +package gen + +import "alon.kr/x/usm/core" + +type RegisterInfo struct { + // The name of the register, as it appears in the source code. + Name string + + // The type of the register. + Type ReferencedTypeInfo + + // Instructions in which the register is a target, and is defined or + // assigned a new value. + // + // Note: This list is not a complete representation of all locations in which + // the register is defined, since it can be defined as a function parameter. + Definitions []*InstructionInfo + + // Instructions in which the register appears as a source, i.e. as an + // read only argument. + Usages []*InstructionInfo + + // TODO: for quicker updates of the data structure, both `Definitions` and + // `Usages` fields should be a linked list, where each entry points to the + // exact target/argument in the relevant instruction, and not to the whole + // instruction information struct. + + // The first location in the source code in which the register is declared + // or assigned a value. + Declaration core.UnmanagedSourceView +} + +func (i *RegisterInfo) AddDefinition(info *InstructionInfo) { + i.Definitions = append(i.Definitions, info) +} + +func (i *RegisterInfo) AddUsage(info *InstructionInfo) { + i.Usages = append(i.Usages, info) +} + +func (i *RegisterInfo) toPartialRegisterInfo() registerPartialInfo { + return registerPartialInfo{ + Name: i.Name, + Type: &i.Type, + Declaration: i.Declaration, + } +} diff --git a/gen/register_manager.go b/gen/register_manager.go new file mode 100644 index 0000000..3923cf8 --- /dev/null +++ b/gen/register_manager.go @@ -0,0 +1,9 @@ +package gen + +import "alon.kr/x/usm/core" + +type RegisterManager interface { + GetRegister(name string) *RegisterInfo + NewRegister(reg *RegisterInfo) core.Result + GetAllRegisters() []*RegisterInfo +} diff --git a/gen/step_info.go b/gen/step_info.go new file mode 100644 index 0000000..2d4aff3 --- /dev/null +++ b/gen/step_info.go @@ -0,0 +1,13 @@ +package gen + +type StepInfo interface{} + +// Regular control flow: just continue to execute the instruction that is +// located right after this one in the source code. +type ContinueToNextInstruction struct{} + +type ReturnFromFunction struct{} + +type JumpToLabel struct { + Label *LabelInfo +} diff --git a/gen/target.go b/gen/target_generator.go similarity index 71% rename from gen/target.go rename to gen/target_generator.go index efd00e2..5093fa8 100644 --- a/gen/target.go +++ b/gen/target_generator.go @@ -6,16 +6,14 @@ import ( "alon.kr/x/usm/parse" ) -// MARK: Generator - -type TargetGenerator[InstT BaseInstruction] struct { - ReferencedTypeGenerator FileContextGenerator[InstT, parse.TypeNode, ReferencedTypeInfo] +type TargetGenerator struct { + ReferencedTypeGenerator FileContextGenerator[parse.TypeNode, ReferencedTypeInfo] } -func NewTargetGenerator[InstT BaseInstruction]() FunctionContextGenerator[InstT, parse.TargetNode, partialRegisterInfo] { - return FunctionContextGenerator[InstT, parse.TargetNode, partialRegisterInfo]( - &TargetGenerator[InstT]{ - ReferencedTypeGenerator: NewReferencedTypeGenerator[InstT](), +func NewTargetGenerator() InstructionContextGenerator[parse.TargetNode, registerPartialInfo] { + return InstructionContextGenerator[parse.TargetNode, registerPartialInfo]( + &TargetGenerator{ + ReferencedTypeGenerator: NewReferencedTypeGenerator(), }, ) } @@ -38,10 +36,10 @@ func NewRegisterTypeMismatchResult( }) } -func (g *TargetGenerator[InstT]) Generate( - ctx *FunctionGenerationContext[InstT], +func (g *TargetGenerator) Generate( + ctx *InstructionGenerationContext, node parse.TargetNode, -) (partialRegisterInfo, core.ResultList) { +) (registerPartialInfo, core.ResultList) { var explicitType *ReferencedTypeInfo // if an explicit type is provided to the target, get the type info. @@ -52,7 +50,7 @@ func (g *TargetGenerator[InstT]) Generate( *node.Type, ) if !results.IsEmpty() { - return partialRegisterInfo{}, results + return registerPartialInfo{}, results } explicitType = &explicitTypeValue @@ -65,8 +63,8 @@ func (g *TargetGenerator[InstT]) Generate( if registerAlreadyDefined { if explicitType != nil { // ensure explicit type matches the previously declared one. - if !explicitType.Equals(registerInfo.Type) { - return partialRegisterInfo{}, NewRegisterTypeMismatchResult( + if !explicitType.Equal(registerInfo.Type) { + return registerPartialInfo{}, NewRegisterTypeMismatchResult( node.View(), registerInfo.Declaration, ) @@ -82,7 +80,7 @@ func (g *TargetGenerator[InstT]) Generate( // the target register at this. // the type and register will be finalized when the instruction is built, // and only then it is added to the register manager. - return partialRegisterInfo{ + return registerPartialInfo{ Name: registerName, Type: explicitType, Declaration: node.View(), diff --git a/gen/target_test.go b/gen/target_generator_test.go similarity index 68% rename from gen/target_test.go rename to gen/target_generator_test.go index e9e8b0e..14d4a06 100644 --- a/gen/target_test.go +++ b/gen/target_generator_test.go @@ -34,16 +34,19 @@ func TestTargetRegisterAlreadyDefined(t *testing.T) { "%a": &gen.RegisterInfo{Name: "%a", Type: intTypeRef}, } - ctx := gen.FunctionGenerationContext[Instruction]{ - FileGenerationContext: &gen.FileGenerationContext[Instruction]{ - GenerationContext: &testGenerationContext, - SourceContext: src.Ctx(), - Types: &types, + ctx := gen.InstructionGenerationContext{ + FunctionGenerationContext: &gen.FunctionGenerationContext{ + FileGenerationContext: &gen.FileGenerationContext{ + GenerationContext: &testGenerationContext, + SourceContext: src.Ctx(), + Types: &types, + }, + Registers: ®isters, }, - Registers: ®isters, + InstructionInfo: gen.NewEmptyInstructionInfo(&unmanaged), } - generator := gen.NewTargetGenerator[Instruction]() + generator := gen.NewTargetGenerator() info, results := generator.Generate(&ctx, node) assert.True(t, results.IsEmpty()) assert.Equal(t, intType, info.Type.Base) diff --git a/gen/type.go b/gen/type.go deleted file mode 100644 index eeb15a8..0000000 --- a/gen/type.go +++ /dev/null @@ -1,337 +0,0 @@ -// Converts AST nodes representing types (type declarations, etc.) into -// types metadata (calculating type structures, type sizes, etc.). - -package gen - -import ( - "alon.kr/x/list" - "alon.kr/x/usm/core" - "alon.kr/x/usm/parse" -) - -// MARK: Info - -// A named type is a type that can has a distinct name. -// It either (1) a builtin type or (2) a type alias declared by the "type" -// keyword. -type NamedTypeInfo struct { - Name string - Size core.UsmUint - - // The source view of the type declaration. - // Should be nil only if it is a builtin type. - Declaration *core.UnmanagedSourceView -} - -type TypeDescriptorType uint8 - -const ( - PointerTypeDescriptor TypeDescriptorType = iota - RepeatTypeDescriptor -) - -type TypeDescriptorInfo struct { - Type TypeDescriptorType - Amount core.UsmUint -} - -// A referenced type is a combination of a basic type with (possibly zero) -// type decorators that wrap it. -// For example, if `$32“ is a basic named type, then `$32 *`, which is a -// pointer to that type is a referenced type with the `$32` named type as it's -// base type, and the pointer as a decorator. -type ReferencedTypeInfo struct { - // A pointer to the base, named type that this type reference refers to. - Base *NamedTypeInfo - Descriptors []TypeDescriptorInfo -} - -func (info ReferencedTypeInfo) Equals(other ReferencedTypeInfo) bool { - if info.Base != other.Base { - return false - } - - if len(info.Descriptors) != len(other.Descriptors) { - return false - } - - for i := range info.Descriptors { - if info.Descriptors[i] != other.Descriptors[i] { - return false - } - } - - return true -} - -// MARK: Manager - -type TypeManager interface { - // Query a already seen before type, and get the type information if it - // exists. Returns nil if the if a type with the provided name has not yet - // been defined. - // - // The implementation should also return information about builtin types, - // although the creation of such types can be possibly done lazily. - GetType(name string) *NamedTypeInfo - - // Register a new type with the provided name and type information. - // The generator will call this method when it encounters a new type - // definition. - // - // The implementation should raise an error if the new registered type is - // invalid. It can however assume that the type name is unique and has not - // been defined before (GetType() returned nil on it). - NewType(typ *NamedTypeInfo) core.Result -} - -// MARK: Descriptor Generator - -type DescriptorGenerator[InstT BaseInstruction] struct{} - -func NewDescriptorGenerator[InstT BaseInstruction]() FileContextGenerator[InstT, parse.TypeDecoratorNode, TypeDescriptorInfo] { - return FileContextGenerator[InstT, parse.TypeDecoratorNode, TypeDescriptorInfo]( - &DescriptorGenerator[InstT]{}, - ) -} - -// Valid type decorators should match the regex ".\d*" where the first rune is -// the decorator identifier (pointer, repeat, etc.), and immediately follows -// the an optional decimal number that is interpreted differently depending on -// decorator. -// -// This function parses the decorator string, and returns the decorator number, -// or an error if the decorator does not match the expected format. If a number -// is not provided, the default number is 1. -// -// Why don't we do this at the `parse` module? because the `parse` module parses -// the structure of tokens only, and does not look inside the content of the -// tokens. More specifically, it does not have access to the source context. -func (g *DescriptorGenerator[InstT]) parseDescriptorAmount( - ctx *FileGenerationContext[InstT], - decorator parse.TypeDecoratorNode, -) (core.UsmUint, core.ResultList) { - if decorator.Len() <= 1 { - // 1 is the default amount for type decorators, when no explicit amount - // is provided. - return 1, core.ResultList{} - } - - numView := decorator.Subview(1, decorator.Len()) - numStr := viewToSourceString(ctx, numView) - num, err := core.ParseUint(numStr) - - if err != nil { - return 0, list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: "Failed to parse number in type decorator", - Location: &numView, - }, - { - Type: core.HintResult, - Message: "Should be a positive, decimal number", - }, - }) - } - - return num, core.ResultList{} -} - -func (g *DescriptorGenerator[InstT]) parsedDescriptorToGenDescriptorType( - node parse.TypeDecoratorNode, -) (genType TypeDescriptorType, results core.ResultList) { - switch node.Type { - case parse.PointerTypeDecorator: - return PointerTypeDescriptor, core.ResultList{} - case parse.RepeatTypeDecorator: - return RepeatTypeDescriptor, core.ResultList{} - default: - // notest - return 0, list.FromSingle(core.Result{ - { - Type: core.InternalErrorResult, - Message: "Invalid type decorator", - Location: &node.UnmanagedSourceView, - }, - }) - } -} - -func (g *DescriptorGenerator[InstT]) Generate( - ctx *FileGenerationContext[InstT], - node parse.TypeDecoratorNode, -) (info TypeDescriptorInfo, results core.ResultList) { - typ, results := g.parsedDescriptorToGenDescriptorType(node) - if !results.IsEmpty() { - return - } - - amount, results := g.parseDescriptorAmount(ctx, node) - if !results.IsEmpty() { - return - } - - return TypeDescriptorInfo{ - Type: typ, - Amount: amount, - }, results -} - -// MARK: Ref'ed Generator - -type ReferencedTypeGenerator[InstT BaseInstruction] struct { - DescriptorGenerator FileContextGenerator[InstT, parse.TypeDecoratorNode, TypeDescriptorInfo] -} - -func NewReferencedTypeGenerator[InstT BaseInstruction]() FileContextGenerator[InstT, parse.TypeNode, ReferencedTypeInfo] { - return FileContextGenerator[InstT, parse.TypeNode, ReferencedTypeInfo]( - &ReferencedTypeGenerator[InstT]{ - DescriptorGenerator: NewDescriptorGenerator[InstT](), - }, - ) -} - -func (g *ReferencedTypeGenerator[InstT]) Generate( - ctx *FileGenerationContext[InstT], - node parse.TypeNode, -) (ReferencedTypeInfo, core.ResultList) { - baseIdentifier := viewToSourceString(ctx, node.Identifier) - baseType := ctx.Types.GetType(baseIdentifier) - - if baseType == nil { - return ReferencedTypeInfo{}, list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: "Undefined type", - Location: &node.Identifier, - }, - // TODO: add a hint in case of a typo hint? - }) - } - - descriptors := make([]TypeDescriptorInfo, 0, len(node.Decorators)) - for _, descriptor := range node.Decorators { - descriptorInfo, results := g.DescriptorGenerator.Generate(ctx, descriptor) - if !results.IsEmpty() { - return ReferencedTypeInfo{}, results - } - - descriptors = append(descriptors, descriptorInfo) - } - - typeInfo := ReferencedTypeInfo{ - Base: baseType, - Descriptors: descriptors, - } - - return typeInfo, core.ResultList{} -} - -// MARK: Named Generator - -type NamedTypeGenerator[InstT BaseInstruction] struct { - ReferencedTypeGenerator FileContextGenerator[InstT, parse.TypeNode, ReferencedTypeInfo] -} - -func NewNamedTypeGenerator[InstT BaseInstruction]() FileContextGenerator[InstT, parse.TypeDeclarationNode, *NamedTypeInfo] { - return FileContextGenerator[InstT, parse.TypeDeclarationNode, *NamedTypeInfo]( - &NamedTypeGenerator[InstT]{ - ReferencedTypeGenerator: NewReferencedTypeGenerator[InstT](), - }, - ) -} - -func (g *NamedTypeGenerator[InstT]) calculateTypeSize( - ctx *FileGenerationContext[InstT], - node parse.TypeNode, - typeInfo ReferencedTypeInfo, -) (core.UsmUint, core.ResultList) { - size := core.UsmUint(typeInfo.Base.Size) - - for _, descriptor := range typeInfo.Descriptors { - switch descriptor.Type { - case PointerTypeDescriptor: - size = ctx.PointerSize - case RepeatTypeDescriptor: - var ok bool - size, ok = core.Mul(size, descriptor.Amount) - if !ok { - v := node.View() - return 0, list.FromSingle(core.Result{{ - Type: core.ErrorResult, - Message: "Type size overflow", - Location: &v, - }}) - } - default: - // notest - v := node.View() - return 0, list.FromSingle(core.Result{{ - Type: core.InternalErrorResult, - Message: "Unknown type descriptor", - Location: &v, - }}) - } - } - - return size, core.ResultList{} -} - -func (g *NamedTypeGenerator[InstT]) Generate( - ctx *FileGenerationContext[InstT], - node parse.TypeDeclarationNode, -) (*NamedTypeInfo, core.ResultList) { - identifier := viewToSourceString(ctx, node.Identifier) - declaration := node.View() - - typeInfo := ctx.Types.GetType(identifier) - if typeInfo != nil { - return nil, list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: "Type already defined", - Location: &declaration, - }, - { - Type: core.HintResult, - Message: "Previous definition here", - Location: typeInfo.Declaration, - }, - }) - } - - if len(node.Fields.Nodes) != 1 { - return nil, list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: "Type declaration currently only supports a single field", - Location: &declaration, - }, - }) - } - - referencedTypeNode := node.Fields.Nodes[0].Type - referencedTypeInfo, results := g.ReferencedTypeGenerator.Generate(ctx, referencedTypeNode) - if !results.IsEmpty() { - return nil, results - } - - size, results := g.calculateTypeSize(ctx, referencedTypeNode, referencedTypeInfo) - if !results.IsEmpty() { - return nil, results - } - - typeInfo = &NamedTypeInfo{ - Name: identifier, - Size: size, - Declaration: &declaration, - } - - result := ctx.Types.NewType(typeInfo) - if result != nil { - return nil, list.FromSingle(result) - } - - return typeInfo, core.ResultList{} -} diff --git a/gen/type_descriptor_generator.go b/gen/type_descriptor_generator.go new file mode 100644 index 0000000..1777cb4 --- /dev/null +++ b/gen/type_descriptor_generator.go @@ -0,0 +1,98 @@ +package gen + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/parse" +) + +type DescriptorGenerator struct{} + +func NewDescriptorGenerator() FileContextGenerator[parse.TypeDecoratorNode, TypeDescriptorInfo] { + return FileContextGenerator[parse.TypeDecoratorNode, TypeDescriptorInfo]( + &DescriptorGenerator{}, + ) +} + +// Valid type decorators should match the regex ".\d*" where the first rune is +// the decorator identifier (pointer, repeat, etc.), and immediately follows +// the an optional decimal number that is interpreted differently depending on +// decorator. +// +// This function parses the decorator string, and returns the decorator number, +// or an error if the decorator does not match the expected format. If a number +// is not provided, the default number is 1. +// +// Why don't we do this at the `parse` module? because the `parse` module parses +// the structure of tokens only, and does not look inside the content of the +// tokens. More specifically, it does not have access to the source context. +func (g *DescriptorGenerator) parseDescriptorAmount( + ctx *FileGenerationContext, + decorator parse.TypeDecoratorNode, +) (core.UsmUint, core.ResultList) { + if decorator.Len() <= 1 { + // 1 is the default amount for type decorators, when no explicit amount + // is provided. + return 1, core.ResultList{} + } + + numView := decorator.Subview(1, decorator.Len()) + numStr := viewToSourceString(ctx, numView) + num, err := core.ParseUint(numStr) + + if err != nil { + return 0, list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Failed to parse number in type decorator", + Location: &numView, + }, + { + Type: core.HintResult, + Message: "Should be a positive, decimal number", + }, + }) + } + + return num, core.ResultList{} +} + +func (g *DescriptorGenerator) parsedDescriptorToGenDescriptorType( + node parse.TypeDecoratorNode, +) (genType TypeDescriptorType, results core.ResultList) { + switch node.Type { + case parse.PointerTypeDecorator: + return PointerTypeDescriptor, core.ResultList{} + case parse.RepeatTypeDecorator: + return RepeatTypeDescriptor, core.ResultList{} + default: + // notest + return 0, list.FromSingle(core.Result{ + { + Type: core.InternalErrorResult, + Message: "Invalid type decorator", + Location: &node.UnmanagedSourceView, + }, + }) + } +} + +func (g *DescriptorGenerator) Generate( + ctx *FileGenerationContext, + node parse.TypeDecoratorNode, +) (info TypeDescriptorInfo, results core.ResultList) { + typ, results := g.parsedDescriptorToGenDescriptorType(node) + if !results.IsEmpty() { + return + } + + amount, results := g.parseDescriptorAmount(ctx, node) + if !results.IsEmpty() { + return + } + + return TypeDescriptorInfo{ + Type: typ, + Amount: amount, + }, results +} diff --git a/gen/type_test.go b/gen/type_generator_test.go similarity index 92% rename from gen/type_test.go rename to gen/type_generator_test.go index fa725e4..314e8b5 100644 --- a/gen/type_test.go +++ b/gen/type_generator_test.go @@ -31,13 +31,13 @@ func TestTypeAliasDeclaration(t *testing.T) { }, } - ctx := gen.FileGenerationContext[Instruction]{ + ctx := gen.FileGenerationContext{ GenerationContext: &testGenerationContext, SourceContext: view.Ctx(), Types: &typeManager, } - generator := gen.NewNamedTypeGenerator[Instruction]() + generator := gen.NewNamedTypeGenerator() typeInfo, results := generator.Generate(&ctx, typeDeclarationNode) assert.True(t, results.IsEmpty()) @@ -74,13 +74,13 @@ func TestPointerTypeDeclaration(t *testing.T) { }, } - ctx := gen.FileGenerationContext[Instruction]{ + ctx := gen.FileGenerationContext{ GenerationContext: &testGenerationContext, SourceContext: view.Ctx(), Types: &typeManager, } - generator := gen.NewNamedTypeGenerator[Instruction]() + generator := gen.NewNamedTypeGenerator() typeInfo, results := generator.Generate(&ctx, typeDeclarationNode) assert.True(t, results.IsEmpty()) @@ -117,13 +117,13 @@ func TestRepeatTypeDeclaration(t *testing.T) { }, } - ctx := gen.FileGenerationContext[Instruction]{ + ctx := gen.FileGenerationContext{ GenerationContext: &testGenerationContext, SourceContext: view.Ctx(), Types: &typeManager, } - generator := gen.NewNamedTypeGenerator[Instruction]() + generator := gen.NewNamedTypeGenerator() typeInfo, results := generator.Generate(&ctx, typeDeclarationNode) assert.True(t, results.IsEmpty()) @@ -159,13 +159,13 @@ func TestAlreadyDefinedTypeDeclaration(t *testing.T) { }, } - genCtx := gen.FileGenerationContext[Instruction]{ + genCtx := gen.FileGenerationContext{ GenerationContext: &testGenerationContext, SourceContext: view.Ctx(), Types: &typeManager, } - generator := gen.NewNamedTypeGenerator[Instruction]() + generator := gen.NewNamedTypeGenerator() _, results := generator.Generate(&genCtx, node) assert.Len(t, results.ToSlice(), 1) @@ -227,13 +227,13 @@ func TestRepeatTypeTooLarge(t *testing.T) { }, } - ctx := &gen.FileGenerationContext[Instruction]{ + ctx := &gen.FileGenerationContext{ GenerationContext: &testGenerationContext, SourceContext: v.Ctx(), Types: &typeManager, } - generator := gen.NewNamedTypeGenerator[Instruction]() + generator := gen.NewNamedTypeGenerator() _, results := generator.Generate(ctx, node) assert.False(t, results.IsEmpty()) } diff --git a/gen/type_manager.go b/gen/type_manager.go new file mode 100644 index 0000000..58ed3f9 --- /dev/null +++ b/gen/type_manager.go @@ -0,0 +1,22 @@ +package gen + +import "alon.kr/x/usm/core" + +type TypeManager interface { + // Query a already seen before type, and get the type information if it + // exists. Returns nil if the if a type with the provided name has not yet + // been defined. + // + // The implementation should also return information about builtin types, + // although the creation of such types can be possibly done lazily. + GetType(name string) *NamedTypeInfo + + // Register a new type with the provided name and type information. + // The generator will call this method when it encounters a new type + // definition. + // + // The implementation should raise an error if the new registered type is + // invalid. It can however assume that the type name is unique and has not + // been defined before (GetType() returned nil on it). + NewType(typ *NamedTypeInfo) core.Result +} diff --git a/gen/type_named_generator.go b/gen/type_named_generator.go new file mode 100644 index 0000000..382512a --- /dev/null +++ b/gen/type_named_generator.go @@ -0,0 +1,113 @@ +package gen + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/parse" +) + +type NamedTypeGenerator struct { + ReferencedTypeGenerator FileContextGenerator[parse.TypeNode, ReferencedTypeInfo] +} + +func NewNamedTypeGenerator() FileContextGenerator[parse.TypeDeclarationNode, *NamedTypeInfo] { + return FileContextGenerator[parse.TypeDeclarationNode, *NamedTypeInfo]( + &NamedTypeGenerator{ + ReferencedTypeGenerator: NewReferencedTypeGenerator(), + }, + ) +} + +func (g *NamedTypeGenerator) calculateTypeSize( + ctx *FileGenerationContext, + node parse.TypeNode, + typeInfo ReferencedTypeInfo, +) (core.UsmUint, core.ResultList) { + size := core.UsmUint(typeInfo.Base.Size) + + for _, descriptor := range typeInfo.Descriptors { + switch descriptor.Type { + case PointerTypeDescriptor: + size = ctx.PointerSize + case RepeatTypeDescriptor: + var ok bool + size, ok = core.Mul(size, descriptor.Amount) + if !ok { + v := node.View() + return 0, list.FromSingle(core.Result{{ + Type: core.ErrorResult, + Message: "Type size overflow", + Location: &v, + }}) + } + default: + // notest + v := node.View() + return 0, list.FromSingle(core.Result{{ + Type: core.InternalErrorResult, + Message: "Unknown type descriptor", + Location: &v, + }}) + } + } + + return size, core.ResultList{} +} + +func (g *NamedTypeGenerator) Generate( + ctx *FileGenerationContext, + node parse.TypeDeclarationNode, +) (*NamedTypeInfo, core.ResultList) { + identifier := viewToSourceString(ctx, node.Identifier) + declaration := node.View() + + typeInfo := ctx.Types.GetType(identifier) + if typeInfo != nil { + return nil, list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Type already defined", + Location: &declaration, + }, + { + Type: core.HintResult, + Message: "Previous definition here", + Location: typeInfo.Declaration, + }, + }) + } + + if len(node.Fields.Nodes) != 1 { + return nil, list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Type declaration currently only supports a single field", + Location: &declaration, + }, + }) + } + + referencedTypeNode := node.Fields.Nodes[0].Type + referencedTypeInfo, results := g.ReferencedTypeGenerator.Generate(ctx, referencedTypeNode) + if !results.IsEmpty() { + return nil, results + } + + size, results := g.calculateTypeSize(ctx, referencedTypeNode, referencedTypeInfo) + if !results.IsEmpty() { + return nil, results + } + + typeInfo = &NamedTypeInfo{ + Name: identifier, + Size: size, + Declaration: &declaration, + } + + result := ctx.Types.NewType(typeInfo) + if result != nil { + return nil, list.FromSingle(result) + } + + return typeInfo, core.ResultList{} +} diff --git a/gen/type_named_info.go b/gen/type_named_info.go new file mode 100644 index 0000000..783535f --- /dev/null +++ b/gen/type_named_info.go @@ -0,0 +1,15 @@ +package gen + +import "alon.kr/x/usm/core" + +// A named type is a type that can has a distinct name. +// It either (1) a builtin type or (2) a type alias declared by the "type" +// keyword. +type NamedTypeInfo struct { + Name string + Size core.UsmUint + + // The source view of the type declaration. + // Should be nil only if it is a builtin type. + Declaration *core.UnmanagedSourceView +} diff --git a/gen/type_referenced_generator.go b/gen/type_referenced_generator.go new file mode 100644 index 0000000..02c60c1 --- /dev/null +++ b/gen/type_referenced_generator.go @@ -0,0 +1,55 @@ +package gen + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/parse" +) + +type ReferencedTypeGenerator struct { + DescriptorGenerator FileContextGenerator[parse.TypeDecoratorNode, TypeDescriptorInfo] +} + +func NewReferencedTypeGenerator() FileContextGenerator[parse.TypeNode, ReferencedTypeInfo] { + return FileContextGenerator[parse.TypeNode, ReferencedTypeInfo]( + &ReferencedTypeGenerator{ + DescriptorGenerator: NewDescriptorGenerator(), + }, + ) +} + +func (g *ReferencedTypeGenerator) Generate( + ctx *FileGenerationContext, + node parse.TypeNode, +) (ReferencedTypeInfo, core.ResultList) { + baseIdentifier := viewToSourceString(ctx, node.Identifier) + baseType := ctx.Types.GetType(baseIdentifier) + + if baseType == nil { + return ReferencedTypeInfo{}, list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Undefined type", + Location: &node.Identifier, + }, + // TODO: add a hint in case of a typo hint? + }) + } + + descriptors := make([]TypeDescriptorInfo, 0, len(node.Decorators)) + for _, descriptor := range node.Decorators { + descriptorInfo, results := g.DescriptorGenerator.Generate(ctx, descriptor) + if !results.IsEmpty() { + return ReferencedTypeInfo{}, results + } + + descriptors = append(descriptors, descriptorInfo) + } + + typeInfo := ReferencedTypeInfo{ + Base: baseType, + Descriptors: descriptors, + } + + return typeInfo, core.ResultList{} +} diff --git a/gen/type_referenced_info.go b/gen/type_referenced_info.go new file mode 100644 index 0000000..44b0231 --- /dev/null +++ b/gen/type_referenced_info.go @@ -0,0 +1,44 @@ +package gen + +import "alon.kr/x/usm/core" + +type TypeDescriptorType uint8 + +const ( + PointerTypeDescriptor TypeDescriptorType = iota + RepeatTypeDescriptor +) + +type TypeDescriptorInfo struct { + Type TypeDescriptorType + Amount core.UsmUint +} + +// A referenced type is a combination of a basic type with (possibly zero) +// type decorators that wrap it. +// For example, if `$32“ is a basic named type, then `$32 *`, which is a +// pointer to that type is a referenced type with the `$32` named type as it's +// base type, and the pointer as a decorator. +type ReferencedTypeInfo struct { + // A pointer to the base, named type that this type reference refers to. + Base *NamedTypeInfo + Descriptors []TypeDescriptorInfo +} + +func (info ReferencedTypeInfo) Equal(other ReferencedTypeInfo) bool { + if info.Base != other.Base { + return false + } + + if len(info.Descriptors) != len(other.Descriptors) { + return false + } + + for i := range info.Descriptors { + if info.Descriptors[i] != other.Descriptors[i] { + return false + } + } + + return true +} diff --git a/gen/utils_test.go b/gen/utils_test.go index bef217f..e166fac 100644 --- a/gen/utils_test.go +++ b/gen/utils_test.go @@ -48,28 +48,39 @@ func (m *RegisterMap) NewRegister(reg *gen.RegisterInfo) core.Result { return nil } +func (m *RegisterMap) GetAllRegisters() []*gen.RegisterInfo { + registers := make([]*gen.RegisterInfo, 0, len(*m)) + for _, reg := range *m { + registers = append(registers, reg) + } + return registers +} + // MARK: LabelMap -type LabelMap map[string]gen.LabelInfo +type LabelMap map[string]*gen.LabelInfo func (m *LabelMap) GetLabel(name string) *gen.LabelInfo { val, ok := (*m)[name] if !ok { return nil } - return &val + return val } -func (m *LabelMap) NewLabel(label gen.LabelInfo) core.Result { +func (m *LabelMap) NewLabel(label *gen.LabelInfo) core.Result { (*m)[label.Name] = label return nil } // MARK: Context -var testInstructionSet = gen.InstructionManager[Instruction]( +var testInstructionSet = gen.InstructionManager( &InstructionMap{ "ADD": &AddInstructionDefinition{}, + "JMP": &JumpInstructionDefinition{}, + "JZ": &JumpZeroInstructionDefinition{}, + "RET": &RetInstructionDefinition{}, }, ) @@ -85,7 +96,7 @@ var testManagerCreators = gen.ManagerCreators{ }, } -var testGenerationContext = gen.GenerationContext[Instruction]{ +var testGenerationContext = gen.GenerationContext{ ManagerCreators: testManagerCreators, Instructions: testInstructionSet, PointerSize: 314, // An arbitrary, unique value. diff --git a/usm.go b/usm.go index 74f0132..10bb158 100644 --- a/usm.go +++ b/usm.go @@ -10,7 +10,6 @@ import ( "alon.kr/x/usm/lex" "alon.kr/x/usm/parse" usm64core "alon.kr/x/usm/usm64/core" - usm64emulate "alon.kr/x/usm/usm64/emulate" "alon.kr/x/usm/usm64/managers" "github.com/spf13/cobra" ) @@ -96,7 +95,7 @@ func emuCommand(cmd *cobra.Command, args []string) { } ctx := managers.NewGenerationContext() - generator := gen.NewFileGenerator[usm64core.Instruction]() + generator := gen.NewFileGenerator() info, results := generator.Generate(ctx, view.Ctx(), node) if !results.IsEmpty() { stringer := core.NewResultStringer(view.Ctx(), inputFilepath) @@ -106,8 +105,17 @@ func emuCommand(cmd *cobra.Command, args []string) { os.Exit(1) } - emulator := usm64emulate.NewEmulator() - emulator.Emulate(info.Functions[0]) + emulator := usm64core.NewEmulator() + results = emulator.Emulate(info.Functions[0]) + if !results.IsEmpty() { + stringer := core.NewResultStringer(view.Ctx(), inputFilepath) + for result := range results.Range() { + fmt.Print(stringer.StringResult(result)) + } + os.Exit(1) + } + + os.Exit(0) } func main() { diff --git a/usm64/core/argument.go b/usm64/core/argument.go deleted file mode 100644 index aa57caa..0000000 --- a/usm64/core/argument.go +++ /dev/null @@ -1,67 +0,0 @@ -package usm64core - -import ( - "alon.kr/x/list" - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" -) - -type Argument interface { - String(ctx *EmulationContext) string - Declaration() core.UnmanagedSourceView -} - -type ValuedArgument interface { - Argument - Value(ctx *EmulationContext) uint64 -} - -func ArgumentToValuedArgument(arg Argument) (ValuedArgument, core.ResultList) { - valued, ok := arg.(ValuedArgument) - if !ok { - v := arg.Declaration() - return nil, list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: "Expected valued argument", - Location: &v, - }, - }) - } - - return valued, core.ResultList{} -} - -func ArgumentToLabel(argument Argument) (Label, core.ResultList) { - label, ok := argument.(Label) - if !ok { - v := argument.Declaration() - return Label{}, list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: "Expected label argument", - Location: &v, - }, - }) - } - - return label, core.ResultList{} -} - -func NewArgument(argument gen.ArgumentInfo) (Argument, core.ResultList) { - switch typedArgument := argument.(type) { - case *gen.ImmediateInfo: - return NewImmediate(*typedArgument) - case *gen.RegisterArgumentInfo: - return NewRegister(typedArgument) - case *gen.LabelArgumentInfo: - return NewLabel(*typedArgument) - default: - return nil, list.FromSingle(core.Result{ - { - Type: core.InternalErrorResult, - Message: "Unknown argument type", - }, - }) - } -} diff --git a/usm64/core/emualtor.go b/usm64/core/emualtor.go new file mode 100644 index 0000000..3965f39 --- /dev/null +++ b/usm64/core/emualtor.go @@ -0,0 +1,49 @@ +package usm64core + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" +) + +func instructionInfoToInstruction(info *gen.InstructionInfo) (Instruction, core.ResultList) { + if inst, ok := info.Instruction.(Instruction); !ok { + return nil, list.FromSingle(core.Result{{ + Type: core.InternalErrorResult, + Message: "Invalid instruction type", + Location: info.Declaration, + }}) + } else { + return inst, core.ResultList{} + } +} + +type Emulator struct{} + +func (Emulator) Emulate( + function *gen.FunctionInfo, +) core.ResultList { + ctx, results := NewEmulationContext(function) + if !results.IsEmpty() { + return results + } + + for !ctx.ShouldTerminate { + instInfo := ctx.NextBlockInfo.Instructions[ctx.NextInstructionIndexInBlock] + instruction, results := instructionInfoToInstruction(instInfo) + if !results.IsEmpty() { + return results + } + + results = instruction.Emulate(ctx) + if !results.IsEmpty() { + return results + } + } + + return core.ResultList{} +} + +func NewEmulator() Emulator { + return Emulator{} +} diff --git a/usm64/core/emulation.go b/usm64/core/emulation.go index 9aa0382..a3843e8 100644 --- a/usm64/core/emulation.go +++ b/usm64/core/emulation.go @@ -1,5 +1,11 @@ package usm64core +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" +) + // MARK: Error type EmulationError interface{} @@ -7,28 +13,87 @@ type EmulationError interface{} // MARK: Context type EmulationContext struct { - // The next instruction index to execute. - // Should be len(instructions) to indicate the return from the function. - NextInstructionIndex uint64 + NextBlockInfo *gen.BasicBlockInfo + NextInstructionIndexInBlock uint + ShouldTerminate bool Registers map[string]uint64 } -func (ctx *EmulationContext) IncrementInstructionPointer() { - ctx.NextInstructionIndex++ +func (ctx *EmulationContext) JumpToLabel(label *gen.LabelInfo) core.ResultList { + ctx.NextBlockInfo = label.BasicBlock + ctx.NextInstructionIndexInBlock = 0 + return core.ResultList{} } -func (ctx *EmulationContext) JumpToLabel(label Label) { - ctx.NextInstructionIndex = label.InstructionIndex +func (ctx *EmulationContext) ContinueToNextInstruction() core.ResultList { + ctx.NextInstructionIndexInBlock++ + if uint(len(ctx.NextBlockInfo.Instructions)) == ctx.NextInstructionIndexInBlock { + ctx.NextBlockInfo = ctx.NextBlockInfo.NextBlock + ctx.NextInstructionIndexInBlock = 0 + } + return core.ResultList{} } -func NewEmulationContext() EmulationContext { - return EmulationContext{ - NextInstructionIndex: 0, - Registers: make(map[string]uint64), +func (ctx *EmulationContext) ArgumentToValue( + argument gen.ArgumentInfo, +) (uint64, core.ResultList) { + switch typedArgument := argument.(type) { + case *gen.RegisterArgumentInfo: + name := typedArgument.Register.Name + value, ok := ctx.Registers[name] + if !ok { + v := argument.Declaration() + return 0, list.FromSingle(core.Result{{ + Type: core.InternalErrorResult, + Message: "Undefined register", + Location: &v, + }}) + } + + return value, core.ResultList{} + + case *gen.ImmediateInfo: + if !typedArgument.Value.IsInt64() { + v := argument.Declaration() + return 0, list.FromSingle(core.Result{{ + Type: core.ErrorResult, + Message: "Immediate overflows 64 bits", + Location: &v, + }}) + } + + return uint64(typedArgument.Value.Int64()), core.ResultList{} + + case *gen.LabelArgumentInfo: + v := argument.Declaration() + return 0, list.FromSingle(core.Result{{ + Type: core.ErrorResult, + Message: "Expected valued argument", + Location: &v, + }}) + + default: + v := argument.Declaration() + return 0, list.FromSingle(core.Result{{ + Type: core.InternalErrorResult, + Message: "Unexpected argument type", + Location: &v, + }}) } } +func NewEmulationContext( + function *gen.FunctionInfo, +) (*EmulationContext, core.ResultList) { + return &EmulationContext{ + NextInstructionIndexInBlock: 0, + NextBlockInfo: function.EntryBlock, + ShouldTerminate: false, + Registers: make(map[string]uint64), + }, core.ResultList{} +} + type Emulateable interface { - Emulate(ctx *EmulationContext) EmulationError + Emulate(ctx *EmulationContext) core.ResultList } diff --git a/usm64/core/immediate.go b/usm64/core/immediate.go deleted file mode 100644 index 30725ab..0000000 --- a/usm64/core/immediate.go +++ /dev/null @@ -1,48 +0,0 @@ -package usm64core - -import ( - "fmt" - "math/big" - - "alon.kr/x/list" - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" -) - -type Immediate struct { - value uint64 - declaration core.UnmanagedSourceView -} - -func NewImmediate(immediate gen.ImmediateInfo) (Immediate, core.ResultList) { - if !immediate.Value.IsInt64() && !immediate.Value.IsUint64() { - v := immediate.Declaration() - return Immediate{}, list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: "Immediate overflows 64 bits", - Location: &v, - }, - }) - } - - m := new(big.Int).Lsh(big.NewInt(1), 64) - value := immediate.Value.Mod(immediate.Value, m).Uint64() - - return Immediate{ - value: value, - declaration: immediate.Declaration(), - }, core.ResultList{} -} - -func (i Immediate) Value(*EmulationContext) uint64 { - return i.value -} - -func (i Immediate) String(*EmulationContext) string { - return fmt.Sprintf("#%d", i) -} - -func (i Immediate) Declaration() core.UnmanagedSourceView { - return i.declaration -} diff --git a/usm64/core/label.go b/usm64/core/label.go index 83af7d9..a77aa49 100644 --- a/usm64/core/label.go +++ b/usm64/core/label.go @@ -13,9 +13,9 @@ type Label struct { func NewLabel(arg gen.LabelArgumentInfo) (Label, core.ResultList) { return Label{ - Name: arg.Label.Name, - InstructionIndex: arg.Label.InstructionIndex, - declaration: arg.Declaration(), + Name: arg.Label.Name, + + declaration: arg.Declaration(), }, core.ResultList{} } diff --git a/usm64/emulate/emualtor.go b/usm64/emulate/emualtor.go deleted file mode 100644 index 6a80917..0000000 --- a/usm64/emulate/emualtor.go +++ /dev/null @@ -1,28 +0,0 @@ -package usm64emulate - -import ( - "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" -) - -type Emulator struct{} - -func (Emulator) Emulate( - function *gen.FunctionInfo[usm64core.Instruction], -) usm64core.EmulationError { - ctx := usm64core.NewEmulationContext() - - for ctx.NextInstructionIndex < uint64(len(function.Instructions)) { - nextInstruction := function.Instructions[ctx.NextInstructionIndex] - err := nextInstruction.Instruction.Emulate(&ctx) - if err != nil { - return err - } - } - - return nil -} - -func NewEmulator() Emulator { - return Emulator{} -} diff --git a/usm64/isa/add.go b/usm64/isa/add.go index 526213a..c8cfb93 100644 --- a/usm64/isa/add.go +++ b/usm64/isa/add.go @@ -1,48 +1,53 @@ package usm64isa import ( + "math/bits" + "alon.kr/x/usm/core" "alon.kr/x/usm/gen" usm64core "alon.kr/x/usm/usm64/core" ) type AddInstruction struct { - Target usm64core.Register - First, Second usm64core.ValuedArgument + nonBranchingInstruction } func (i *AddInstruction) Emulate( ctx *usm64core.EmulationContext, -) usm64core.EmulationError { - ctx.Registers[i.Target.Name] = i.First.Value(ctx) + i.Second.Value(ctx) - ctx.IncrementInstructionPointer() - return nil -} - -func NewAddInstruction( - targets []usm64core.Register, - arguments []usm64core.Argument, -) (usm64core.Instruction, core.ResultList) { +) core.ResultList { results := core.ResultList{} - first, firstResults := usm64core.ArgumentToValuedArgument(arguments[0]) + first, firstResults := ctx.ArgumentToValue(i.Arguments[0]) results.Extend(&firstResults) - second, secondResults := usm64core.ArgumentToValuedArgument(arguments[1]) + second, secondResults := ctx.ArgumentToValue(i.Arguments[1]) results.Extend(&secondResults) + if !results.IsEmpty() { + return results + } + + targetName := i.Targets[0].Register.Name + sum, _ := bits.Add64(first, second, 0) + ctx.Registers[targetName] = sum + return ctx.ContinueToNextInstruction() +} + +func NewAddInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + results := core.ResultList{} + if !results.IsEmpty() { return nil, results } - return &AddInstruction{ - Target: targets[0], - First: first, - Second: second, - }, core.ResultList{} + return gen.BaseInstruction(&AddInstruction{ + nonBranchingInstruction: newNonBranchingInstruction(info), + }), core.ResultList{} } -func NewAddInstructionDefinition() gen.InstructionDefinition[usm64core.Instruction] { +func NewAddInstructionDefinition() gen.InstructionDefinition { return &FixedInstructionDefinition{ Targets: 1, Arguments: 2, diff --git a/usm64/isa/base_instruction.go b/usm64/isa/base_instruction.go new file mode 100644 index 0000000..776a5b7 --- /dev/null +++ b/usm64/isa/base_instruction.go @@ -0,0 +1,13 @@ +package usm64isa + +import "alon.kr/x/usm/gen" + +type baseInstruction struct { + // A pointer the the internal USM representation of the instruction. + // This in turn has the representation of the arguments, targets, and types. + *gen.InstructionInfo +} + +func newBaseInstruction(info *gen.InstructionInfo) baseInstruction { + return baseInstruction{info} +} diff --git a/usm64/isa/fixed.go b/usm64/isa/fixed.go index 583ec05..54839c9 100644 --- a/usm64/isa/fixed.go +++ b/usm64/isa/fixed.go @@ -6,20 +6,18 @@ import ( "alon.kr/x/list" "alon.kr/x/usm/core" "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" ) type FixedInstructionDefinition struct { Targets core.UsmUint Arguments core.UsmUint Creator func( - targets []usm64core.Register, - arguments []usm64core.Argument, - ) (usm64core.Instruction, core.ResultList) + info *gen.InstructionInfo, + ) (gen.BaseInstruction, core.ResultList) } func (d *FixedInstructionDefinition) InferTargetTypes( - ctx *gen.FunctionGenerationContext[usm64core.Instruction], + ctx *gen.FunctionGenerationContext, targets []*gen.ReferencedTypeInfo, arguments []*gen.ReferencedTypeInfo, ) ([]gen.ReferencedTypeInfo, core.ResultList) { @@ -84,48 +82,13 @@ func (d *FixedInstructionDefinition) assertInputLengths( return results } -func (d *FixedInstructionDefinition) createRegisters( - registerInfos []*gen.RegisterArgumentInfo, -) (registers []usm64core.Register, results core.ResultList) { - registers = make([]usm64core.Register, len(registerInfos)) - for i, register := range registerInfos { - target, curResults := usm64core.NewRegister(register) - results.Extend(&curResults) - registers[i] = target - } - return -} - -func (d *FixedInstructionDefinition) createArguments( - argumentInfos []gen.ArgumentInfo, -) (arguments []usm64core.Argument, results core.ResultList) { - arguments = make([]usm64core.Argument, len(argumentInfos)) - for i, argument := range argumentInfos { - argument, curResults := usm64core.NewArgument(argument) - results.Extend(&curResults) - arguments[i] = argument - } - return -} - func (d *FixedInstructionDefinition) BuildInstruction( - targetInfos []*gen.RegisterArgumentInfo, - argumentInfos []gen.ArgumentInfo, -) (usm64core.Instruction, core.ResultList) { - results := d.assertInputLengths(targetInfos, argumentInfos) - if !results.IsEmpty() { - return nil, results - } - - targets, results := d.createRegisters(targetInfos) - if !results.IsEmpty() { - return nil, results - } - - arguments, results := d.createArguments(argumentInfos) + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + results := d.assertInputLengths(info.Targets, info.Arguments) if !results.IsEmpty() { return nil, results } - return d.Creator(targets, arguments) + return d.Creator(info) } diff --git a/usm64/isa/jump.go b/usm64/isa/jump.go index 5716d59..c564e9e 100644 --- a/usm64/isa/jump.go +++ b/usm64/isa/jump.go @@ -7,29 +7,28 @@ import ( ) type JumpInstruction struct { - Label usm64core.Label + baseInstruction +} + +func (i *JumpInstruction) PossibleNextSteps() ([]gen.StepInfo, core.ResultList) { + label := i.InstructionInfo.Arguments[0].(*gen.LabelArgumentInfo).Label + return []gen.StepInfo{gen.JumpToLabel{Label: label}}, core.ResultList{} } func (i *JumpInstruction) Emulate( ctx *usm64core.EmulationContext, -) usm64core.EmulationError { - ctx.JumpToLabel(i.Label) - return nil +) core.ResultList { + labelArgument := i.InstructionInfo.Arguments[0].(*gen.LabelArgumentInfo) + return ctx.JumpToLabel(labelArgument.Label) } func NewJumpInstruction( - targets []usm64core.Register, - arguments []usm64core.Argument, -) (usm64core.Instruction, core.ResultList) { - label, results := usm64core.ArgumentToLabel(arguments[0]) - if !results.IsEmpty() { - return nil, results - } - - return &JumpInstruction{Label: label}, core.ResultList{} + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&JumpInstruction{baseInstruction: baseInstruction{info}}), core.ResultList{} } -func NewJumpInstructionDefinition() gen.InstructionDefinition[usm64core.Instruction] { +func NewJumpInstructionDefinition() gen.InstructionDefinition { return &FixedInstructionDefinition{ Targets: 0, Arguments: 1, diff --git a/usm64/isa/jump_not_zero.go b/usm64/isa/jump_not_zero.go index 737d058..8580491 100644 --- a/usm64/isa/jump_not_zero.go +++ b/usm64/isa/jump_not_zero.go @@ -7,41 +7,43 @@ import ( ) type JumpNotZeroInstruction struct { - Argument usm64core.ValuedArgument - Label usm64core.Label + baseInstruction +} + +func (i *JumpNotZeroInstruction) PossibleNextSteps() ([]gen.StepInfo, core.ResultList) { + label := i.InstructionInfo.Arguments[1].(*gen.LabelArgumentInfo).Label + return []gen.StepInfo{ + gen.ContinueToNextInstruction{}, + gen.JumpToLabel{Label: label}, + }, core.ResultList{} } func (i *JumpNotZeroInstruction) Emulate( ctx *usm64core.EmulationContext, -) usm64core.EmulationError { - if i.Argument.Value(ctx) != uint64(0) { - ctx.JumpToLabel(i.Label) - } else { - ctx.IncrementInstructionPointer() +) core.ResultList { + value, results := ctx.ArgumentToValue(i.InstructionInfo.Arguments[0]) + if !results.IsEmpty() { + return results } - return nil -} - -func NewJumpNotZeroInstruction( - targets []usm64core.Register, - arguments []usm64core.Argument, -) (usm64core.Instruction, core.ResultList) { - results := core.ResultList{} - - argument, argumentResults := usm64core.ArgumentToValuedArgument(arguments[0]) - results.Extend(&argumentResults) - label, labelResults := usm64core.ArgumentToLabel(arguments[1]) - results.Extend(&labelResults) + if value != uint64(0) { + labelArgument := i.InstructionInfo.Arguments[1].(*gen.LabelArgumentInfo) + return ctx.JumpToLabel(labelArgument.Label) - if !results.IsEmpty() { - return nil, results + } else { + return ctx.ContinueToNextInstruction() } +} - return &JumpNotZeroInstruction{Argument: argument, Label: label}, core.ResultList{} +func NewJumpNotZeroInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&JumpNotZeroInstruction{ + baseInstruction: newBaseInstruction(info), + }), core.ResultList{} } -func NewJumpNotZeroInstructionDefinition() gen.InstructionDefinition[usm64core.Instruction] { +func NewJumpNotZeroInstructionDefinition() gen.InstructionDefinition { return &FixedInstructionDefinition{ Targets: 0, Arguments: 2, diff --git a/usm64/isa/jump_zero.go b/usm64/isa/jump_zero.go index 3ccfbaf..f8de4a6 100644 --- a/usm64/isa/jump_zero.go +++ b/usm64/isa/jump_zero.go @@ -7,41 +7,43 @@ import ( ) type JumpZeroInstruction struct { - Argument usm64core.ValuedArgument - Label usm64core.Label + baseInstruction +} + +func (i *JumpZeroInstruction) PossibleNextSteps() ([]gen.StepInfo, core.ResultList) { + label := i.InstructionInfo.Arguments[1].(*gen.LabelArgumentInfo).Label + return []gen.StepInfo{ + gen.ContinueToNextInstruction{}, + gen.JumpToLabel{Label: label}, + }, core.ResultList{} } func (i *JumpZeroInstruction) Emulate( ctx *usm64core.EmulationContext, -) usm64core.EmulationError { - if i.Argument.Value(ctx) == uint64(0) { - ctx.JumpToLabel(i.Label) - } else { - ctx.IncrementInstructionPointer() +) core.ResultList { + value, results := ctx.ArgumentToValue(i.InstructionInfo.Arguments[0]) + if !results.IsEmpty() { + return results } - return nil -} - -func NewJumpZeroInstruction( - targets []usm64core.Register, - arguments []usm64core.Argument, -) (usm64core.Instruction, core.ResultList) { - results := core.ResultList{} - - argument, argumentResults := usm64core.ArgumentToValuedArgument(arguments[0]) - results.Extend(&argumentResults) - label, labelResults := usm64core.ArgumentToLabel(arguments[1]) - results.Extend(&labelResults) + if value == uint64(0) { + labelArgument := i.InstructionInfo.Arguments[1].(*gen.LabelArgumentInfo) + return ctx.JumpToLabel(labelArgument.Label) - if !results.IsEmpty() { - return nil, results + } else { + return ctx.ContinueToNextInstruction() } +} - return &JumpZeroInstruction{Argument: argument, Label: label}, core.ResultList{} +func NewJumpZeroInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&JumpZeroInstruction{ + baseInstruction: newBaseInstruction(info), + }), core.ResultList{} } -func NewJumpZeroInstructionDefinition() gen.InstructionDefinition[usm64core.Instruction] { +func NewJumpZeroInstructionDefinition() gen.InstructionDefinition { return &FixedInstructionDefinition{ Targets: 0, Arguments: 2, diff --git a/usm64/isa/move.go b/usm64/isa/move.go index d6b7d66..78866d2 100644 --- a/usm64/isa/move.go +++ b/usm64/isa/move.go @@ -9,35 +9,31 @@ import ( // Currently, supporting only a single argument + target. // TODO: support n arguments and target pairs. type MoveInstruction struct { - Target usm64core.Register - Value usm64core.ValuedArgument + nonBranchingInstruction } func (i *MoveInstruction) Emulate( ctx *usm64core.EmulationContext, -) usm64core.EmulationError { - ctx.Registers[i.Target.Name] = i.Value.Value(ctx) - ctx.IncrementInstructionPointer() - return nil -} - -func NewMoveInstruction( - targets []usm64core.Register, - arguments []usm64core.Argument, -) (usm64core.Instruction, core.ResultList) { - results := core.ResultList{} - - value, valueResults := usm64core.ArgumentToValuedArgument(arguments[0]) - results.Extend(&valueResults) - +) core.ResultList { + value, results := ctx.ArgumentToValue(i.Arguments[0]) if !results.IsEmpty() { - return nil, results + return results } - return &MoveInstruction{Target: targets[0], Value: value}, core.ResultList{} + targetName := i.Targets[0].Register.Name + ctx.Registers[targetName] = value + return ctx.ContinueToNextInstruction() +} + +func NewMoveInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&MoveInstruction{ + nonBranchingInstruction: newNonBranchingInstruction(info), + }), core.ResultList{} } -func NewMoveInstructionDefinition() gen.InstructionDefinition[usm64core.Instruction] { +func NewMoveInstructionDefinition() gen.InstructionDefinition { return &FixedInstructionDefinition{ Targets: 1, Arguments: 1, diff --git a/usm64/isa/non_branching_instruction.go b/usm64/isa/non_branching_instruction.go new file mode 100644 index 0000000..132f9db --- /dev/null +++ b/usm64/isa/non_branching_instruction.go @@ -0,0 +1,18 @@ +package usm64isa + +import ( + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" +) + +type nonBranchingInstruction struct { + baseInstruction +} + +func newNonBranchingInstruction(info *gen.InstructionInfo) nonBranchingInstruction { + return nonBranchingInstruction{baseInstruction: newBaseInstruction(info)} +} + +func (i *nonBranchingInstruction) PossibleNextSteps() ([]gen.StepInfo, core.ResultList) { + return []gen.StepInfo{gen.ContinueToNextInstruction{}}, core.ResultList{} +} diff --git a/usm64/isa/put.go b/usm64/isa/put.go index ee54d51..b7add87 100644 --- a/usm64/isa/put.go +++ b/usm64/isa/put.go @@ -9,30 +9,30 @@ import ( ) type PutInstruction struct { - Argument usm64core.ValuedArgument + nonBranchingInstruction } func (i *PutInstruction) Emulate( ctx *usm64core.EmulationContext, -) usm64core.EmulationError { - fmt.Println(i.Argument.Value(ctx)) - ctx.NextInstructionIndex++ - return nil -} - -func NewPutInstruction( - targets []usm64core.Register, - arguments []usm64core.Argument, -) (usm64core.Instruction, core.ResultList) { - valued, results := usm64core.ArgumentToValuedArgument(arguments[0]) +) core.ResultList { + value, results := ctx.ArgumentToValue(i.Arguments[0]) if !results.IsEmpty() { - return nil, results + return results } - return &PutInstruction{Argument: valued}, core.ResultList{} + fmt.Println(value) + return ctx.ContinueToNextInstruction() +} + +func NewPutInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&PutInstruction{ + nonBranchingInstruction: newNonBranchingInstruction(info), + }), core.ResultList{} } -func NewPutInstructionDefinition() gen.InstructionDefinition[usm64core.Instruction] { +func NewPutInstructionDefinition() gen.InstructionDefinition { return &FixedInstructionDefinition{ Targets: 0, Arguments: 1, diff --git a/usm64/isa/terminate.go b/usm64/isa/terminate.go new file mode 100644 index 0000000..c4b879b --- /dev/null +++ b/usm64/isa/terminate.go @@ -0,0 +1,37 @@ +package usm64isa + +import ( + "fmt" + + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" + usm64core "alon.kr/x/usm/usm64/core" +) + +type TerminateInstruction struct{} + +func (i *TerminateInstruction) PossibleNextSteps() ([]gen.StepInfo, core.ResultList) { + return []gen.StepInfo{gen.ReturnFromFunction{}}, core.ResultList{} +} + +func (i *TerminateInstruction) Emulate( + ctx *usm64core.EmulationContext, +) core.ResultList { + fmt.Println("[Terminate]") + ctx.ShouldTerminate = true + return core.ResultList{} +} + +func NewTerminateInstruction( + info *gen.InstructionInfo, +) (gen.BaseInstruction, core.ResultList) { + return gen.BaseInstruction(&TerminateInstruction{}), core.ResultList{} +} + +func NewTerminateInstructionDefinition() gen.InstructionDefinition { + return &FixedInstructionDefinition{ + Targets: 0, + Arguments: 0, + Creator: NewTerminateInstruction, + } +} diff --git a/usm64/managers/context.go b/usm64/managers/context.go index 306f92c..514da2c 100644 --- a/usm64/managers/context.go +++ b/usm64/managers/context.go @@ -2,7 +2,6 @@ package managers import ( "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" ) func NewManagerCreators() gen.ManagerCreators { @@ -13,8 +12,8 @@ func NewManagerCreators() gen.ManagerCreators { } } -func NewGenerationContext() *gen.GenerationContext[usm64core.Instruction] { - return &gen.GenerationContext[usm64core.Instruction]{ +func NewGenerationContext() *gen.GenerationContext { + return &gen.GenerationContext{ ManagerCreators: NewManagerCreators(), Instructions: NewInstructionManager(), PointerSize: 8, diff --git a/usm64/managers/instructions.go b/usm64/managers/instructions.go index 1fd4b73..8fcc190 100644 --- a/usm64/managers/instructions.go +++ b/usm64/managers/instructions.go @@ -6,30 +6,31 @@ import ( "alon.kr/x/list" "alon.kr/x/usm/core" "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" + "alon.kr/x/usm/parse" usm64isa "alon.kr/x/usm/usm64/isa" ) // TODO: optimization: implement using alon.kr/x/faststringmap -type InstructionMap map[string]gen.InstructionDefinition[usm64core.Instruction] +type InstructionMap map[string]gen.InstructionDefinition func (m *InstructionMap) GetInstructionDefinition( name string, -) (gen.InstructionDefinition[usm64core.Instruction], core.ResultList) { + node parse.InstructionNode, +) (gen.InstructionDefinition, core.ResultList) { key := strings.ToLower(name) instDef, ok := (*m)[key] if !ok { return nil, list.FromSingle(core.Result{{ - Type: core.ErrorResult, - Message: "Undefined instruction", - // TODO: add location + Type: core.ErrorResult, + Message: "Undefined instruction", + Location: &node.Operator, }}) } return instDef, core.ResultList{} } -func NewInstructionManager() gen.InstructionManager[usm64core.Instruction] { - return gen.InstructionManager[usm64core.Instruction]( +func NewInstructionManager() gen.InstructionManager { + return gen.InstructionManager( &InstructionMap{ // mov @@ -45,7 +46,8 @@ func NewInstructionManager() gen.InstructionManager[usm64core.Instruction] { "jnz": usm64isa.NewJumpNotZeroInstructionDefinition(), // debug - "put": usm64isa.NewPutInstructionDefinition(), + "put": usm64isa.NewPutInstructionDefinition(), + "term": usm64isa.NewTerminateInstructionDefinition(), }, ) } diff --git a/usm64/managers/labels.go b/usm64/managers/labels.go index eeff696..888a4a6 100644 --- a/usm64/managers/labels.go +++ b/usm64/managers/labels.go @@ -5,17 +5,17 @@ import ( "alon.kr/x/usm/gen" ) -type LabelMap map[string]gen.LabelInfo +type LabelMap map[string]*gen.LabelInfo func (m *LabelMap) GetLabel(name string) *gen.LabelInfo { val, ok := (*m)[name] if !ok { return nil } - return &val + return val } -func (m *LabelMap) NewLabel(label gen.LabelInfo) core.Result { +func (m *LabelMap) NewLabel(label *gen.LabelInfo) core.Result { (*m)[label.Name] = label return nil } diff --git a/usm64/managers/registers.go b/usm64/managers/registers.go index b9027d3..35e9653 100644 --- a/usm64/managers/registers.go +++ b/usm64/managers/registers.go @@ -20,6 +20,14 @@ func (m *RegisterMap) NewRegister(reg *gen.RegisterInfo) core.Result { return nil } +func (m *RegisterMap) GetAllRegisters() []*gen.RegisterInfo { + registers := make([]*gen.RegisterInfo, 0, len(*m)) + for _, reg := range *m { + registers = append(registers, reg) + } + return registers +} + func NewRegisterManager() gen.RegisterManager { return &RegisterMap{} }