diff --git a/.gitignore b/.gitignore index 36d8760..336c342 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,6 @@ # build files build -usm # coverage coverage.txt diff --git a/aarch64/codegen/file.go b/aarch64/codegen/file.go index ceb60fd..628f9d0 100644 --- a/aarch64/codegen/file.go +++ b/aarch64/codegen/file.go @@ -31,14 +31,17 @@ func NewFileCodegenContext(file *gen.FileInfo) *FileCodegenContext { functionIndices := make(map[*gen.FunctionInfo]uint32, len(file.Functions)) offset := uint64(0) - for idx, function := range file.Functions { + idx := uint32(0) + for _, function := range file.Functions { if function.IsDefined() { functionOffsets[function] = offset - functionIndices[function] = uint32(idx) + functionIndices[function] = idx functionSize := uint64(function.Size()) * 4 // TODO: handle overflow? offset += functionSize } + + idx++ } return &FileCodegenContext{ diff --git a/aarch64/codegen/instruction.go b/aarch64/codegen/instruction.go index 3eefb42..14b8d27 100644 --- a/aarch64/codegen/instruction.go +++ b/aarch64/codegen/instruction.go @@ -11,12 +11,12 @@ import ( ) type Instruction interface { - gen.BaseInstruction + gen.InstructionDefinition // Converts the abstract instruction representation into a concrete binary // instruction. - Generate( - *InstructionCodegenContext, + Codegen( + ctx *InstructionCodegenContext, ) (instructions.Instruction, core.ResultList) } @@ -34,7 +34,7 @@ func (ctx *InstructionCodegenContext) InstructionOffsetInFile() uint64 { func (ctx *InstructionCodegenContext) Codegen( buffer *bytes.Buffer, ) core.ResultList { - instruction, ok := ctx.Instruction.(Instruction) + instruction, ok := ctx.InstructionInfo.Definition.(Instruction) if !ok { return list.FromSingle(core.Result{ { @@ -45,7 +45,7 @@ func (ctx *InstructionCodegenContext) Codegen( }) } - binaryInst, results := instruction.Generate(ctx) + binaryInst, results := instruction.Codegen(ctx) if !results.IsEmpty() { return results } diff --git a/aarch64/isa/add.go b/aarch64/isa/add.go index 448e49b..d8d3335 100644 --- a/aarch64/isa/add.go +++ b/aarch64/isa/add.go @@ -9,67 +9,49 @@ import ( "alon.kr/x/usm/gen" ) -type BaseAdd struct { - NonBranchingInstruction +type Add struct { + gen.NonBranchingInstruction } -func (BaseAdd) Operator() string { - return "add" -} - -type AddReg struct { - BaseAdd - instructions.AddShiftedRegister -} - -func (i AddReg) Generate( - *aarch64codegen.InstructionCodegenContext, -) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} -} - -type AddImm struct { - BaseAdd - instructions.AddImmediate +func NewAdd() gen.InstructionDefinition { + return Add{} } -func (i AddImm) Generate( - *aarch64codegen.InstructionCodegenContext, -) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} +func (Add) Operator(*gen.InstructionInfo) string { + return "add" } -type AddDefinition struct{} - -func (d AddDefinition) buildRegisterVariant( +func (add Add) codegenRegisterVariant( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +) (instructions.Instruction, core.ResultList) { Xd, Xn, Xm, results := aarch64translation.BinaryInstructionToAarch64(info) if !results.IsEmpty() { return nil, results } - return AddReg{ - AddShiftedRegister: instructions.NewAddShiftedRegister(Xd, Xn, Xm), - }, core.ResultList{} + inst := instructions.NewAddShiftedRegister(Xd, Xn, Xm) + return inst, core.ResultList{} } -func (AddDefinition) buildImmediateVariant( +func (add Add) codegenImmediateVariant( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +) (instructions.Instruction, core.ResultList) { Xd, Xn, imm, results := aarch64translation.Immediate12InstructionToAarch64(info) if !results.IsEmpty() { return nil, results } - return AddImm{ - AddImmediate: instructions.NewAddImmediate(Xd, Xn, imm), - }, core.ResultList{} + inst := instructions.NewAddImmediate(Xd, Xn, imm) + return inst, core.ResultList{} } -func (d AddDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +func (add Add) Codegen( + ctx *aarch64codegen.InstructionCodegenContext, +) (instructions.Instruction, core.ResultList) { + // TODO: this implementation is very similar to the one in adds.go, and possibly + // other binary arithmetic instructions. Consider refactoring this. + + info := ctx.InstructionInfo results := aarch64translation.ValidateBinaryInstruction(info) if !results.IsEmpty() { return nil, results @@ -77,11 +59,9 @@ func (d AddDefinition) BuildInstruction( switch info.Arguments[1].(type) { case *gen.RegisterArgumentInfo: - return d.buildRegisterVariant(info) - + return add.codegenRegisterVariant(info) case *gen.ImmediateInfo: - return d.buildImmediateVariant(info) - + return add.codegenImmediateVariant(info) default: return nil, list.FromSingle(core.Result{ { @@ -93,6 +73,13 @@ func (d AddDefinition) BuildInstruction( } } -func NewAddInstructionDefinition() gen.InstructionDefinition { - return AddDefinition{} +func (add Add) Validate( + info *gen.InstructionInfo, +) core.ResultList { + // TODO: this is a pretty hacky way to validate the instruction: we create + // a "mock" generation context, and then try to generate the binary + // representation of the instruction. + ctx := aarch64codegen.InstructionCodegenContext{InstructionInfo: info} + _, results := add.Codegen(&ctx) + return results } diff --git a/aarch64/isa/add_test.go b/aarch64/isa/add_test.go index 9cc621c..38d3a2a 100644 --- a/aarch64/isa/add_test.go +++ b/aarch64/isa/add_test.go @@ -22,7 +22,7 @@ func buildInstructionFromSource( t *testing.T, def gen.InstructionDefinition, src string, -) aarch64codegen.Instruction { +) (*gen.InstructionInfo, aarch64codegen.Instruction) { srcView := core.NewSourceView(src) tokenizer := lex.NewTokenizer() @@ -39,18 +39,14 @@ func buildInstructionFromSource( NewFunctionGenerationContext() generator := gen.NewInstructionGenerator() - baseInfo, results := generator.Generate(ctx, node) + info, results := generator.Generate(ctx, node) assert.True(t, results.IsEmpty()) - assert.NotNil(t, baseInfo) + assert.NotNil(t, info) - baseInst, results := def.BuildInstruction(baseInfo) - assert.True(t, results.IsEmpty()) - assert.NotNil(t, baseInst) - - inst, ok := baseInst.(aarch64codegen.Instruction) + inst, ok := info.Definition.(aarch64codegen.Instruction) assert.True(t, ok) - return inst + return info, inst } func assertExpectedCodegen( @@ -59,17 +55,20 @@ func assertExpectedCodegen( expected instructions.Instruction, src string, ) { - inst := buildInstructionFromSource(t, def, src) + info, inst := buildInstructionFromSource(t, def, src) + + generationContext := &aarch64codegen.InstructionCodegenContext{ + InstructionInfo: info, + } - generationContext := &aarch64codegen.InstructionCodegenContext{} - code, results := inst.Generate(generationContext) + code, results := inst.Codegen(generationContext) assert.True(t, results.IsEmpty()) assert.Equal(t, expected.Binary(), code.Binary()) } func TestAddExpectedCodegen(t *testing.T) { - def := aarch64isa.NewAddInstructionDefinition() + def := aarch64isa.NewAdd() testCases := []struct { src string diff --git a/aarch64/isa/adds.go b/aarch64/isa/adds.go index a3e50ff..e421339 100644 --- a/aarch64/isa/adds.go +++ b/aarch64/isa/adds.go @@ -9,67 +9,47 @@ import ( "alon.kr/x/usm/gen" ) -type BaseAdds struct { - NonBranchingInstruction +type Adds struct { + gen.NonBranchingInstruction } -func (BaseAdds) Operator() string { - return "adds" -} - -type AddsReg struct { - BaseAdd - instructions.AddShiftedRegister +func NewAdds() gen.InstructionDefinition { + return Adds{} } -func (i AddsReg) Generate( - *aarch64codegen.InstructionCodegenContext, -) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} -} - -type AddsImm struct { - BaseAdd - instructions.AddsImmediate -} - -func (i AddsImm) Generate( - *aarch64codegen.InstructionCodegenContext, -) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} +func (Adds) Operator(*gen.InstructionInfo) string { + return "adds" } -type AddsDefinition struct{} - -func (d AddsDefinition) buildRegisterVariant( +func (adds Adds) codegenRegisterVariant( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +) (instructions.Instruction, core.ResultList) { Xd, Xn, Xm, results := aarch64translation.BinaryInstructionToAarch64(info) if !results.IsEmpty() { return nil, results } - return AddReg{ - AddShiftedRegister: instructions.NewAddsShiftedRegister(Xd, Xn, Xm), - }, core.ResultList{} + inst := instructions.NewAddsShiftedRegister(Xd, Xn, Xm) + return inst, core.ResultList{} } -func (AddsDefinition) buildImmediateVariant( +func (adds Adds) codegenImmediateVariant( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +) (instructions.Instruction, core.ResultList) { Xd, Xn, imm, results := aarch64translation.Immediate12GPRegisterTargetInstructionToAarch64(info) if !results.IsEmpty() { return nil, results } - return AddsImm{ - AddsImmediate: instructions.NewAddsImmediate(Xd, Xn, imm), - }, core.ResultList{} + inst := instructions.NewAddsImmediate(Xd, Xn, imm) + return inst, core.ResultList{} } -func (d AddsDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +func (adds Adds) Codegen( + ctx *aarch64codegen.InstructionCodegenContext, +) (instructions.Instruction, core.ResultList) { + info := ctx.InstructionInfo + results := aarch64translation.ValidateBinaryInstruction(info) if !results.IsEmpty() { return nil, results @@ -77,10 +57,10 @@ func (d AddsDefinition) BuildInstruction( switch info.Arguments[1].(type) { case *gen.RegisterArgumentInfo: - return d.buildRegisterVariant(info) + return adds.codegenRegisterVariant(info) case *gen.ImmediateInfo: - return d.buildImmediateVariant(info) + return adds.codegenImmediateVariant(info) default: return nil, list.FromSingle(core.Result{ @@ -93,6 +73,13 @@ func (d AddsDefinition) BuildInstruction( } } -func NewAddsInstructionDefinition() gen.InstructionDefinition { - return AddsDefinition{} +func (adds Adds) Validate( + info *gen.InstructionInfo, +) core.ResultList { + // TODO: this is a pretty hacky way to validate the instruction: we create + // a "mock" generation context, and then try to generate the binary + // representation of the instruction. + ctx := aarch64codegen.InstructionCodegenContext{InstructionInfo: info} + _, results := adds.Codegen(&ctx) + return results } diff --git a/aarch64/isa/b.go b/aarch64/isa/b.go index 350d0e7..015b5a8 100644 --- a/aarch64/isa/b.go +++ b/aarch64/isa/b.go @@ -9,24 +9,79 @@ import ( "alon.kr/x/usm/gen" ) -type Branch struct { - Target *gen.LabelInfo +type Branch struct{} + +func NewBranch() gen.InstructionDefinition { + return Branch{} } -func (b Branch) Operator() string { +func (i Branch) Operator(*gen.InstructionInfo) string { return "b" } -func (b Branch) PossibleNextSteps() (gen.StepInfo, core.ResultList) { +func (i Branch) Target( + info *gen.InstructionInfo, +) (*gen.LabelInfo, core.ResultList) { + results := gen.AssertArgumentsExactly(info, 1) + if !results.IsEmpty() { + return nil, results + } + + label, results := aarch64translation.ArgumentToLabelInfo(info.Arguments[0]) + if !results.IsEmpty() { + return nil, results + } + + return label, core.ResultList{} +} + +func (i Branch) PossibleNextSteps( + info *gen.InstructionInfo, +) (gen.StepInfo, core.ResultList) { + target, results := i.Target(info) return gen.StepInfo{ - PossibleBranches: []*gen.LabelInfo{b.Target}, - }, core.ResultList{} + PossibleBranches: []*gen.LabelInfo{target}, + }, results +} + +type branchValidationArtifacts struct { + Target *gen.LabelInfo +} + +func (i Branch) internalValidate( + info *gen.InstructionInfo, +) (*branchValidationArtifacts, core.ResultList) { + results := core.ResultList{} + + target, curResults := i.Target(info) + results.Extend(&curResults) + + curResults = gen.AssertTargetsExactly(info, 0) + results.Extend(&curResults) + + if !results.IsEmpty() { + return nil, results + } + + artifacts := &branchValidationArtifacts{ + Target: target, + } + + return artifacts, core.ResultList{} } -func (b Branch) Generate( +func (i Branch) Codegen( ctx *aarch64codegen.InstructionCodegenContext, ) (instructions.Instruction, core.ResultList) { - targetBasicBlock := b.Target.BasicBlock + info := ctx.InstructionInfo + + artifacts, results := i.internalValidate(info) + if !results.IsEmpty() { + return nil, results + } + + target := artifacts.Target + targetBasicBlock := target.BasicBlock targetOffset := ctx.BasicBlockOffsets[targetBasicBlock] currentOffset := ctx.InstructionOffsetInFunction offset, err := aarch64translation.Uint64DiffToOffset26Align4(targetOffset, currentOffset) @@ -35,7 +90,7 @@ func (b Branch) Generate( return nil, list.FromSingle(core.Result{ { Type: core.ErrorResult, - Message: "Invalid branch offset (arbitrary large offsets are not yet supported)", + Message: "Branch offset too large", Location: ctx.Declaration, }, { @@ -45,37 +100,13 @@ func (b Branch) Generate( }) } - instruction := instructions.B(offset) - return instruction, core.ResultList{} + inst := instructions.NewBranch(offset) + return inst, core.ResultList{} } -type BranchDefinition struct{} - -func (BranchDefinition) BuildInstruction( +func (i Branch) Validate( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - results := core.ResultList{} - - curResults := aarch64translation.AssertArgumentsExactly(info, 1) - results.Extend(&curResults) - - curResults = aarch64translation.AssertTargetsExactly(info, 0) - results.Extend(&curResults) - - if !results.IsEmpty() { - return nil, results - } - - target, curResults := aarch64translation.ArgumentToLabelInfo(info.Arguments[0]) - results.Extend(&curResults) - - if !results.IsEmpty() { - return nil, results - } - - return Branch{Target: target}, core.ResultList{} -} - -func NewBranchInstructionDefinition() gen.InstructionDefinition { - return BranchDefinition{} +) core.ResultList { + _, results := i.internalValidate(info) + return results } diff --git a/aarch64/isa/base.go b/aarch64/isa/base.go deleted file mode 100644 index 33d20d4..0000000 --- a/aarch64/isa/base.go +++ /dev/null @@ -1,12 +0,0 @@ -package aarch64isa - -import ( - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" -) - -type NonBranchingInstruction struct{} - -func (NonBranchingInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { - return gen.StepInfo{PossibleContinue: true}, core.ResultList{} -} diff --git a/aarch64/isa/bcond.go b/aarch64/isa/bcond.go index b7ea46a..76f5cd4 100644 --- a/aarch64/isa/bcond.go +++ b/aarch64/isa/bcond.go @@ -12,24 +12,55 @@ import ( type Bcond struct { Condition immediates.Condition - Target *gen.LabelInfo } -func (b Bcond) Operator() string { +func NewBcond(condition immediates.Condition) gen.InstructionDefinition { + return Bcond{ + Condition: condition, + } +} + +func (b Bcond) Target( + info *gen.InstructionInfo, +) (*gen.LabelInfo, core.ResultList) { + results := gen.AssertArgumentsExactly(info, 1) + if !results.IsEmpty() { + return nil, results + } + + target, results := aarch64translation.ArgumentToLabelInfo(info.Arguments[0]) + if !results.IsEmpty() { + return nil, results + } + + return target, core.ResultList{} +} + +func (b Bcond) Operator(*gen.InstructionInfo) string { return "b." + b.Condition.String() } -func (b Bcond) PossibleNextSteps() (gen.StepInfo, core.ResultList) { +func (b Bcond) PossibleNextSteps(info *gen.InstructionInfo) (gen.StepInfo, core.ResultList) { + target, results := b.Target(info) + if !results.IsEmpty() { + return gen.StepInfo{}, results + } + return gen.StepInfo{ - PossibleBranches: []*gen.LabelInfo{b.Target}, + PossibleBranches: []*gen.LabelInfo{target}, PossibleContinue: true, }, core.ResultList{} } -func (b Bcond) Generate( +func (b Bcond) Codegen( ctx *aarch64codegen.InstructionCodegenContext, ) (instructions.Instruction, core.ResultList) { - targetBasicBlock := b.Target.BasicBlock + target, results := b.Target(ctx.InstructionInfo) + if !results.IsEmpty() { + return nil, results + } + + targetBasicBlock := target.BasicBlock targetOffset := ctx.BasicBlockOffsets[targetBasicBlock] currentOffset := ctx.InstructionOffsetInFunction offset, err := aarch64translation.Uint64DiffToOffset19Align4(targetOffset, currentOffset) @@ -52,35 +83,20 @@ func (b Bcond) Generate( return instruction, core.ResultList{} } -type BcondDefinition struct { - Condition immediates.Condition -} - -func (d BcondDefinition) BuildInstruction( +func (b Bcond) Validate( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +) core.ResultList { results := core.ResultList{} - curResults := aarch64translation.AssertArgumentsExactly(info, 1) - results.Extend(&curResults) - - curResults = aarch64translation.AssertTargetsExactly(info, 0) + _, curResults := b.Target(info) results.Extend(&curResults) - if !results.IsEmpty() { - return nil, results - } - - target, curResults := aarch64translation.ArgumentToLabelInfo(info.Arguments[0]) + curResults = gen.AssertTargetsExactly(info, 0) results.Extend(&curResults) if !results.IsEmpty() { - return nil, results + return results } - return Bcond{Condition: d.Condition, Target: target}, core.ResultList{} -} - -func NewBcondInstructionDefinition(condition immediates.Condition) gen.InstructionDefinition { - return BcondDefinition{Condition: condition} + return core.ResultList{} } diff --git a/aarch64/isa/bl.go b/aarch64/isa/bl.go index 7ea566a..4f65368 100644 --- a/aarch64/isa/bl.go +++ b/aarch64/isa/bl.go @@ -11,23 +11,60 @@ import ( ) type Bl struct { - Target *gen.FunctionInfo + gen.NonBranchingInstruction } -func (b Bl) Operator() string { +func NewBl() gen.InstructionDefinition { + return Bl{} +} + +func (b Bl) Operator(*gen.InstructionInfo) string { return "bl" } -func (b Bl) PossibleNextSteps() (gen.StepInfo, core.ResultList) { - // TODO: add an analysis to check if the target function is a no-return - // function. - return gen.StepInfo{PossibleContinue: true}, core.ResultList{} +func (b Bl) Target( + info *gen.InstructionInfo, +) (*gen.FunctionInfo, core.ResultList) { + results := gen.AssertArgumentsExactly(info, 1) + if !results.IsEmpty() { + return nil, results + } + + target, results := aarch64translation.ArgumentToFunctionInfo(info.Arguments[0]) + if !results.IsEmpty() { + return nil, results + } + + return target, core.ResultList{} } -func (b Bl) registerRelocation(ctx *aarch64codegen.InstructionCodegenContext) { +func (b Bl) Validate(info *gen.InstructionInfo) core.ResultList { + results := core.ResultList{} + + _, curResults := b.Target(info) + results.Extend(&curResults) + + curResults = gen.AssertTargetsExactly(info, 0) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + return core.ResultList{} +} + +func (b Bl) registerRelocation( + ctx *aarch64codegen.InstructionCodegenContext, +) core.ResultList { + target, results := b.Target(ctx.InstructionInfo) + if !results.IsEmpty() { + return results + } + relocation := section64.RelocationBuilder{ Address: uint32(ctx.InstructionOffsetInFile()), - SymbolIndex: ctx.FunctionIndices[b.Target], + SymbolIndex: ctx.FunctionIndices[target], IsRelocationPcRelative: true, Length: section64.RelocationLengthLong, IsRelocationExtern: true, @@ -35,17 +72,27 @@ func (b Bl) registerRelocation(ctx *aarch64codegen.InstructionCodegenContext) { } ctx.Relocations = append(ctx.Relocations, relocation) + return core.ResultList{} } -func (b Bl) Generate( +func (b Bl) Codegen( ctx *aarch64codegen.InstructionCodegenContext, ) (instructions.Instruction, core.ResultList) { - targetOffset, ok := ctx.FunctionOffsets[b.Target] + target, results := b.Target(ctx.InstructionInfo) + if !results.IsEmpty() { + return nil, results + } + + targetOffset, ok := ctx.FunctionOffsets[target] if !ok { // Target function is not defined: we add a relocation to the symbol // and let the linker resolve it. - b.registerRelocation(ctx) - return instructions.BL(0), core.ResultList{} + results = b.registerRelocation(ctx) + if !results.IsEmpty() { + return nil, results + } + + return instructions.NewBl(0), core.ResultList{} } currentOffset := ctx.InstructionOffsetInFile() @@ -65,36 +112,5 @@ func (b Bl) Generate( }) } - return instructions.BL(offset), core.ResultList{} -} - -type BlDefinition struct{} - -func (BlDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - results := core.ResultList{} - - curResults := aarch64translation.AssertArgumentsExactly(info, 1) - results.Extend(&curResults) - - curResults = aarch64translation.AssertTargetsExactly(info, 0) - results.Extend(&curResults) - - if !results.IsEmpty() { - return nil, results - } - - target, curResults := aarch64translation.ArgumentToFunctionInfo(info.Arguments[0]) - results.Extend(&curResults) - - if !results.IsEmpty() { - return nil, results - } - - return Bl{Target: target}, core.ResultList{} -} - -func NewBlInstructionDefinition() gen.InstructionDefinition { - return BlDefinition{} + return instructions.NewBl(offset), core.ResultList{} } diff --git a/aarch64/isa/movz.go b/aarch64/isa/movz.go index 33f8332..2dd0e7c 100644 --- a/aarch64/isa/movz.go +++ b/aarch64/isa/movz.go @@ -1,7 +1,9 @@ package aarch64isa import ( + "alon.kr/x/aarch64codegen/immediates" "alon.kr/x/aarch64codegen/instructions" + "alon.kr/x/aarch64codegen/registers" aarch64codegen "alon.kr/x/usm/aarch64/codegen" aarch64translation "alon.kr/x/usm/aarch64/translation" "alon.kr/x/usm/core" @@ -9,39 +11,40 @@ import ( ) type Movz struct { - NonBranchingInstruction - instructions.Movz + gen.NonBranchingInstruction } -func (Movz) Operator() string { - return "movz" +func NewMovz() gen.InstructionDefinition { + return Movz{} } -func (i Movz) Generate( - *aarch64codegen.InstructionCodegenContext, -) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} +func (Movz) Operator(*gen.InstructionInfo) string { + return "movz" } -type MovzDefinition struct{} +func (i Movz) Xd(info *gen.InstructionInfo) (registers.GPRegister, core.ResultList) { + results := gen.AssertTargetsExactly(info, 1) -func (MovzDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - results := core.ResultList{} + if !results.IsEmpty() { + return registers.GPRegister(0), results + } - curResults := aarch64translation.AssertTargetsExactly(info, 1) - results.Extend(&curResults) + Xd, results := aarch64translation.TargetToAarch64GPRegister(info.Targets[0]) + if !results.IsEmpty() { + return registers.GPRegister(0), results + } - curResults = aarch64translation.AssertArgumentsBetween(info, 1, 2) - results.Extend(&curResults) + return Xd, core.ResultList{} +} +func (i Movz) Immediate( + info *gen.InstructionInfo, +) (immediates.Immediate16, instructions.MovShift, core.ResultList) { + results := gen.AssertArgumentsBetween(info, 1, 2) if !results.IsEmpty() { - return nil, results - } + return immediates.Immediate16(0), instructions.MovShift0, results - Xd, curResults := aarch64translation.TargetToAarch64GPRegister(info.Targets[0]) - results.Extend(&curResults) + } imm, curResults := aarch64translation.ArgumentToAarch64Immediate16(info.Arguments[0]) results.Extend(&curResults) @@ -55,14 +58,43 @@ func (MovzDefinition) BuildInstruction( } if !results.IsEmpty() { - return nil, results + return immediates.Immediate16(0), instructions.MovShift0, results + } + + return imm, shift, core.ResultList{} +} + +func (i Movz) Validate(info *gen.InstructionInfo) core.ResultList { + results := core.ResultList{} + + _, curResults := i.Xd(info) + results.Extend(&curResults) + + _, _, curResults = i.Immediate(info) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results } - return Movz{ - Movz: instructions.MOVZ(Xd, imm, shift), - }, core.ResultList{} + return core.ResultList{} } -func NewMovzInstructionDefinition() gen.InstructionDefinition { - return MovzDefinition{} +func (i Movz) Codegen( + ctx *aarch64codegen.InstructionCodegenContext, +) (instructions.Instruction, core.ResultList) { + info := ctx.InstructionInfo + results := core.ResultList{} + + Xd, curResults := i.Xd(info) + results.Extend(&curResults) + + imm, shift, curResults := i.Immediate(info) + results.Extend(&curResults) + + if !results.IsEmpty() { + return nil, results + } + + return instructions.MOVZ(Xd, imm, shift), core.ResultList{} } diff --git a/aarch64/isa/ret.go b/aarch64/isa/ret.go index e75a070..cb2b87b 100644 --- a/aarch64/isa/ret.go +++ b/aarch64/isa/ret.go @@ -9,56 +9,61 @@ import ( "alon.kr/x/usm/gen" ) -type Ret struct { - instructions.Ret +type Ret struct{} + +func NewRet() gen.InstructionDefinition { + return Ret{} } -func (Ret) Operator() string { +func (Ret) Operator(*gen.InstructionInfo) string { return "ret" } -func (Ret) PossibleNextSteps() (gen.StepInfo, core.ResultList) { +func (Ret) PossibleNextSteps(*gen.InstructionInfo) (gen.StepInfo, core.ResultList) { return gen.StepInfo{PossibleReturn: true}, core.ResultList{} } -func (i Ret) Generate( - *aarch64codegen.InstructionCodegenContext, +func (i Ret) Codegen( + ctx *aarch64codegen.InstructionCodegenContext, ) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} -} - -type RetDefinition struct{} - -func (RetDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - results := core.ResultList{} - - curResults := aarch64translation.AssertArgumentsBetween(info, 0, 1) - results.Extend(&curResults) + info := ctx.InstructionInfo + Xn, results := i.Xn(info) + if !results.IsEmpty() { + return nil, results + } - curResults = aarch64translation.AssertTargetsExactly(info, 0) - results.Extend(&curResults) + return instructions.RET(Xn), core.ResultList{} +} +func (i Ret) Xn(info *gen.InstructionInfo) (registers.GPRegister, core.ResultList) { + results := gen.AssertArgumentsBetween(info, 0, 1) if !results.IsEmpty() { - return nil, results + return registers.GPRegister(0), results } Xn := registers.GPRegisterX30 if len(info.Arguments) > 0 { - Xn, curResults = aarch64translation.ArgumentToAarch64GPRegister(info.Arguments[0]) - results.Extend(&curResults) + Xn, results = aarch64translation.ArgumentToAarch64GPRegister(info.Arguments[0]) + if !results.IsEmpty() { + return registers.GPRegister(0), results + } } + return Xn, core.ResultList{} +} + +func (i Ret) Validate(info *gen.InstructionInfo) core.ResultList { + results := core.ResultList{} + + _, curResults := i.Xn(info) + results.Extend(&curResults) + + curResults = gen.AssertTargetsExactly(info, 0) + results.Extend(&curResults) + if !results.IsEmpty() { - return nil, results + return results } - return Ret{ - instructions.RET(Xn), - }, core.ResultList{} -} - -func NewRetInstructionDefinition() gen.InstructionDefinition { - return RetDefinition{} + return core.ResultList{} } diff --git a/aarch64/isa/sub.go b/aarch64/isa/sub.go index 67c411d..e29d9ab 100644 --- a/aarch64/isa/sub.go +++ b/aarch64/isa/sub.go @@ -9,86 +9,56 @@ import ( "alon.kr/x/usm/gen" ) -type BaseSub struct { - NonBranchingInstruction +type Sub struct { + gen.NonBranchingInstruction } -func (BaseSub) Operator() string { - return "sub" -} - -type SubReg struct { - BaseSub - instructions.SubShiftedRegister -} - -func (i SubReg) Generate( - *aarch64codegen.InstructionCodegenContext, -) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} +func NewSub() gen.InstructionDefinition { + return Sub{} } -type SubImm struct { - BaseSub - instructions.SubImmediate -} - -func (i SubImm) Generate( - *aarch64codegen.InstructionCodegenContext, -) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} +func (Sub) Operator(*gen.InstructionInfo) string { + return "sub" } -type SubDefinition struct{} - -func (SubDefinition) buildRegisterVariant( +func (Sub) codegenRegisterVariant( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +) (instructions.Instruction, core.ResultList) { Xd, Xn, Xm, results := aarch64translation.BinaryInstructionToAarch64(info) if !results.IsEmpty() { return nil, results } - return SubReg{ - SubShiftedRegister: instructions.NewSubShiftedRegister(Xd, Xn, Xm), - }, core.ResultList{} + inst := instructions.NewSubShiftedRegister(Xd, Xn, Xm) + return inst, core.ResultList{} } -func (SubDefinition) buildImmediateVariant( +func (Sub) codegenImmediateVariant( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +) (instructions.Instruction, core.ResultList) { Xd, Xn, imm, results := aarch64translation.Immediate12GPRegisterTargetInstructionToAarch64(info) if !results.IsEmpty() { return nil, results } - return SubImm{ - SubImmediate: instructions.NewSubImmediate(Xd, Xn, imm), - }, core.ResultList{} + inst := instructions.NewSubImmediate(Xd, Xn, imm) + return inst, core.ResultList{} } -func (d SubDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - results := core.ResultList{} - - curResults := aarch64translation.AssertTargetsExactly(info, 1) - results.Extend(&curResults) - - curResults = aarch64translation.AssertArgumentsExactly(info, 2) - results.Extend(&curResults) - +func (i Sub) Codegen( + ctx *aarch64codegen.InstructionCodegenContext, +) (instructions.Instruction, core.ResultList) { + info := ctx.InstructionInfo + results := aarch64translation.ValidateBinaryInstruction(info) if !results.IsEmpty() { return nil, results } switch info.Arguments[1].(type) { case *gen.RegisterArgumentInfo: - return d.buildRegisterVariant(info) - + return i.codegenRegisterVariant(info) case *gen.ImmediateInfo: - return d.buildImmediateVariant(info) - + return i.codegenImmediateVariant(info) default: return nil, list.FromSingle(core.Result{ { @@ -100,6 +70,13 @@ func (d SubDefinition) BuildInstruction( } } -func NewSubInstructionDefinition() gen.InstructionDefinition { - return SubDefinition{} +func (i Sub) Validate( + info *gen.InstructionInfo, +) core.ResultList { + // TODO: this is a pretty hacky way to validate the instruction: we create + // a "mock" generation context, and then try to generate the binary + // representation of the instruction. + ctx := aarch64codegen.InstructionCodegenContext{InstructionInfo: info} + _, results := i.Codegen(&ctx) + return results } diff --git a/aarch64/isa/subs.go b/aarch64/isa/subs.go index 35f98ff..458c6df 100644 --- a/aarch64/isa/subs.go +++ b/aarch64/isa/subs.go @@ -9,67 +9,47 @@ import ( "alon.kr/x/usm/gen" ) -type BaseSubs struct { - NonBranchingInstruction +type Subs struct { + gen.NonBranchingInstruction } -func (BaseSubs) Operator() string { - return "subs" -} - -type SubsReg struct { - BaseSubs - instructions.SubShiftedRegister +func NewSubs() gen.InstructionDefinition { + return Subs{} } -func (i SubsReg) Generate( - *aarch64codegen.InstructionCodegenContext, -) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} -} - -type SubsImm struct { - BaseSubs - instructions.SubImmediate -} - -func (i SubsImm) Generate( - *aarch64codegen.InstructionCodegenContext, -) (instructions.Instruction, core.ResultList) { - return i, core.ResultList{} +func (Subs) Operator(*gen.InstructionInfo) string { + return "subs" } -type SubsDefinition struct{} - -func (d SubsDefinition) buildRegisterVariant( +func (i Subs) codegenRegisterVariant( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +) (instructions.Instruction, core.ResultList) { Xd, Xn, Xm, results := aarch64translation.BinaryInstructionToAarch64(info) if !results.IsEmpty() { return nil, results } - return SubsReg{ - SubShiftedRegister: instructions.NewSubsShiftedRegister(Xd, Xn, Xm), - }, core.ResultList{} + inst := instructions.NewSubsShiftedRegister(Xd, Xn, Xm) + return inst, core.ResultList{} } -func (SubsDefinition) buildImmediateVariant( +func (i Subs) codegenImmediateVariant( info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +) (instructions.Instruction, core.ResultList) { Xd, Xn, imm, results := aarch64translation.Immediate12GPRegisterTargetInstructionToAarch64(info) if !results.IsEmpty() { return nil, results } - return SubsImm{ - SubImmediate: instructions.NewSubsImmediate(Xd, Xn, imm), - }, core.ResultList{} + inst := instructions.NewSubsImmediate(Xd, Xn, imm) + return inst, core.ResultList{} } -func (d SubsDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { +func (i Subs) Codegen( + ctx *aarch64codegen.InstructionCodegenContext, +) (instructions.Instruction, core.ResultList) { + info := ctx.InstructionInfo + results := aarch64translation.ValidateBinaryInstruction(info) if !results.IsEmpty() { return nil, results @@ -77,10 +57,10 @@ func (d SubsDefinition) BuildInstruction( switch info.Arguments[1].(type) { case *gen.RegisterArgumentInfo: - return d.buildRegisterVariant(info) + return i.codegenRegisterVariant(info) case *gen.ImmediateInfo: - return d.buildImmediateVariant(info) + return i.codegenImmediateVariant(info) default: return nil, list.FromSingle(core.Result{ @@ -93,6 +73,13 @@ func (d SubsDefinition) BuildInstruction( } } -func NewSubsInstructionDefinition() gen.InstructionDefinition { - return SubsDefinition{} +func (i Subs) Validate( + info *gen.InstructionInfo, +) core.ResultList { + // TODO: this is a pretty hacky way to validate the instruction: we create + // a "mock" generation context, and then try to generate the binary + // representation of the instruction. + ctx := aarch64codegen.InstructionCodegenContext{InstructionInfo: info} + _, results := i.Codegen(&ctx) + return results } diff --git a/aarch64/managers/instructions.go b/aarch64/managers/instructions.go index eef6329..27f4cb7 100644 --- a/aarch64/managers/instructions.go +++ b/aarch64/managers/instructions.go @@ -11,36 +11,36 @@ func NewInstructionManager() gen.InstructionManager { return gen.NewInstructionMap( []faststringmap.MapEntry[gen.InstructionDefinition]{ // Move - {Key: "movz", Value: aarch64isa.NewMovzInstructionDefinition()}, + {Key: "movz", Value: aarch64isa.NewMovz()}, // Arithmetic - {Key: "add", Value: aarch64isa.NewAddInstructionDefinition()}, - {Key: "adds", Value: aarch64isa.NewAddsInstructionDefinition()}, - {Key: "sub", Value: aarch64isa.NewSubInstructionDefinition()}, - {Key: "subs", Value: aarch64isa.NewSubsInstructionDefinition()}, + {Key: "add", Value: aarch64isa.NewAdd()}, + {Key: "adds", Value: aarch64isa.NewAdds()}, + {Key: "sub", Value: aarch64isa.NewSub()}, + {Key: "subs", Value: aarch64isa.NewSubs()}, // Control flow - {Key: "b", Value: aarch64isa.NewBranchInstructionDefinition()}, - {Key: "bl", Value: aarch64isa.NewBlInstructionDefinition()}, - {Key: "ret", Value: aarch64isa.NewRetInstructionDefinition()}, + {Key: "b", Value: aarch64isa.NewBranch()}, + {Key: "bl", Value: aarch64isa.NewBl()}, + {Key: "ret", Value: aarch64isa.NewRet()}, // Conditional branches - {Key: "b.eq", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionEq)}, - {Key: "b.ne", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionNe)}, - {Key: "b.cs", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionCs)}, - {Key: "b.cc", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionCc)}, - {Key: "b.mi", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionMi)}, - {Key: "b.pl", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionPl)}, - {Key: "b.vs", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionVs)}, - {Key: "b.vc", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionVc)}, - {Key: "b.hi", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionHi)}, - {Key: "b.ls", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionLs)}, - {Key: "b.ge", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionGe)}, - {Key: "b.lt", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionLt)}, - {Key: "b.gt", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionGt)}, - {Key: "b.le", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionLe)}, - {Key: "b.al", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionAl)}, - {Key: "b.nv", Value: aarch64isa.NewBcondInstructionDefinition(immediates.ConditionNv)}, + {Key: "b.eq", Value: aarch64isa.NewBcond(immediates.ConditionEq)}, + {Key: "b.ne", Value: aarch64isa.NewBcond(immediates.ConditionNe)}, + {Key: "b.cs", Value: aarch64isa.NewBcond(immediates.ConditionCs)}, + {Key: "b.cc", Value: aarch64isa.NewBcond(immediates.ConditionCc)}, + {Key: "b.mi", Value: aarch64isa.NewBcond(immediates.ConditionMi)}, + {Key: "b.pl", Value: aarch64isa.NewBcond(immediates.ConditionPl)}, + {Key: "b.vs", Value: aarch64isa.NewBcond(immediates.ConditionVs)}, + {Key: "b.vc", Value: aarch64isa.NewBcond(immediates.ConditionVc)}, + {Key: "b.hi", Value: aarch64isa.NewBcond(immediates.ConditionHi)}, + {Key: "b.ls", Value: aarch64isa.NewBcond(immediates.ConditionLs)}, + {Key: "b.ge", Value: aarch64isa.NewBcond(immediates.ConditionGe)}, + {Key: "b.lt", Value: aarch64isa.NewBcond(immediates.ConditionLt)}, + {Key: "b.gt", Value: aarch64isa.NewBcond(immediates.ConditionGt)}, + {Key: "b.le", Value: aarch64isa.NewBcond(immediates.ConditionLe)}, + {Key: "b.al", Value: aarch64isa.NewBcond(immediates.ConditionAl)}, + {Key: "b.nv", Value: aarch64isa.NewBcond(immediates.ConditionNv)}, }, false, ) diff --git a/aarch64/translation/arguments.go b/aarch64/translation/arguments.go index cac3b09..9d87bfc 100644 --- a/aarch64/translation/arguments.go +++ b/aarch64/translation/arguments.go @@ -1,9 +1,7 @@ package aarch64translation import ( - "fmt" "math/big" - "strconv" "alon.kr/x/aarch64codegen/immediates" "alon.kr/x/aarch64codegen/instructions" @@ -13,79 +11,6 @@ import ( "alon.kr/x/usm/gen" ) -func AssertAtLeastArguments( - info *gen.InstructionInfo, - atLeast int, -) core.ResultList { - if len(info.Arguments) < atLeast { - return list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: fmt.Sprintf("Expected at least %d arguments", atLeast), - Location: info.Declaration, - }, - }) - } - - return core.ResultList{} -} - -func AssertAtMostArguments( - info *gen.InstructionInfo, - atMost int, -) core.ResultList { - if len(info.Arguments) > atMost { - return list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: fmt.Sprintf("Expected at most %d arguments", atMost), - Location: info.Declaration, - }, - }) - } - - return core.ResultList{} -} - -func AssertArgumentsBetween( - info *gen.InstructionInfo, - atLeast int, - atMost int, -) core.ResultList { - if len(info.Arguments) < atLeast || len(info.Arguments) > atMost { - return list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: fmt.Sprintf( - "Expected between %d and %d arguments", - atLeast, - atMost, - ), - Location: info.Declaration, - }, - }) - } - - return core.ResultList{} -} - -func AssertArgumentsExactly( - info *gen.InstructionInfo, - count int, -) core.ResultList { - if len(info.Arguments) != count { - return list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: fmt.Sprintf("Expected %d arguments", count), - Location: info.Declaration, - }, - }) - } - - return core.ResultList{} -} - func ArgumentToAarch64GPRegister( argument gen.ArgumentInfo, ) (registers.GPRegister, core.ResultList) { @@ -222,46 +147,11 @@ fail: }) } -func AssertBigIntInSet( - view *core.UnmanagedSourceView, - bigInt *big.Int, - options []int64, -) (int64, core.ResultList) { - var value int64 - isInvalid := !bigInt.IsInt64() - - if isInvalid { - goto fail - } - - value = bigInt.Int64() - for _, option := range options { - if value == option { - return value, core.ResultList{} - } - } - -fail: - message := "Expected one of " - message += "#" + strconv.FormatInt(options[0], 10) - for _, option := range options { - message += ", #" + strconv.FormatInt(option, 10) - } - - return 0, list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: message, - Location: view, - }, - }) -} - func BigIntToAarch64MovShift( view *core.UnmanagedSourceView, bigInt *big.Int, ) (instructions.MovShift, core.ResultList) { - value, results := AssertBigIntInSet(view, bigInt, []int64{0, 16, 32, 48}) + value, results := gen.AssertBigIntInSet(view, bigInt, []int64{0, 16, 32, 48}) if !results.IsEmpty() { return 0, results } diff --git a/aarch64/translation/instructions.go b/aarch64/translation/instructions.go index 4ada212..ee22d5c 100644 --- a/aarch64/translation/instructions.go +++ b/aarch64/translation/instructions.go @@ -10,9 +10,9 @@ import ( func ValidateBinaryInstruction( info *gen.InstructionInfo, ) core.ResultList { - results := AssertTargetsExactly(info, 1) + results := gen.AssertTargetsExactly(info, 1) - argumentResults := AssertArgumentsExactly(info, 2) + argumentResults := gen.AssertArgumentsExactly(info, 2) results.Extend(&argumentResults) return results diff --git a/aarch64/translation/targets.go b/aarch64/translation/targets.go index 0de54ac..e3983c2 100644 --- a/aarch64/translation/targets.go +++ b/aarch64/translation/targets.go @@ -1,31 +1,11 @@ package aarch64translation import ( - "fmt" - "alon.kr/x/aarch64codegen/registers" - "alon.kr/x/list" "alon.kr/x/usm/core" "alon.kr/x/usm/gen" ) -func AssertTargetsExactly( - info *gen.InstructionInfo, - count int, -) core.ResultList { - if len(info.Targets) != count { - return list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: fmt.Sprintf("Expected %d targets", count), - Location: info.Declaration, - }, - }) - } - - return core.ResultList{} -} - func TargetToAarch64GPRegister( target *gen.TargetInfo, ) (registers.GPRegister, core.ResultList) { diff --git a/gen/assert.go b/gen/assert.go new file mode 100644 index 0000000..9641c70 --- /dev/null +++ b/gen/assert.go @@ -0,0 +1,141 @@ +package gen + +import ( + "fmt" + "math/big" + "strconv" + + "alon.kr/x/list" + "alon.kr/x/usm/core" +) + +// MARK: Arguments + +func AssertAtLeastArguments( + info *InstructionInfo, + atLeast int, +) core.ResultList { + if len(info.Arguments) < atLeast { + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: fmt.Sprintf("Expected at least %d arguments", atLeast), + Location: info.Declaration, + }, + }) + } + + return core.ResultList{} +} + +func AssertAtMostArguments( + info *InstructionInfo, + atMost int, +) core.ResultList { + if len(info.Arguments) > atMost { + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: fmt.Sprintf("Expected at most %d arguments", atMost), + Location: info.Declaration, + }, + }) + } + + return core.ResultList{} +} + +func AssertArgumentsBetween( + info *InstructionInfo, + atLeast int, + atMost int, +) core.ResultList { + if len(info.Arguments) < atLeast || len(info.Arguments) > atMost { + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: fmt.Sprintf( + "Expected between %d and %d arguments", + atLeast, + atMost, + ), + Location: info.Declaration, + }, + }) + } + + return core.ResultList{} +} + +func AssertArgumentsExactly( + info *InstructionInfo, + count int, +) core.ResultList { + if len(info.Arguments) != count { + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: fmt.Sprintf("Expected %d arguments", count), + Location: info.Declaration, + }, + }) + } + + return core.ResultList{} +} + +// MARK: Targets + +func AssertTargetsExactly( + info *InstructionInfo, + count int, +) core.ResultList { + if len(info.Targets) != count { + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: fmt.Sprintf("Expected %d targets", count), + Location: info.Declaration, + }, + }) + } + + return core.ResultList{} +} + +// MARK: Integers + +func AssertBigIntInSet( + view *core.UnmanagedSourceView, + bigInt *big.Int, + options []int64, +) (int64, core.ResultList) { + var value int64 + isInvalid := !bigInt.IsInt64() + + if isInvalid { + goto fail + } + + value = bigInt.Int64() + for _, option := range options { + if value == option { + return value, core.ResultList{} + } + } + +fail: + message := "Expected one of " + message += "#" + strconv.FormatInt(options[0], 10) + for _, option := range options[1:] { + message += ", #" + strconv.FormatInt(option, 10) + } + + return 0, list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: message, + Location: view, + }, + }) +} diff --git a/gen/basic_block_info.go b/gen/basic_block_info.go index 2873075..2d32c8d 100644 --- a/gen/basic_block_info.go +++ b/gen/basic_block_info.go @@ -1,6 +1,10 @@ package gen -import "slices" +import ( + "slices" + + "alon.kr/x/usm/core" +) type BasicBlockInfo struct { *FunctionInfo @@ -28,6 +32,17 @@ func NewEmptyBasicBlockInfo(function *FunctionInfo) *BasicBlockInfo { } } +func (b *BasicBlockInfo) Validate() core.ResultList { + results := core.ResultList{} + + for _, instruction := range b.Instructions { + curResults := instruction.Validate() + results.Extend(&curResults) + } + + return results +} + // Returns the number of instructions in the basic block. func (b *BasicBlockInfo) Size() int { return len(b.Instructions) diff --git a/gen/file_generator.go b/gen/file_generator.go index 24490d3..7e65696 100644 --- a/gen/file_generator.go +++ b/gen/file_generator.go @@ -86,8 +86,14 @@ func (g *FileGenerator) Generate( return nil, results } - file := &FileInfo{ - Functions: functions, + file := NewFileInfo() + for _, function := range functions { + file.AppendFunction(function) + } + + results = file.Validate() + if !results.IsEmpty() { + return nil, results } return file, core.ResultList{} diff --git a/gen/file_info.go b/gen/file_info.go index fc2ba77..9df4bf6 100644 --- a/gen/file_info.go +++ b/gen/file_info.go @@ -1,7 +1,15 @@ package gen +import "alon.kr/x/usm/core" + type FileInfo struct { - Functions []*FunctionInfo + Functions map[string]*FunctionInfo +} + +func NewFileInfo() *FileInfo { + return &FileInfo{ + Functions: make(map[string]*FunctionInfo), + } } func (i *FileInfo) String() string { @@ -11,11 +19,17 @@ func (i *FileInfo) String() string { return s } - for _, function := range i.Functions[:len(i.Functions)-1] { - s += function.String() + "\n" + functions := make([]*FunctionInfo, 0, len(i.Functions)) + for _, function := range i.Functions { + functions = append(functions, function) } - s += i.Functions[len(i.Functions)-1].String() + for _, f := range functions[:len(functions)-1] { + s += f.String() + "\n" + } + + lastFunction := functions[len(functions)-1] + s += lastFunction.String() return s } @@ -23,10 +37,31 @@ func (i *FileInfo) String() string { // GetFunction returns the function with the given name, or nil if it does not // exist. func (i *FileInfo) GetFunction(name string) *FunctionInfo { + info, ok := i.Functions[name] + if !ok { + return nil + } + + return info +} + +func (i *FileInfo) AppendFunction(function *FunctionInfo) { + oldFunction, ok := i.Functions[function.Name] + if ok { + oldFunction.FileInfo = nil + } + + function.FileInfo = i + i.Functions[function.Name] = function +} + +func (i *FileInfo) Validate() core.ResultList { + results := core.ResultList{} + for _, function := range i.Functions { - if function.Name == name { - return function - } + curResults := function.Validate() + results.Extend(&curResults) } - return nil + + return results } diff --git a/gen/function_generator.go b/gen/function_generator.go index f9f3592..24b6c19 100644 --- a/gen/function_generator.go +++ b/gen/function_generator.go @@ -144,7 +144,7 @@ func (g *FunctionGenerator) getInstructionBranchingDestinations( info *InstructionInfo, labels functionLabelData, ) ([]int, core.ResultList) { - steps, results := info.Instruction.PossibleNextSteps() + steps, results := info.Definition.PossibleNextSteps(info) if !results.IsEmpty() { return nil, results } @@ -286,7 +286,7 @@ func (g *FunctionGenerator) generateBasicBlocks( basicBlockLength := len(currentBasicBlock.Instructions) lastInstruction := currentBasicBlock.Instructions[basicBlockLength-1] - steps, results := lastInstruction.Instruction.PossibleNextSteps() + steps, results := lastInstruction.Definition.PossibleNextSteps(lastInstruction) if !results.IsEmpty() { return results } diff --git a/gen/function_generator_test.go b/gen/function_generator_test.go index 26084f0..506ed25 100644 --- a/gen/function_generator_test.go +++ b/gen/function_generator_test.go @@ -37,9 +37,9 @@ func generateFunctionFromSource( func TestSimpleFunctionGeneration(t *testing.T) { src := `func $32 @add $32 %a { .entry - $32 %b = ADD %a $32 #1 - $32 %c = ADD %b %a - RET + $32 %b = add %a $32 #1 + $32 %c = add %b %a + ret } ` @@ -90,14 +90,14 @@ func TestSimpleFunctionGeneration(t *testing.T) { func TestIfElseFunctionGeneration(t *testing.T) { src := `func @toBool $32 %n { .entry - JZ %n .zero + jz %n .zero .nonzero - $32 %bool = ADD $32 #1 $32 #0 - JMP .end + $32 %bool = add $32 #1 $32 #0 + j .end .zero - $32 %bool = ADD $32 #0 $32 #0 + $32 %bool = add $32 #0 $32 #0 .end - RET + ret } ` @@ -131,7 +131,7 @@ func TestEmptyFunctionGeneration(t *testing.T) { func TestNoReturnFunctionGeneration(t *testing.T) { src := `func @noReturn { - $32 %n = ADD $32 #1 $32 #2 + $32 %n = add $32 #1 $32 #2 }` function, results := generateFunctionFromSource(t, src) assert.False(t, results.IsEmpty()) @@ -142,8 +142,8 @@ func TestNoReturnFunctionGeneration(t *testing.T) { func TestNoExplicitRegisterType(t *testing.T) { src := `func @noExplicitType { - %a = ADD $32 #1 $32 #2 - RET + %a = add $32 #1 $32 #2 + ret }` function, results := generateFunctionFromSource(t, src) @@ -155,9 +155,9 @@ func TestNoExplicitRegisterType(t *testing.T) { func TestExplicitRegisterDefinitionNotOnSecondSight(t *testing.T) { src := `func @main { - %a = ADD $32 #0 $32 #0 - $32 %a = ADD %a $32 #1 - RET + %a = add $32 #0 $32 #0 + $32 %a = add %a $32 #1 + ret }` function, results := generateFunctionFromSource(t, src) assert.True(t, results.IsEmpty()) diff --git a/gen/function_info.go b/gen/function_info.go index 965d412..5fe4994 100644 --- a/gen/function_info.go +++ b/gen/function_info.go @@ -3,6 +3,8 @@ package gen import "alon.kr/x/usm/core" type FunctionInfo struct { + *FileInfo + Name string Declaration *core.UnmanagedSourceView @@ -73,3 +75,14 @@ func (i *FunctionInfo) String() string { s += "\n" return s } + +func (i *FunctionInfo) Validate() core.ResultList { + results := core.ResultList{} + + for _, block := range i.CollectBasicBlocks() { + curResults := block.Validate() + results.Extend(&curResults) + } + + return results +} diff --git a/gen/instruction_definition.go b/gen/instruction_definition.go index 94a62d2..d079838 100644 --- a/gen/instruction_definition.go +++ b/gen/instruction_definition.go @@ -4,27 +4,60 @@ import ( "alon.kr/x/usm/core" ) -type BaseInstruction interface { +type InstructionDefinition 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) + PossibleNextSteps(*InstructionInfo) (StepInfo, core.ResultList) // Returns the string that represents the operator of the instruction. // For example, for the add instruction this method would return "ADD". // // This is required because some instructions may be generated automatically, // and we want to be able to display them in a human-readable format. - Operator() string + Operator(*InstructionInfo) string + + // Validate the instruction information structure, according to the + // expected arguments, targets, and other related information. + // + // Instruction validation should as one of the last steps in the compilation + // process, and you should be able to assume that all relevant information + // in the structures is filled in and propagated correctly. + Validate(*InstructionInfo) 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) +type NonBranchingInstruction struct{} + +func (NonBranchingInstruction) PossibleNextSteps(*InstructionInfo) (StepInfo, core.ResultList) { + return StepInfo{ + PossibleContinue: true, + }, core.ResultList{} +} + +type BranchToLabelArguments struct{} + +func (BranchToLabelArguments) PossibleNextSteps(info *InstructionInfo) (StepInfo, core.ResultList) { + return StepInfo{ + PossibleBranches: ArgumentsToLabels(info.Arguments), + }, core.ResultList{} +} + +type BranchesToLabelArgumentsOrContinues struct{} + +func (BranchesToLabelArgumentsOrContinues) PossibleNextSteps(info *InstructionInfo) (StepInfo, core.ResultList) { + return StepInfo{ + PossibleBranches: ArgumentsToLabels(info.Arguments), + PossibleContinue: true, + }, core.ResultList{} +} + +type ReturningInstruction struct{} + +func (ReturningInstruction) PossibleNextSteps(*InstructionInfo) (StepInfo, core.ResultList) { + return StepInfo{ + PossibleReturn: true, + }, core.ResultList{} } diff --git a/gen/instruction_generator.go b/gen/instruction_generator.go index 04bd526..89c721b 100644 --- a/gen/instruction_generator.go +++ b/gen/instruction_generator.go @@ -121,7 +121,7 @@ func (g *InstructionGenerator) Generate( } instName := ViewToSourceString(ctx.FileGenerationContext, node.Operator) - instDef, results := ctx.Instructions.GetInstructionDefinition(instName, node) + instDef, results := ctx.Instructions.GetInstructionDefinition(instName, &node) arguments, curResults := g.generateArguments(&instCtx, node) results.Extend(&curResults) @@ -137,11 +137,6 @@ func (g *InstructionGenerator) Generate( instCtx.InstructionInfo.AppendTarget(targets...) instCtx.InstructionInfo.AppendArgument(arguments...) - instruction, results := instDef.BuildInstruction(instCtx.InstructionInfo) - if !results.IsEmpty() { - return nil, results - } - - instCtx.InstructionInfo.Instruction = instruction + instCtx.InstructionInfo.SetInstruction(instDef) return instCtx.InstructionInfo, core.ResultList{} } diff --git a/gen/instruction_generator_test.go b/gen/instruction_generator_test.go index c3c2a60..04b1572 100644 --- a/gen/instruction_generator_test.go +++ b/gen/instruction_generator_test.go @@ -14,94 +14,50 @@ import ( // MARK: Add -type AddInstruction struct{} - -func (i *AddInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { - return gen.StepInfo{PossibleContinue: true}, core.ResultList{} +type Add struct { + gen.NonBranchingInstruction } -func (i *AddInstruction) Operator() string { - return "ADD" +func NewAdd() gen.InstructionDefinition { + return Add{} } -type AddInstructionDefinition struct{} - -func (AddInstructionDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - return gen.BaseInstruction(&AddInstruction{}), core.ResultList{} +func (Add) Operator(*gen.InstructionInfo) string { + return "add" } -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{} +func (Add) Validate(info *gen.InstructionInfo) core.ResultList { + return core.ResultList{} } // MARK: Ret -type RetInstruction struct{} +type Ret struct{} -func (i *RetInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { - return gen.StepInfo{PossibleReturn: true}, core.ResultList{} +func NewRet() gen.InstructionDefinition { + return Ret{} } -func (i *RetInstruction) Operator() string { - return "RET" +func (Ret) Operator(*gen.InstructionInfo) string { + return "ret" } - -type RetInstructionDefinition struct{} - -func (RetInstructionDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - return gen.BaseInstruction(&RetInstruction{}), core.ResultList{} +func (Ret) PossibleNextSteps(*gen.InstructionInfo) (gen.StepInfo, core.ResultList) { + return gen.StepInfo{PossibleReturn: true}, core.ResultList{} } -func (RetInstructionDefinition) InferTargetTypes( - ctx *gen.FunctionGenerationContext, - targets []*gen.ReferencedTypeInfo, - arguments []*gen.ReferencedTypeInfo, -) ([]gen.ReferencedTypeInfo, core.ResultList) { - return []gen.ReferencedTypeInfo{}, core.ResultList{} +func (Ret) Validate(*gen.InstructionInfo) core.ResultList { + return core.ResultList{} } // MARK: Jump -type JumpInstruction struct { - *gen.InstructionInfo +type Jump struct{} + +func NewJump() gen.InstructionDefinition { + return Jump{} } -func (i *JumpInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { +func (Jump) PossibleNextSteps(i *gen.InstructionInfo) (gen.StepInfo, core.ResultList) { return gen.StepInfo{ PossibleBranches: []*gen.LabelInfo{ i.Arguments[0].(*gen.LabelArgumentInfo).Label, @@ -109,34 +65,28 @@ func (i *JumpInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { }, core.ResultList{} } -func (i *JumpInstruction) Operator() string { - return "JMP" +func (Jump) Operator(*gen.InstructionInfo) string { + return "j" } -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{} +func (Jump) Validate(info *gen.InstructionInfo) core.ResultList { + return core.ResultList{} } // MARK: Jump Zero // JZ %condition .label -type JumpZeroInstruction struct { - *gen.InstructionInfo +type JumpZero struct{} + +func NewJumpZero() gen.InstructionDefinition { + return JumpZero{} } -func (i *JumpZeroInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { +func (JumpZero) Operator(*gen.InstructionInfo) string { + return "jz" +} + +func (JumpZero) PossibleNextSteps(i *gen.InstructionInfo) (gen.StepInfo, core.ResultList) { label := i.Arguments[1].(*gen.LabelArgumentInfo).Label return gen.StepInfo{ PossibleBranches: []*gen.LabelInfo{label}, @@ -144,24 +94,8 @@ func (i *JumpZeroInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList }, core.ResultList{} } -func (i *JumpZeroInstruction) Operator() string { - return "JZ" -} - -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{} +func (JumpZero) Validate(info *gen.InstructionInfo) core.ResultList { + return core.ResultList{} } // MARK: Instruction Map @@ -170,17 +104,23 @@ type InstructionMap map[string]gen.InstructionDefinition func (m *InstructionMap) GetInstructionDefinition( name string, - node parse.InstructionNode, + node *parse.InstructionNode, ) (gen.InstructionDefinition, core.ResultList) { - instDef, ok := (*m)[name] + inst, ok := (*m)[name] if !ok { + v := (*core.UnmanagedSourceView)(nil) + if node != nil { + v = &node.Operator + } + return nil, list.FromSingle(core.Result{{ Type: core.ErrorResult, Message: "undefined instruction", - Location: &node.Operator, + Location: v, }}) } - return instDef, core.ResultList{} + + return inst, core.ResultList{} } func PrepareTestForInstructionGeneration( @@ -222,7 +162,7 @@ func PrepareTestForInstructionGeneration( } func TestInstructionCreateTarget(t *testing.T) { - src := core.NewSourceView("$32 %c = ADD %a %b\n") + src := core.NewSourceView("$32 %c = add %a %b\n") node, ctx := PrepareTestForInstructionGeneration(src, t) generator := gen.NewInstructionGenerator() diff --git a/gen/instruction_info.go b/gen/instruction_info.go index 9704983..1575eb9 100644 --- a/gen/instruction_info.go +++ b/gen/instruction_info.go @@ -13,7 +13,7 @@ type InstructionInfo struct { Arguments []ArgumentInfo // The actual instruction information, which is ISA specific. - Instruction BaseInstruction + Definition InstructionDefinition // The location in which the instruction was defined in the source code. // Can be nil if the instruction was defined internally, for example, @@ -28,11 +28,15 @@ func NewEmptyInstructionInfo( BasicBlockInfo: nil, Targets: []*TargetInfo{}, Arguments: []ArgumentInfo{}, - Instruction: nil, + Definition: nil, Declaration: declaration, } } +func (i *InstructionInfo) Validate() core.ResultList { + return i.Definition.Validate(i) +} + // Appends the given register(s) as a target(s) of the instruction, // including updating the required instruction and register information fields. func (i *InstructionInfo) AppendTarget(targets ...*TargetInfo) { @@ -61,13 +65,13 @@ func (i *InstructionInfo) AppendArgument(arguments ...ArgumentInfo) { // This can be used to update the instruction, but keep the same arguments and // targets, for example, as an optimization to a more specific operation which // accepts the same arguments in certain cases. -func (i *InstructionInfo) SetBaseInstruction(instruction BaseInstruction) { - i.Instruction = instruction +func (i *InstructionInfo) SetInstruction(instruction InstructionDefinition) { + i.Definition = instruction } func (i *InstructionInfo) String() string { s := "" - operator := i.Instruction.Operator() + operator := i.Definition.Operator(i) if len(i.Targets) > 0 { for _, target := range i.Targets { diff --git a/gen/instruction_manager.go b/gen/instruction_manager.go index c4f5f6b..7935538 100644 --- a/gen/instruction_manager.go +++ b/gen/instruction_manager.go @@ -12,6 +12,6 @@ type InstructionManager interface { // generating nice error messages. GetInstructionDefinition( name string, - node parse.InstructionNode, + node *parse.InstructionNode, ) (InstructionDefinition, core.ResultList) } diff --git a/gen/instruction_map.go b/gen/instruction_map.go index 09195c1..a945442 100644 --- a/gen/instruction_map.go +++ b/gen/instruction_map.go @@ -16,7 +16,7 @@ type InstructionMap struct { func (m *InstructionMap) GetInstructionDefinition( name string, - node parse.InstructionNode, + node *parse.InstructionNode, ) (InstructionDefinition, core.ResultList) { if !m.caseSensitive { name = strings.ToLower(name) @@ -24,10 +24,15 @@ func (m *InstructionMap) GetInstructionDefinition( def, ok := m.Map.LookupString(name) if !ok { + v := (*core.UnmanagedSourceView)(nil) + if node != nil { + v = &node.Operator + } + return nil, list.FromSingle(core.Result{{ Type: core.ErrorResult, Message: "Undefined instruction", - Location: &node.Operator, + Location: v, }}) } diff --git a/gen/register_argument.go b/gen/register_argument.go index 91590dc..1891a55 100644 --- a/gen/register_argument.go +++ b/gen/register_argument.go @@ -13,8 +13,8 @@ type RegisterArgumentInfo struct { declaration *core.UnmanagedSourceView } -func NewRegisterArgument(register *RegisterInfo) RegisterArgumentInfo { - return RegisterArgumentInfo{ +func NewRegisterArgumentInfo(register *RegisterInfo) *RegisterArgumentInfo { + return &RegisterArgumentInfo{ Register: register, } } diff --git a/gen/translate.go b/gen/translate.go new file mode 100644 index 0000000..f081732 --- /dev/null +++ b/gen/translate.go @@ -0,0 +1,95 @@ +package gen + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" +) + +func ArgumentsToRegisters( + arguments []ArgumentInfo, +) []*RegisterInfo { + registers := []*RegisterInfo{} + + for _, arg := range arguments { + if regArg, ok := arg.(*RegisterArgumentInfo); ok { + registers = append(registers, regArg.Register) + } + } + + return registers +} + +func ArgumentsToLabels(arguments []ArgumentInfo) []*LabelInfo { + labels := []*LabelInfo{} + + for _, arg := range arguments { + if labelArg, ok := arg.(*LabelArgumentInfo); ok { + labels = append(labels, labelArg.Label) + } + } + + return labels +} + +func ArgumentToType(arg ArgumentInfo) (ReferencedTypeInfo, core.ResultList) { + switch typedArg := arg.(type) { + + case *RegisterArgumentInfo: + return typedArg.Register.Type, core.ResultList{} + + case *ImmediateInfo: + return typedArg.Type, core.ResultList{} + + default: + return ReferencedTypeInfo{}, list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Expected a typed argument", + Location: arg.Declaration(), + }, + }) + } +} + +func ArgumentsToTypes( + arguments []ArgumentInfo, +) ([]ReferencedTypeInfo, core.ResultList) { + results := core.ResultList{} + types := make([]ReferencedTypeInfo, len(arguments)) + + for i, arg := range arguments { + typ, curResults := ArgumentToType(arg) + results.Extend(&curResults) + types[i] = typ + } + + if !results.IsEmpty() { + return nil, results + } + + return types, core.ResultList{} +} + +func ArgumentToLabel(arg ArgumentInfo) (*LabelInfo, core.ResultList) { + if labelArg, ok := arg.(*LabelArgumentInfo); ok { + return labelArg.Label, core.ResultList{} + } + + return nil, list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Expected a label argument", + Location: arg.Declaration(), + }, + }) +} + +func TargetsToRegisters(targets []*TargetInfo) []*RegisterInfo { + registers := []*RegisterInfo{} + + for _, target := range targets { + registers = append(registers, target.Register) + } + + return registers +} diff --git a/gen/utils_test.go b/gen/utils_test.go index f67bd21..5488798 100644 --- a/gen/utils_test.go +++ b/gen/utils_test.go @@ -67,10 +67,10 @@ func (m *RegisterMap) GetAllRegisters() []*gen.RegisterInfo { var testInstructionSet = gen.InstructionManager( &InstructionMap{ - "ADD": &AddInstructionDefinition{}, - "JMP": &JumpInstructionDefinition{}, - "JZ": &JumpZeroInstructionDefinition{}, - "RET": &RetInstructionDefinition{}, + "add": NewAdd(), + "j": NewJump(), + "jz": NewJumpZero(), + "ret": NewRet(), }, ) diff --git a/go.mod b/go.mod index 3c67536..3552939 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module alon.kr/x/usm go 1.23.0 require ( - alon.kr/x/aarch64codegen v0.0.0-20250423211537-52c2f85d1367 + alon.kr/x/aarch64codegen v0.0.0-20250509160800-a2358754df5d alon.kr/x/faststringmap v0.0.0-20250503134653-20d6364c2c94 alon.kr/x/graph v0.0.0-20250319212444-dd67d0281ab7 alon.kr/x/list v0.0.0-20241203223347-3173d76828c0 diff --git a/go.sum b/go.sum index d07f0f9..f328d52 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -alon.kr/x/aarch64codegen v0.0.0-20250423211537-52c2f85d1367 h1:l+WdERkNIO4GVBHvcqs0PJSELEkr3A0CGbwSTL3/6pE= -alon.kr/x/aarch64codegen v0.0.0-20250423211537-52c2f85d1367/go.mod h1:WAdZYqOdp9KwoBjWamrMDAphahR5oXkPSICj3+tLIyQ= -alon.kr/x/faststringmap v0.0.0-20250425112818-35d6525968e3 h1:i2q4NANxEeSzGiWidH8lodMM/fZ3eCxfZdvA4WDKC9I= -alon.kr/x/faststringmap v0.0.0-20250425112818-35d6525968e3/go.mod h1:dPORoTvAFIehHRaI/lfJV3eT6PL5tY7fpAtxzz7rQVw= +alon.kr/x/aarch64codegen v0.0.0-20250509160800-a2358754df5d h1:zaesIgOOypUXQT8zaOj68unauQP83nD/dT/gBlDobLQ= +alon.kr/x/aarch64codegen v0.0.0-20250509160800-a2358754df5d/go.mod h1:WAdZYqOdp9KwoBjWamrMDAphahR5oXkPSICj3+tLIyQ= alon.kr/x/faststringmap v0.0.0-20250503134653-20d6364c2c94 h1:EdG6bQh5IT5MZ76MY85/GK4Ma7zoJ4zcFqd2fpxK6NI= alon.kr/x/faststringmap v0.0.0-20250503134653-20d6364c2c94/go.mod h1:dPORoTvAFIehHRaI/lfJV3eT6PL5tY7fpAtxzz7rQVw= alon.kr/x/graph v0.0.0-20250319212444-dd67d0281ab7 h1:0d/oLvPQWc1B38ZfWfJ5UQXeHx9JT8gmp3K0AOOG8aQ= diff --git a/justfile b/justfile index 8766795..71b6797 100644 --- a/justfile +++ b/justfile @@ -4,9 +4,10 @@ GO := `if command -v richgo >/dev/null 2>&1; then echo richgo; else echo go; fi` PY := "python3" COVERPROFILE := "coverage.out" +OUTPUT := "usm.out" build: - {{GO}} build + {{GO}} build -o {{OUTPUT}} test: {{GO}} test ./... diff --git a/opt/dead_code_elimination.go b/opt/dead_code_elimination.go index d6f3805..2dcd8d9 100644 --- a/opt/dead_code_elimination.go +++ b/opt/dead_code_elimination.go @@ -21,20 +21,20 @@ import ( // instruction since we assume SSA form). type DCESupportedInstruction interface { - gen.BaseInstruction + gen.InstructionDefinition // Returns true if the instruction is a critical instruction, which means // it can't be removed by the dead code elimination process, by definition. // // A critical instruction might be a function call, a branch, an instruction // with a side effect, etc. - IsCritical() bool + IsCritical(info *gen.InstructionInfo) bool // Returns the registers that the instruction defines, directly or indirectly. - Defines() []*gen.RegisterInfo + Defines(info *gen.InstructionInfo) []*gen.RegisterInfo // Returns the registers that the instruction uses, directly or indirectly. - Uses() []*gen.RegisterInfo + Uses(info *gen.InstructionInfo) []*gen.RegisterInfo } func newDCENotSupportedError(instruction *gen.InstructionInfo) core.ResultList { @@ -45,6 +45,42 @@ func newDCENotSupportedError(instruction *gen.InstructionInfo) core.ResultList { }}) } +type CriticalInstruction struct{} + +func (CriticalInstruction) IsCritical(*gen.InstructionInfo) bool { + return true +} + +type NonCriticalInstruction struct{} + +func (NonCriticalInstruction) IsCritical(*gen.InstructionInfo) bool { + return false +} + +type UsesArgumentsInstruction struct{} + +func (UsesArgumentsInstruction) Uses(info *gen.InstructionInfo) []*gen.RegisterInfo { + return gen.ArgumentsToRegisters(info.Arguments) +} + +type UsesNothingInstruction struct{} + +func (UsesNothingInstruction) Uses(*gen.InstructionInfo) []*gen.RegisterInfo { + return []*gen.RegisterInfo{} +} + +type DefinesTargetsInstruction struct{} + +func (DefinesTargetsInstruction) Defines(info *gen.InstructionInfo) []*gen.RegisterInfo { + return gen.TargetsToRegisters(info.Targets) +} + +type DefinesNothingInstruction struct{} + +func (DefinesNothingInstruction) Defines(*gen.InstructionInfo) []*gen.RegisterInfo { + return []*gen.RegisterInfo{} +} + func collectCriticalInstructions( function *gen.FunctionInfo, ) (stack.Stack[*gen.InstructionInfo], core.ResultList) { @@ -53,11 +89,11 @@ func collectCriticalInstructions( for block := function.EntryBlock; block != nil; block = block.NextBlock { for _, instruction := range block.Instructions { - dceInstruction, ok := instruction.Instruction.(DCESupportedInstruction) + dceInstruction, ok := instruction.Definition.(DCESupportedInstruction) if !ok { curResults := newDCENotSupportedError(instruction) results.Extend(&curResults) - } else if dceInstruction.IsCritical() { + } else if dceInstruction.IsCritical(instruction) { collected.Push(instruction) } } @@ -81,7 +117,7 @@ func collectUsefulInstructions( unprocessedInstructions.Pop() processedInstructions.Add(instruction) - dceInstruction, ok := instruction.Instruction.(DCESupportedInstruction) + dceInstruction, ok := instruction.Definition.(DCESupportedInstruction) if !ok { // Should not happen, since we try to convert all instructions to // DCESupportedInstruction in the collection of critical instructions @@ -91,7 +127,7 @@ func collectUsefulInstructions( usefulInstructions.Add(instruction) - for _, register := range dceInstruction.Uses() { + for _, register := range dceInstruction.Uses(instruction) { definitions := register.Definitions // In SSA form, a register can have at most one definition, and thus diff --git a/opt/opt_test.go b/opt/opt_test.go index 22a3459..840bdbb 100644 --- a/opt/opt_test.go +++ b/opt/opt_test.go @@ -10,7 +10,7 @@ import ( "alon.kr/x/usm/gen" "alon.kr/x/usm/lex" "alon.kr/x/usm/parse" - usm64managers "alon.kr/x/usm/usm64/managers" + usmmanagers "alon.kr/x/usm/usm/managers" "github.com/stretchr/testify/assert" ) @@ -72,7 +72,7 @@ func generateFileInfo(t *testing.T, source string) *gen.FileInfo { fileNode, result := parse.NewFileParser().Parse(&tknView) assert.Nil(t, result) - ctx := usm64managers.NewGenerationContext() + ctx := usmmanagers.NewGenerationContext() generator := gen.NewFileGenerator() info, results := generator.Generate(ctx, srcView.Ctx(), fileNode) assert.True(t, results.IsEmpty(), "Failed to generate file info") diff --git a/opt/ssa/construction_scheme.go b/opt/ssa/construction_scheme.go index 54bd182..3dbddfd 100644 --- a/opt/ssa/construction_scheme.go +++ b/opt/ssa/construction_scheme.go @@ -126,10 +126,11 @@ func (s *ReachingDefinitionsSet) popBlock() { s.registerDefinitionPushes.Pop() } -type PhiInstruction interface { - gen.BaseInstruction +type PhiInstructionDefinition interface { + gen.InstructionDefinition AddForwardingRegister( + *gen.InstructionInfo, *gen.BasicBlockInfo, *gen.RegisterInfo, ) core.ResultList @@ -146,7 +147,7 @@ type SsaConstructionScheme interface { NewPhiInstruction( *gen.BasicBlockInfo, *gen.RegisterInfo, - ) (PhiInstruction, core.ResultList) + ) (*gen.InstructionInfo, core.ResultList) // Creates a new unique register that is used as a renaming of the provided // register in the construction of the SSA form. diff --git a/opt/ssa/function_ssa_info.go b/opt/ssa/function_ssa_info.go index c612057..390d092 100644 --- a/opt/ssa/function_ssa_info.go +++ b/opt/ssa/function_ssa_info.go @@ -1,7 +1,10 @@ package ssa import ( + "fmt" + "alon.kr/x/graph" + "alon.kr/x/list" "alon.kr/x/set" "alon.kr/x/usm/core" "alon.kr/x/usm/gen" @@ -14,7 +17,7 @@ type forwardingRegisterDescriptor struct { type phiInstructionDescriptor struct { // The new phi instruction. - instruction PhiInstruction + *gen.InstructionInfo // The original base register that this phi instruction is a definition for. base *gen.RegisterInfo @@ -39,8 +42,29 @@ func (p *phiInstructionDescriptor) AddForwardingRegister( // will result in some weird behavior. func (p *phiInstructionDescriptor) CommitForwardingRegisters() core.ResultList { results := core.ResultList{} + phiDefinition, ok := p.Definition.(PhiInstructionDefinition) + + if !ok { + defName := p.Definition.Operator(p.InstructionInfo) + return list.FromSingle(core.Result{ + { + Type: core.InternalErrorResult, + Message: "Expected phi instruction definition", + Location: p.Declaration, + }, + { + Type: core.HintResult, + Message: fmt.Sprintf("Got \"%s\" instruction definition", defName), + }, + }) + } + for _, forward := range p.forwards { - curResults := p.instruction.AddForwardingRegister(forward.block, forward.renamed) + curResults := phiDefinition.AddForwardingRegister( + p.InstructionInfo, + forward.block, + forward.renamed, + ) results.Extend(&curResults) } @@ -185,8 +209,8 @@ func (i *FunctionSsaInfo) InsertPhiInstructions() core.ResultList { return results } descriptor := phiInstructionDescriptor{ - instruction: phi, - base: register, + InstructionInfo: phi, + base: register, } i.PhiInstructionsPerBlock[blockIndex] = append( i.PhiInstructionsPerBlock[blockIndex], diff --git a/opt/testdata/dead_code_elimination/test1.usm b/opt/testdata/dead_code_elimination/test1.usm index c707f2e..67a52bd 100644 --- a/opt/testdata/dead_code_elimination/test1.usm +++ b/opt/testdata/dead_code_elimination/test1.usm @@ -1,14 +1,16 @@ +func @use $64 %reg + func @input { .entry $64 %a = $64 #0 $64 %b = $64 #1 - PUT %a - TERM + call @use %a + ret } func @expected { .entry $64 %a = $64 #0 - PUT %a - TERM + call @use %a + ret } diff --git a/opt/testdata/dead_code_elimination/test2.usm b/opt/testdata/dead_code_elimination/test2.usm index 04198bf..9943332 100644 --- a/opt/testdata/dead_code_elimination/test2.usm +++ b/opt/testdata/dead_code_elimination/test2.usm @@ -1,17 +1,19 @@ +func @use $64 %reg + func @input { .entry $64 %a = $64 #0 - $64 %b = ADD %a $64 #1 - $64 %c = ADD %b $64 #1 - PUT %c - TERM + $64 %b = %a + $64 %c = %b + call @use %c + ret } func @expected { .entry $64 %a = $64 #0 - $64 %b = ADD %a $64 #1 - $64 %c = ADD %b $64 #1 - PUT %c - TERM + $64 %b = %a + $64 %c = %b + call @use %c + ret } diff --git a/opt/testdata/dead_code_elimination/test3.usm b/opt/testdata/dead_code_elimination/test3.usm new file mode 100644 index 0000000..427905e --- /dev/null +++ b/opt/testdata/dead_code_elimination/test3.usm @@ -0,0 +1,12 @@ +func @input { +.entry + $64 %a = $64 #0 + $64 %b = %a + $64 %c = %b + ret +} + +func @expected { +.entry + ret +} diff --git a/usm.go b/usm.go index 0c537ee..3ebdc7a 100644 --- a/usm.go +++ b/usm.go @@ -12,7 +12,8 @@ import ( "alon.kr/x/usm/lex" "alon.kr/x/usm/parse" "alon.kr/x/usm/transform" - usm64managers "alon.kr/x/usm/usm64/managers" + usmmanagers "alon.kr/x/usm/usm/managers" + usmssa "alon.kr/x/usm/usm/ssa" "github.com/spf13/cobra" ) @@ -21,7 +22,7 @@ var targets = transform.NewTargetCollection( Names: []string{"usm"}, Extensions: []string{".usm"}, Description: "A universal assembly language", - GenerationContext: usm64managers.NewGenerationContext(), + GenerationContext: usmmanagers.NewGenerationContext(), Transformations: *transform.NewTransformationCollection( &transform.Transformation{ Names: []string{"dead-code-elimination", "dce"}, @@ -29,9 +30,15 @@ var targets = transform.NewTargetCollection( TargetName: "usm", // Transform: , }, + &transform.Transformation{ + Names: []string{"static-single-assignment", "ssa"}, + Description: "An optimization pass that converts the assembly to static single assignment form", + TargetName: "usm", + Transform: usmssa.TransformFileToSsaForm, + }, &transform.Transformation{ Names: []string{"aarch64", "arm64"}, - Description: "Converts the universal assembly to matching machine specific aarch64 assembly", + Description: "Converts the universal assembly to matching machine specific AArch64 assembly", TargetName: "aarch64", // Transform: , }, @@ -41,7 +48,7 @@ var targets = transform.NewTargetCollection( &transform.Target{ Names: []string{"aarch64", "arm64"}, Extensions: []string{".aarch64.usm", ".arm64.usm"}, - Description: "Aarch64 (arm64v8) assembly language", + Description: "AArch64 (ARM64, ARMv8) assembly", GenerationContext: aarch64managers.NewGenerationContext(), Transformations: *transform.NewTransformationCollection( &transform.Transformation{ @@ -56,8 +63,7 @@ var targets = transform.NewTargetCollection( Names: []string{ "aarch64-macho-object", "aarch64-macho-obj", - "arm64-macho-object", - "arm64-macho-obj", + "aarch64-macho", }, Extensions: []string{".o"}, Description: "Mach-O object file containing aarch64 assembly", diff --git a/usm/isa/call.go b/usm/isa/call.go new file mode 100644 index 0000000..5243243 --- /dev/null +++ b/usm/isa/call.go @@ -0,0 +1,77 @@ +package usmisa + +import ( + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" + "alon.kr/x/usm/opt" +) + +// The call instruction +type Call struct { + // Control Flow + gen.NonBranchingInstruction + + // Dead Code Elimination + opt.CriticalInstruction + opt.UsesArgumentsInstruction + opt.DefinesTargetsInstruction +} + +func NewCall() gen.InstructionDefinition { + return Call{} +} + +func (Call) Operator(*gen.InstructionInfo) string { + return "call" +} + +func (Call) Validate(info *gen.InstructionInfo) core.ResultList { + results := gen.AssertAtLeastArguments(info, 1) + if !results.IsEmpty() { + return results + } + + funcArg, ok := info.Arguments[0].(*gen.GlobalArgumentInfo) + if !ok { + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Call instruction requires a global function argument as the first argument", + Location: funcArg.Declaration(), + }, + }) + } + + funcInfo := info.FileInfo.GetFunction(funcArg.Name()) + if funcInfo == nil { + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Call global function does not exist, or is not a function", + Location: funcArg.Declaration(), + }, + }) + } + + // TODO: this should also check types. + curResults := gen.AssertArgumentsExactly(info, len(funcInfo.Parameters)+1) + results.Extend(&curResults) + + curResults = gen.AssertTargetsExactly(info, len(funcInfo.Targets)) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + return core.ResultList{} +} + +func (Call) Defines(info *gen.InstructionInfo) []*gen.RegisterInfo { + return gen.TargetsToRegisters(info.Targets) +} + +func (Call) Uses(info *gen.InstructionInfo) []*gen.RegisterInfo { + return gen.ArgumentsToRegisters(info.Arguments) +} diff --git a/usm/isa/j.go b/usm/isa/j.go new file mode 100644 index 0000000..2b507f5 --- /dev/null +++ b/usm/isa/j.go @@ -0,0 +1,49 @@ +package usmisa + +import ( + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" + "alon.kr/x/usm/opt" +) + +type J struct { + // Control Flow + gen.BranchToLabelArguments + + // Dead Code Elimination + opt.CriticalInstruction + opt.UsesNothingInstruction + opt.DefinesNothingInstruction +} + +func NewJump() J { + return J{} +} + +func (J) Operator(*gen.InstructionInfo) string { + return "j" +} + +func (i J) Validate(info *gen.InstructionInfo) core.ResultList { + results := core.ResultList{} + + curResults := gen.AssertTargetsExactly(info, 0) + results.Extend(&curResults) + + curResults = gen.AssertArgumentsExactly(info, 1) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + labelArg := info.Arguments[0] + _, curResults = gen.ArgumentToLabel(labelArg) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + return core.ResultList{} +} diff --git a/usm/isa/jz.go b/usm/isa/jz.go new file mode 100644 index 0000000..bb4ecec --- /dev/null +++ b/usm/isa/jz.go @@ -0,0 +1,53 @@ +package usmisa + +import ( + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" + "alon.kr/x/usm/opt" +) + +type Jz struct { + // Control Flow + gen.BranchesToLabelArgumentsOrContinues + + // Dead Code Elimination + opt.CriticalInstruction + opt.UsesArgumentsInstruction + opt.DefinesNothingInstruction +} + +func NewJz() gen.InstructionDefinition { + return Jz{} +} + +func (Jz) Operator(*gen.InstructionInfo) string { + return "jz" +} + +func (Jz) Validate(info *gen.InstructionInfo) core.ResultList { + results := core.ResultList{} + + curResults := gen.AssertTargetsExactly(info, 0) + results.Extend(&curResults) + + curResults = gen.AssertArgumentsExactly(info, 2) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + valueArg := info.Arguments[0] + _, curResults = gen.ArgumentToType(valueArg) + results.Extend(&curResults) + + labelArg := info.Arguments[1] + _, curResults = gen.ArgumentToLabel(labelArg) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + return core.ResultList{} +} diff --git a/usm/isa/move.go b/usm/isa/move.go new file mode 100644 index 0000000..c66bb9b --- /dev/null +++ b/usm/isa/move.go @@ -0,0 +1,81 @@ +package usmisa + +import ( + "fmt" + + "alon.kr/x/list" + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" + "alon.kr/x/usm/opt" +) + +type Move struct { + // Control Flow + gen.NonBranchingInstruction + + // Dead Code Elimination + opt.NonCriticalInstruction + opt.UsesArgumentsInstruction + opt.DefinesTargetsInstruction +} + +func NewMove() gen.InstructionDefinition { + return Move{} +} + +func (Move) Operator(*gen.InstructionInfo) string { + return "" +} + +func (Move) Validate(info *gen.InstructionInfo) core.ResultList { + results := core.ResultList{} + + curResults := gen.AssertTargetsExactly(info, 1) + results.Extend(&curResults) + + curResults = gen.AssertArgumentsExactly(info, 1) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + argumentType, curResults := gen.ArgumentToType(info.Arguments[0]) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + targetType := info.Targets[0].Register.Type + + if !targetType.Equal(argumentType) { + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "Target and argument types do not match", + Location: info.Declaration, + }, + { + Type: core.HintResult, + Message: fmt.Sprintf("Target type is \"%s\"", targetType), + Location: targetType.Declaration, + }, + { + Type: core.HintResult, + Message: fmt.Sprintf("Argument type is \"%s\"", argumentType), + Location: argumentType.Declaration, + }, + }) + } + + return core.ResultList{} +} + +func (Move) Defines(info *gen.InstructionInfo) []*gen.RegisterInfo { + return gen.TargetsToRegisters(info.Targets) +} + +func (Move) Uses(info *gen.InstructionInfo) []*gen.RegisterInfo { + return gen.ArgumentsToRegisters(info.Arguments) +} diff --git a/usm/isa/phi.go b/usm/isa/phi.go new file mode 100644 index 0000000..7434e53 --- /dev/null +++ b/usm/isa/phi.go @@ -0,0 +1,117 @@ +package usmisa + +import ( + "alon.kr/x/list" + "alon.kr/x/set" + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" + "alon.kr/x/usm/opt" +) + +type Phi struct { + // Control Flow + gen.NonBranchingInstruction + + // Dead Code Elimination + opt.NonCriticalInstruction + opt.UsesArgumentsInstruction + opt.DefinesTargetsInstruction +} + +func NewPhi() gen.InstructionDefinition { + return Phi{} +} + +func (Phi) Operator(*gen.InstructionInfo) string { + return "phi" +} + +func (Phi) validateEvenArguments(info *gen.InstructionInfo) core.ResultList { + if len(info.Arguments)%2 != 0 { + return list.FromSingle(core.Result{ + { + Type: core.ErrorResult, + Message: "A \"phi\" instruction must have an even number of arguments.", + Location: info.Declaration, + }, + { + Type: core.HintResult, + Message: "Each pair of arguments should consist of a label and a value.", + }, + }) + } + + return core.ResultList{} +} + +func (i Phi) Validate(info *gen.InstructionInfo) core.ResultList { + results := core.ResultList{} + + curResults := gen.AssertTargetsExactly(info, 1) + results.Extend(&curResults) + + curResults = i.validateEvenArguments(info) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + targetType := info.Targets[0].Register.Type + incomingEdges := set.FromSlice(info.BasicBlockInfo.BackwardEdges) + + for i := 0; i < len(info.Arguments); i += 2 { + labelArg := info.Arguments[i] + + label, curResults := gen.ArgumentToLabel(labelArg) + results.Extend(&curResults) + + valueArg := info.Arguments[i+1] + argType, curResults := gen.ArgumentToType(valueArg) + results.Extend(&curResults) + + if !results.IsEmpty() { + continue + } + + block := label.BasicBlock + if incomingEdges.Contains(block) { + incomingEdges.Remove(block) + } else { + results.Append(core.Result{ + { + Type: core.ErrorResult, + Message: "Label does not match any incoming edge, or appears multiple times.", + Location: labelArg.Declaration(), + }, + }) + } + + if !argType.Equal(targetType) { + results.Append(core.Result{ + { + Type: core.ErrorResult, + Message: "Argument type does not match target type.", + Location: valueArg.Declaration(), + }, + }) + } + } + + // Notice that we do not check if incomingEdges is empty. + // This is because it it VALID for some incoming edges to not have a + // specified value. In that case the value will be undefined. + + return results +} + +func (Phi) AddForwardingRegister( + instruction *gen.InstructionInfo, + block *gen.BasicBlockInfo, + register *gen.RegisterInfo, +) core.ResultList { + labelArg := gen.NewLabelArgumentInfo(block.Label) + regArg := gen.NewRegisterArgumentInfo(register) + instruction.AppendArgument(labelArg, regArg) + return core.ResultList{} +} diff --git a/usm/isa/ret.go b/usm/isa/ret.go new file mode 100644 index 0000000..7d217a8 --- /dev/null +++ b/usm/isa/ret.go @@ -0,0 +1,85 @@ +package usmisa + +import ( + "fmt" + + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" + "alon.kr/x/usm/opt" +) + +type Ret struct { + // Control Flow + gen.ReturningInstruction + + // Dead Code Elimination + opt.CriticalInstruction + opt.UsesArgumentsInstruction + opt.DefinesNothingInstruction +} + +func NewRet() gen.InstructionDefinition { + return Ret{} +} + +func (Ret) Operator(*gen.InstructionInfo) string { + return "ret" +} + +func (Ret) Validate(info *gen.InstructionInfo) core.ResultList { + results := core.ResultList{} + + curResults := gen.AssertTargetsExactly(info, 0) + results.Extend(&curResults) + + functionTargetTypes := info.FunctionInfo.Targets + retTypes, curResults := gen.ArgumentsToTypes(info.Arguments) + results.Extend(&curResults) + + if !results.IsEmpty() { + return results + } + + if len(functionTargetTypes) != len(retTypes) { + results.Append(core.Result{ + { + Type: core.ErrorResult, + Message: fmt.Sprintf( + "Number of arguments (%d) and the number of targets of the function (%d) do not match", + len(retTypes), + len(functionTargetTypes), + ), + Location: info.Declaration, + }, + }) + } + + if !results.IsEmpty() { + return results + } + + for i, funcTargetType := range functionTargetTypes { + retArgumentType := retTypes[i] + + if !funcTargetType.Equal(retArgumentType) { + results.Append(core.Result{ + { + Type: core.ErrorResult, + Message: "Return type does not match the type of the function target", + Location: retArgumentType.Declaration, + }, + { + Type: core.HintResult, + Message: "Matches this function target type", + Location: funcTargetType.Declaration, + }, + }) + } + } + + if !results.IsEmpty() { + return results + } + + return core.ResultList{} +} diff --git a/usm64/managers/context.go b/usm/managers/context.go similarity index 95% rename from usm64/managers/context.go rename to usm/managers/context.go index e19fc58..26aa18f 100644 --- a/usm64/managers/context.go +++ b/usm/managers/context.go @@ -1,4 +1,4 @@ -package usm64managers +package usmmanagers import ( "math/big" diff --git a/usm/managers/instructions.go b/usm/managers/instructions.go new file mode 100644 index 0000000..782dd9b --- /dev/null +++ b/usm/managers/instructions.go @@ -0,0 +1,27 @@ +package usmmanagers + +import ( + "alon.kr/x/faststringmap" + "alon.kr/x/usm/gen" + usmisa "alon.kr/x/usm/usm/isa" +) + +func NewInstructionManager() gen.InstructionManager { + return gen.NewInstructionMap( + []faststringmap.MapEntry[gen.InstructionDefinition]{ + {Key: "", Value: usmisa.NewMove()}, + + // Functions + {Key: "ret", Value: usmisa.NewRet()}, + {Key: "call", Value: usmisa.NewCall()}, + + // Control Flow + {Key: "j", Value: usmisa.NewJump()}, + {Key: "jz", Value: usmisa.NewJz()}, + + // Static Single Assignment (SSA) + {Key: "phi", Value: usmisa.NewPhi()}, + }, + false, + ) +} diff --git a/usm64/managers/registers.go b/usm/managers/registers.go similarity index 97% rename from usm64/managers/registers.go rename to usm/managers/registers.go index 920d4f0..96a6fba 100644 --- a/usm64/managers/registers.go +++ b/usm/managers/registers.go @@ -1,4 +1,4 @@ -package usm64managers +package usmmanagers import ( "alon.kr/x/usm/core" diff --git a/usm/managers/types.go b/usm/managers/types.go new file mode 100644 index 0000000..0737764 --- /dev/null +++ b/usm/managers/types.go @@ -0,0 +1,58 @@ +package usmmanagers + +import ( + "math/big" + + "alon.kr/x/usm/core" + "alon.kr/x/usm/gen" +) + +// In the USM isa, there are infinitly many "base types", of the form "$", +// each representing an integer of size n bits. +// We create those types lazily, and store them in the map. +type TypeMap map[string]*gen.NamedTypeInfo + +// Check if the provided type name is of the form "$" where n is a +// non-negative integer. +func (TypeMap) toBaseTypeSize(name string) *big.Int { + // notice that there are multiple strings that can be mapped to the + // same size, e.g. "$64" and "$064". + + size, ok := new(big.Int).SetString(name[1:], 10) + if !ok || size.Sign() < 0 { + return nil + } + + return size +} + +func (TypeMap) baseTypeSizeToCannonicalName(size *big.Int) string { + return "$" + size.String() +} + +func (m *TypeMap) GetType(name string) *gen.NamedTypeInfo { + size := m.toBaseTypeSize(name) + if size != nil { + // is a base type, convert name to cannonical form + name = m.baseTypeSizeToCannonicalName(size) + if _, exists := (*m)[name]; !exists { + // if does not exist, create it! + (*m)[name] = gen.NewNamedTypeInfo(name, size, nil) + } + } + + return (*m)[name] +} + +func (m *TypeMap) NewType(*gen.NamedTypeInfo) core.Result { + return core.Result{ + { + Type: core.ErrorResult, + Message: "Type declaration not supported yet in usm isa", + }, + } +} + +func NewTypeManager(*gen.GenerationContext) gen.TypeManager { + return &TypeMap{} +} diff --git a/usm64/ssa/ssa_construction.go b/usm/ssa/ssa_construction.go similarity index 78% rename from usm64/ssa/ssa_construction.go rename to usm/ssa/ssa_construction.go index 4eee9bc..40b2eaa 100644 --- a/usm64/ssa/ssa_construction.go +++ b/usm/ssa/ssa_construction.go @@ -1,4 +1,4 @@ -package usm64ssa +package usmssa import ( "fmt" @@ -6,7 +6,8 @@ import ( "alon.kr/x/usm/core" "alon.kr/x/usm/gen" "alon.kr/x/usm/opt/ssa" - usm64isa "alon.kr/x/usm/usm64/isa" + "alon.kr/x/usm/transform" + usmisa "alon.kr/x/usm/usm/isa" ) type ConstructionScheme struct { @@ -14,24 +15,24 @@ type ConstructionScheme struct { } func NewConstructionScheme() ssa.SsaConstructionScheme { - scheme := &ConstructionScheme{ + return &ConstructionScheme{ RenamesPerRegister: make(map[*gen.RegisterInfo]uint), } - - return ssa.SsaConstructionScheme(scheme) } func (s *ConstructionScheme) NewPhiInstruction( block *gen.BasicBlockInfo, register *gen.RegisterInfo, -) (ssa.PhiInstruction, core.ResultList) { +) (*gen.InstructionInfo, core.ResultList) { info := gen.NewEmptyInstructionInfo(nil) + info.SetInstruction(usmisa.NewPhi()) + target := gen.NewTargetInfo(register) info.AppendTarget(&target) - instruction, results := usm64isa.NewPhiInstruction(info) - info.SetBaseInstruction(instruction) + block.PrependInstruction(info) - return ssa.PhiInstruction(instruction), results + + return info, core.ResultList{} } func (s *ConstructionScheme) NewRenamedRegister( @@ -104,7 +105,7 @@ func (s *ConstructionScheme) RenameBasicBlock( return core.ResultList{} } -func ConvertToSsaForm(function *gen.FunctionInfo) core.ResultList { +func FunctionToSsaForm(function *gen.FunctionInfo) core.ResultList { constructionScheme := NewConstructionScheme() ssaInfo := ssa.NewFunctionSsaInfo(function, constructionScheme) results := ssaInfo.InsertPhiInstructions() @@ -113,9 +114,29 @@ func ConvertToSsaForm(function *gen.FunctionInfo) core.ResultList { } results = ssaInfo.RenameRegisters() - if results.IsEmpty() { + if !results.IsEmpty() { return results } return core.ResultList{} } + +func FileToSsaForm(file *gen.FileInfo) core.ResultList { + results := core.ResultList{} + + for _, function := range file.Functions { + if function.IsDefined() { + curResults := FunctionToSsaForm(function) + results.Extend(&curResults) + } + } + + return results +} + +func TransformFileToSsaForm( + data *transform.TargetData, +) (*transform.TargetData, core.ResultList) { + results := FileToSsaForm(data.Code) + return data, results +} diff --git a/usm64/core/emualtor.go b/usm64/core/emualtor.go deleted file mode 100644 index 3965f39..0000000 --- a/usm64/core/emualtor.go +++ /dev/null @@ -1,49 +0,0 @@ -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 deleted file mode 100644 index 7b0fe99..0000000 --- a/usm64/core/emulation.go +++ /dev/null @@ -1,99 +0,0 @@ -package usm64core - -import ( - "alon.kr/x/list" - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" -) - -// MARK: Error - -type EmulationError interface{} - -// MARK: Context - -type EmulationContext struct { - NextBlockInfo *gen.BasicBlockInfo - NextInstructionIndexInBlock uint - ShouldTerminate bool - - Registers map[string]uint64 -} - -func (ctx *EmulationContext) JumpToLabel(label *gen.LabelInfo) core.ResultList { - ctx.NextBlockInfo = label.BasicBlock - ctx.NextInstructionIndexInBlock = 0 - return core.ResultList{} -} - -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 (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) core.ResultList -} diff --git a/usm64/core/instruction.go b/usm64/core/instruction.go deleted file mode 100644 index 2ca2a74..0000000 --- a/usm64/core/instruction.go +++ /dev/null @@ -1,8 +0,0 @@ -package usm64core - -// MARK: Instruction - -type Instruction interface { - // Emulate (interpret) the instruction, provided the context. - Emulateable -} diff --git a/usm64/core/label.go b/usm64/core/label.go deleted file mode 100644 index 80e25f7..0000000 --- a/usm64/core/label.go +++ /dev/null @@ -1,27 +0,0 @@ -package usm64core - -import ( - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" -) - -type Label struct { - Name string - InstructionIndex uint64 - declaration *core.UnmanagedSourceView -} - -func NewLabel(arg gen.LabelArgumentInfo) (Label, core.ResultList) { - return Label{ - Name: arg.Label.Name, - declaration: arg.Declaration(), - }, core.ResultList{} -} - -func (l Label) String(*EmulationContext) string { - return l.Name -} - -func (l Label) Declaration() *core.UnmanagedSourceView { - return l.declaration -} diff --git a/usm64/core/register.go b/usm64/core/register.go deleted file mode 100644 index 6bb4a93..0000000 --- a/usm64/core/register.go +++ /dev/null @@ -1,32 +0,0 @@ -package usm64core - -import ( - "fmt" - - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" -) - -type Register struct { - Name string - declaration *core.UnmanagedSourceView -} - -func NewRegister(arg *gen.RegisterArgumentInfo) (Register, core.ResultList) { - return Register{ - Name: arg.Register.Name, - declaration: arg.Declaration(), - }, core.ResultList{} -} - -func (r Register) Value(ctx *EmulationContext) uint64 { - return ctx.Registers[r.Name] -} - -func (r Register) String(ctx *EmulationContext) string { - return fmt.Sprintf("%s (#%d)", r.Name, r.Value(ctx)) -} - -func (r Register) Declaration() *core.UnmanagedSourceView { - return r.declaration -} diff --git a/usm64/isa/add.go b/usm64/isa/add.go deleted file mode 100644 index 40288c3..0000000 --- a/usm64/isa/add.go +++ /dev/null @@ -1,60 +0,0 @@ -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 { - nonBranchingInstruction -} - -func (i *AddInstruction) Emulate( - ctx *usm64core.EmulationContext, -) core.ResultList { - results := core.ResultList{} - - first, firstResults := ctx.ArgumentToValue(i.Arguments[0]) - results.Extend(&firstResults) - - 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 (i *AddInstruction) Operator() string { - return "ADD" -} - -func NewAddInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - results := core.ResultList{} - - if !results.IsEmpty() { - return nil, results - } - - return gen.BaseInstruction(&AddInstruction{ - nonBranchingInstruction: newNonBranchingInstruction(info), - }), core.ResultList{} -} - -func NewAddInstructionDefinition() gen.InstructionDefinition { - return &FixedInstructionDefinition{ - Targets: 1, - Arguments: 2, - Creator: NewAddInstruction, - } -} diff --git a/usm64/isa/base_instruction.go b/usm64/isa/base_instruction.go deleted file mode 100644 index 95a2fb5..0000000 --- a/usm64/isa/base_instruction.go +++ /dev/null @@ -1,37 +0,0 @@ -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} -} - -func (i *baseInstruction) Uses() []*gen.RegisterInfo { - arguments := i.InstructionInfo.Arguments - registers := []*gen.RegisterInfo{} - for _, argument := range arguments { - if argument, ok := argument.(*gen.RegisterArgumentInfo); ok { - registers = append(registers, argument.Register) - } - } - - return registers -} - -func (i *baseInstruction) Defines() []*gen.RegisterInfo { - targets := i.InstructionInfo.Targets - registers := []*gen.RegisterInfo{} - for _, target := range targets { - registers = append(registers, target.Register) - } - - return registers -} diff --git a/usm64/isa/critical_instruction.go b/usm64/isa/critical_instruction.go deleted file mode 100644 index 2886d77..0000000 --- a/usm64/isa/critical_instruction.go +++ /dev/null @@ -1,16 +0,0 @@ -package usm64isa - -// A critical instruction is an instruction that cannot be removed by the -// dead code elimination process. This means that it has side effects, or -// is a function call, or is a branch, etc. -type CriticalInstruction struct{} - -func (i *CriticalInstruction) IsCritical() bool { - return true -} - -type NotCriticalInstruction struct{} - -func (i *NotCriticalInstruction) IsCritical() bool { - return false -} diff --git a/usm64/isa/fixed.go b/usm64/isa/fixed.go deleted file mode 100644 index 1f21416..0000000 --- a/usm64/isa/fixed.go +++ /dev/null @@ -1,94 +0,0 @@ -package usm64isa - -import ( - "fmt" - - "alon.kr/x/list" - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" -) - -type FixedInstructionDefinition struct { - Targets core.UsmUint - Arguments core.UsmUint - Creator func( - info *gen.InstructionInfo, - ) (gen.BaseInstruction, core.ResultList) -} - -func (d *FixedInstructionDefinition) InferTargetTypes( - ctx *gen.FunctionGenerationContext, - targets []*gen.ReferencedTypeInfo, - arguments []*gen.ReferencedTypeInfo, -) ([]gen.ReferencedTypeInfo, core.ResultList) { - base := ctx.Types.GetType("$64") - if base == nil { - return nil, list.FromSingle(core.Result{ - { - Type: core.InternalErrorResult, - Message: "The $64 type is not defined", - }, - }) - } - - inferredTargets := make([]gen.ReferencedTypeInfo, len(targets)) - for i := core.UsmUint(0); i < d.Targets; i++ { - inferredTargets[i] = gen.ReferencedTypeInfo{Base: base} - } - - return inferredTargets, core.ResultList{} -} - -func (d *FixedInstructionDefinition) assertTargetAmount( - targets []*gen.TargetInfo, -) core.ResultList { - // TODO: possible overflow? - if core.UsmUint(len(targets)) != d.Targets { - return list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: fmt.Sprintf("Exactly %d target(s) are allowed", d.Targets), - }, - }) - } - return core.ResultList{} -} - -func (d *FixedInstructionDefinition) assertArgumentAmount( - arguments []gen.ArgumentInfo, -) core.ResultList { - // TODO: possible overflow? - if core.UsmUint(len(arguments)) != d.Arguments { - return list.FromSingle(core.Result{ - { - Type: core.ErrorResult, - Message: fmt.Sprintf("Exactly %d argument(s) are allowed", d.Arguments), - }, - }) - } - return core.ResultList{} -} - -func (d *FixedInstructionDefinition) assertInputLengths( - targetInfos []*gen.TargetInfo, - argumentInfos []gen.ArgumentInfo, -) (results core.ResultList) { - targetResults := d.assertTargetAmount(targetInfos) - results.Extend(&targetResults) - - argumentResults := d.assertArgumentAmount(argumentInfos) - results.Extend(&argumentResults) - - return results -} - -func (d *FixedInstructionDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - results := d.assertInputLengths(info.Targets, info.Arguments) - if !results.IsEmpty() { - return nil, results - } - - return d.Creator(info) -} diff --git a/usm64/isa/jump.go b/usm64/isa/jump.go deleted file mode 100644 index 940ffc5..0000000 --- a/usm64/isa/jump.go +++ /dev/null @@ -1,42 +0,0 @@ -package usm64isa - -import ( - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" -) - -type JumpInstruction struct { - baseInstruction - CriticalInstruction -} - -func (i *JumpInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { - label := i.InstructionInfo.Arguments[0].(*gen.LabelArgumentInfo).Label - return gen.StepInfo{PossibleBranches: []*gen.LabelInfo{label}}, core.ResultList{} -} - -func (i *JumpInstruction) Emulate( - ctx *usm64core.EmulationContext, -) core.ResultList { - labelArgument := i.InstructionInfo.Arguments[0].(*gen.LabelArgumentInfo) - return ctx.JumpToLabel(labelArgument.Label) -} - -func (i *JumpInstruction) Operator() string { - return "J" -} - -func NewJumpInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - return gen.BaseInstruction(&JumpInstruction{baseInstruction: baseInstruction{info}}), core.ResultList{} -} - -func NewJumpInstructionDefinition() gen.InstructionDefinition { - return &FixedInstructionDefinition{ - Targets: 0, - Arguments: 1, - Creator: NewJumpInstruction, - } -} diff --git a/usm64/isa/jump_not_zero.go b/usm64/isa/jump_not_zero.go deleted file mode 100644 index b8277c1..0000000 --- a/usm64/isa/jump_not_zero.go +++ /dev/null @@ -1,57 +0,0 @@ -package usm64isa - -import ( - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" -) - -type JumpNotZeroInstruction struct { - baseInstruction - CriticalInstruction -} - -func (i *JumpNotZeroInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { - label := i.InstructionInfo.Arguments[1].(*gen.LabelArgumentInfo).Label - return gen.StepInfo{ - PossibleBranches: []*gen.LabelInfo{label}, - PossibleContinue: true, - }, core.ResultList{} -} - -func (i *JumpNotZeroInstruction) Emulate( - ctx *usm64core.EmulationContext, -) core.ResultList { - value, results := ctx.ArgumentToValue(i.InstructionInfo.Arguments[0]) - if !results.IsEmpty() { - return results - } - - if value != uint64(0) { - labelArgument := i.InstructionInfo.Arguments[1].(*gen.LabelArgumentInfo) - return ctx.JumpToLabel(labelArgument.Label) - - } else { - return ctx.ContinueToNextInstruction() - } -} - -func (i *JumpNotZeroInstruction) Operator() string { - return "JNZ" -} - -func NewJumpNotZeroInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - return gen.BaseInstruction(&JumpNotZeroInstruction{ - baseInstruction: newBaseInstruction(info), - }), core.ResultList{} -} - -func NewJumpNotZeroInstructionDefinition() gen.InstructionDefinition { - return &FixedInstructionDefinition{ - Targets: 0, - Arguments: 2, - Creator: NewJumpNotZeroInstruction, - } -} diff --git a/usm64/isa/jump_zero.go b/usm64/isa/jump_zero.go deleted file mode 100644 index 6d4f05b..0000000 --- a/usm64/isa/jump_zero.go +++ /dev/null @@ -1,57 +0,0 @@ -package usm64isa - -import ( - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" -) - -type JumpZeroInstruction struct { - baseInstruction - CriticalInstruction -} - -func (i *JumpZeroInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { - label := i.InstructionInfo.Arguments[1].(*gen.LabelArgumentInfo).Label - return gen.StepInfo{ - PossibleBranches: []*gen.LabelInfo{label}, - PossibleContinue: true, - }, core.ResultList{} -} - -func (i *JumpZeroInstruction) Emulate( - ctx *usm64core.EmulationContext, -) core.ResultList { - value, results := ctx.ArgumentToValue(i.InstructionInfo.Arguments[0]) - if !results.IsEmpty() { - return results - } - - if value == uint64(0) { - labelArgument := i.InstructionInfo.Arguments[1].(*gen.LabelArgumentInfo) - return ctx.JumpToLabel(labelArgument.Label) - - } else { - return ctx.ContinueToNextInstruction() - } -} - -func (i *JumpZeroInstruction) Operator() string { - return "JZ" -} - -func NewJumpZeroInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - return gen.BaseInstruction(&JumpZeroInstruction{ - baseInstruction: newBaseInstruction(info), - }), core.ResultList{} -} - -func NewJumpZeroInstructionDefinition() gen.InstructionDefinition { - return &FixedInstructionDefinition{ - Targets: 0, - Arguments: 2, - Creator: NewJumpZeroInstruction, - } -} diff --git a/usm64/isa/move.go b/usm64/isa/move.go deleted file mode 100644 index 6d5b04b..0000000 --- a/usm64/isa/move.go +++ /dev/null @@ -1,46 +0,0 @@ -package usm64isa - -import ( - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" -) - -// Currently, supporting only a single argument + target. -// TODO: support n arguments and target pairs. -type MoveInstruction struct { - nonBranchingInstruction -} - -func (i *MoveInstruction) Emulate( - ctx *usm64core.EmulationContext, -) core.ResultList { - value, results := ctx.ArgumentToValue(i.Arguments[0]) - if !results.IsEmpty() { - return results - } - - targetName := i.Targets[0].Register.Name - ctx.Registers[targetName] = value - return ctx.ContinueToNextInstruction() -} - -func (i *MoveInstruction) Operator() string { - return "" -} - -func NewMoveInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - return gen.BaseInstruction(&MoveInstruction{ - nonBranchingInstruction: newNonBranchingInstruction(info), - }), core.ResultList{} -} - -func NewMoveInstructionDefinition() gen.InstructionDefinition { - return &FixedInstructionDefinition{ - Targets: 1, - Arguments: 1, - Creator: NewMoveInstruction, - } -} diff --git a/usm64/isa/non_branching_instruction.go b/usm64/isa/non_branching_instruction.go deleted file mode 100644 index 2af7019..0000000 --- a/usm64/isa/non_branching_instruction.go +++ /dev/null @@ -1,19 +0,0 @@ -package usm64isa - -import ( - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" -) - -type nonBranchingInstruction struct { - baseInstruction - NotCriticalInstruction -} - -func newNonBranchingInstruction(info *gen.InstructionInfo) nonBranchingInstruction { - return nonBranchingInstruction{baseInstruction: newBaseInstruction(info)} -} - -func (i *nonBranchingInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { - return gen.StepInfo{PossibleContinue: true}, core.ResultList{} -} diff --git a/usm64/isa/phi.go b/usm64/isa/phi.go deleted file mode 100644 index 5b14bde..0000000 --- a/usm64/isa/phi.go +++ /dev/null @@ -1,59 +0,0 @@ -package usm64isa - -import ( - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" -) - -type PhiInstruction struct { - nonBranchingInstruction -} - -func (i *PhiInstruction) Emulate( - ctx *usm64core.EmulationContext, -) core.ResultList { - labelArgument := i.InstructionInfo.Arguments[0].(*gen.LabelArgumentInfo) - return ctx.JumpToLabel(labelArgument.Label) -} - -func NewPhiInstruction( - info *gen.InstructionInfo, -) (*PhiInstruction, core.ResultList) { - return &PhiInstruction{newNonBranchingInstruction(info)}, core.ResultList{} -} - -func (i *PhiInstruction) Operator() string { - return "PHI" -} - -func (i *PhiInstruction) AddForwardingRegister( - block *gen.BasicBlockInfo, - register *gen.RegisterInfo, -) core.ResultList { - labelArgument := gen.NewLabelArgumentInfo(block.Label) - registerArgument := gen.NewRegisterArgument(register) - i.AppendArgument(labelArgument, ®isterArgument) - return core.ResultList{} -} - -type PhiInstructionDefinition struct{} - -func (d *PhiInstructionDefinition) BuildInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - // TODO: argument validation - return NewPhiInstruction(info) -} - -func (d *PhiInstructionDefinition) InferTargetTypes( - ctx *gen.FunctionGenerationContext, - targets []*gen.ReferencedTypeInfo, - arguments []*gen.ReferencedTypeInfo, -) ([]gen.ReferencedTypeInfo, core.ResultList) { - return []gen.ReferencedTypeInfo{}, core.ResultList{} -} - -func NewPhiInstructionDefinition() gen.InstructionDefinition { - return &PhiInstructionDefinition{} -} diff --git a/usm64/isa/put.go b/usm64/isa/put.go deleted file mode 100644 index 1d5de53..0000000 --- a/usm64/isa/put.go +++ /dev/null @@ -1,46 +0,0 @@ -package usm64isa - -import ( - "fmt" - - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" -) - -type PutInstruction struct { - nonBranchingInstruction - CriticalInstruction -} - -func (i *PutInstruction) Emulate( - ctx *usm64core.EmulationContext, -) core.ResultList { - value, results := ctx.ArgumentToValue(i.Arguments[0]) - if !results.IsEmpty() { - return results - } - - fmt.Println(value) - return ctx.ContinueToNextInstruction() -} - -func (i *PutInstruction) Operator() string { - return "PUT" -} - -func NewPutInstruction( - info *gen.InstructionInfo, -) (gen.BaseInstruction, core.ResultList) { - return gen.BaseInstruction(&PutInstruction{ - nonBranchingInstruction: newNonBranchingInstruction(info), - }), core.ResultList{} -} - -func NewPutInstructionDefinition() gen.InstructionDefinition { - return &FixedInstructionDefinition{ - Targets: 0, - Arguments: 1, - Creator: NewPutInstruction, - } -} diff --git a/usm64/isa/terminate.go b/usm64/isa/terminate.go deleted file mode 100644 index 4c66448..0000000 --- a/usm64/isa/terminate.go +++ /dev/null @@ -1,46 +0,0 @@ -package usm64isa - -import ( - "fmt" - - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" - usm64core "alon.kr/x/usm/usm64/core" -) - -type TerminateInstruction struct { - baseInstruction - CriticalInstruction -} - -func (i *TerminateInstruction) PossibleNextSteps() (gen.StepInfo, core.ResultList) { - return gen.StepInfo{PossibleReturn: true}, core.ResultList{} -} - -func (i *TerminateInstruction) Operator() string { - return "TERM" -} - -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{ - baseInstruction: newBaseInstruction(info), - }), core.ResultList{} -} - -func NewTerminateInstructionDefinition() gen.InstructionDefinition { - return &FixedInstructionDefinition{ - Targets: 0, - Arguments: 0, - Creator: NewTerminateInstruction, - } -} diff --git a/usm64/managers/instructions.go b/usm64/managers/instructions.go deleted file mode 100644 index 4267ab1..0000000 --- a/usm64/managers/instructions.go +++ /dev/null @@ -1,33 +0,0 @@ -package usm64managers - -import ( - "alon.kr/x/faststringmap" - "alon.kr/x/usm/gen" - usm64isa "alon.kr/x/usm/usm64/isa" -) - -func NewInstructionManager() gen.InstructionManager { - return gen.NewInstructionMap( - []faststringmap.MapEntry[gen.InstructionDefinition]{ - // mov - {Key: "", Value: usm64isa.NewMoveInstructionDefinition()}, - {Key: "mov", Value: usm64isa.NewMoveInstructionDefinition()}, - - // arithmetic - {Key: "add", Value: usm64isa.NewAddInstructionDefinition()}, - - // control flow - {Key: "j", Value: usm64isa.NewJumpInstructionDefinition()}, - {Key: "jz", Value: usm64isa.NewJumpZeroInstructionDefinition()}, - {Key: "jnz", Value: usm64isa.NewJumpNotZeroInstructionDefinition()}, - - // SSA - {Key: "phi", Value: usm64isa.NewPhiInstructionDefinition()}, - - // debug - {Key: "put", Value: usm64isa.NewPutInstructionDefinition()}, - {Key: "term", Value: usm64isa.NewTerminateInstructionDefinition()}, - }, - false, - ) -} diff --git a/usm64/managers/types.go b/usm64/managers/types.go deleted file mode 100644 index 8476ca0..0000000 --- a/usm64/managers/types.go +++ /dev/null @@ -1,35 +0,0 @@ -package usm64managers - -import ( - "math/big" - - "alon.kr/x/usm/core" - "alon.kr/x/usm/gen" -) - -type TypeMap struct { - BaseType *gen.NamedTypeInfo -} - -func (m *TypeMap) GetType(name string) *gen.NamedTypeInfo { - if name == m.BaseType.Name { - return m.BaseType - } else { - return nil - } -} - -func (m *TypeMap) NewType(*gen.NamedTypeInfo) core.Result { - return core.Result{ - { - Type: core.ErrorResult, - Message: "Type declaration not supported in usm64", - }, - } -} - -func NewTypeManager(*gen.GenerationContext) gen.TypeManager { - return &TypeMap{ - BaseType: gen.NewNamedTypeInfo("$64", big.NewInt(64), nil), - } -}