public ScheduleSlot(ProgramInfo.InstructionNode inst, int iter) { m_inst = inst; m_iter = iter; }
public void SimpleSchedule(ProgramInfo prog_info, CheatSheet cheat_sheet) { if (prog_info.m_original_program.Count == 0) { m_log = "\n\nno instructions found to schedule. Try writing some code"; m_as_string = ""; return; } m_log = "\n\nscheduling logic:"; int mii = prog_info.MinII; m_schedule = new ScheduleSlot[mii, 2]; m_instruction_to_schedule_map = Enumerable.Repeat<InstructionToScheduleMapNode>(null, prog_info.m_original_program.Count).ToList(); // schedule the final jump int final_slot = prog_info.m_original_program.Count - 1; ProgramInfo.InstructionNode final_jump_inst = prog_info.m_original_program[final_slot]; if (!final_jump_inst.IsBranch(cheat_sheet)) { Debug.Fail("last instruction must be a jump to the start of the loop"); m_log += "\nlast instruction must be a jump to the start of the loop"; return; } m_instruction_to_schedule_map[final_slot] = new InstructionToScheduleMapNode(mii - 1, 0); m_schedule[mii - 1, 1] = new ScheduleSlot(final_jump_inst, 0); // schedule other instruction for (int i = 0; i < prog_info.m_original_program.Count - 1; ++i) { // step 1: find all dependencies and figure out when the last one finishes // each operand can have at most one dependency ProgramInfo.InstructionNode inst = prog_info.m_original_program[i]; // don't schedule instructions that are unused or don't need to be included if (inst.m_include_in_schedule == false) { continue; } int num_regs = inst.m_reg_info.Count; if (inst.IsBranch(cheat_sheet)) { Debug.Fail("no jump instructions allowed in the middle of a loop"); return; } // absolute cycle time, not which slot in the schedule. iteration num * mii + schedule slot num int cycle_to_schedule = 0; for (int d = 0; d < num_regs; ++d) { ProgramInfo.DepPair dep_pair = inst.m_reg_info[d].m_dependency; if (dep_pair != null) { int dependency_index = dep_pair.m_inst_num; InstructionToScheduleMapNode dependency_location = m_instruction_to_schedule_map[dependency_index]; if (dependency_location.m_schedule_slot >= 0) { ProgramInfo.InstructionNode dependency = prog_info.m_original_program[dependency_index]; cycle_to_schedule = Math.Max ( cycle_to_schedule, dependency_location.m_iteration * mii + dependency_location.m_schedule_slot + dependency.m_latency ); } } } // step 2: see if that slot is free. If not, keep going until free slot found // todo: what happens if we can't find a valid schedule? We need to back up and reschedule int schedule_slot = cycle_to_schedule % mii; int schedule_iter = cycle_to_schedule / mii; int pipeline_index = inst.m_pipeline == Program.Pipe.EVEN ? 0 : 1; while (m_schedule[schedule_slot, pipeline_index] != null) { ++cycle_to_schedule; schedule_slot = cycle_to_schedule % mii; schedule_iter = cycle_to_schedule / mii; } // step 3: schedule the instruction m_instruction_to_schedule_map[i] = new InstructionToScheduleMapNode(schedule_slot, schedule_iter); m_schedule[schedule_slot, pipeline_index] = new ScheduleSlot(inst, schedule_iter); m_total_iterations = Math.Max(m_total_iterations, schedule_iter + 1); m_log += string.Format("\nscheduling {0} in slot {1} iteration {2}", inst.m_opcode, schedule_slot, schedule_iter); } // so now that we have our schedule and know what belongs to what iteration, do register allocation. // this is necessary mainly to avoid anti-dependencies from rearranging instructions. // TODO: because we are now inserting copies to deal with antidependencies, register allocation can // fail if there is no valid place to put the copy. We need to be in a loop and try to schedule again // if something fails AllocateRegisters(prog_info, mii); GeneratePrologue(cheat_sheet); m_as_string = BuildScheduleString(cheat_sheet); }
void ScheduleFromLines(string[] fake_program_lines) { TextView text_view = OriginalCode2; TextBuffer buffer = text_view.Buffer; buffer.RemoveAllTags(buffer.GetIterAtOffset(0), buffer.GetIterAtOffset(buffer.Text.Length)); PipelinedSchedule.Buffer.Text = ""; pipelining_tool_test.ProgramInfo original_prog_info = new pipelining_tool_test.ProgramInfo(fake_program_lines, m_cheat_sheet); pipelining_tool_test.ProgramInfo prog_info = original_prog_info.Copy(); this.Log.Buffer.Text += prog_info.m_log; if (prog_info.m_program_status.IsError()) { this.Log.Buffer.Text += "\n\n" + prog_info.m_program_status.m_error_msg; // get the char num start and end of the error line int error_linenum = 0; int error_line_start_char = 0; int error_line_end_char = 0; while (error_linenum != prog_info.m_program_status.m_error_line) { error_line_start_char += fake_program_lines[error_linenum].Length + 1; ++error_linenum; } error_line_end_char = error_line_start_char + fake_program_lines[error_linenum].Length; TextIter iter1 = buffer.GetIterAtOffset(error_line_start_char); TextIter iter2 = buffer.GetIterAtOffset(error_line_end_char); buffer.ApplyTag(m_tag_error, iter1, iter2); //You can find a longer example in the GtkDemo application that comes with //GTK#. The relevant source file is also here: //http://code.google.com/p/slickr-dotnet/source/browse/trunk/SlickrGtk/DemoTextView.cs?r=2 //And there is a good tutorial for TextView here (it's written for the C-version of GTK+): //http://www.bravegnu.org/gtktext/ } else { // schedule pipelining_tool_test.Scheduler schedueler = new pipelining_tool_test.Scheduler(); schedueler.SimpleSchedule(prog_info, m_cheat_sheet); // display it PipelinedSchedule.Buffer.Text = string.Copy(schedueler.ToString()); this.Log.Buffer.Text += schedueler.m_log; } }
SchedulerResult AllocateRegisters(ProgramInfo prog_info, int min_ii) { // despite my grand schemes, I doing the simplest thing possible for now and going sequential const int first_reg = 16; const int last_reg = 128; const int num_regs = last_reg - first_reg; // there is a bit of redundency here. allowed_reg_list starts with the allowed range of registers // and then removes the ones that can't be touched. do_not_touch_list starts empty and adds the // list of registers we can't touch. List<int> allowed_reg_list = Enumerable.Range(first_reg, num_regs).ToList(); List<int> do_not_touch_list = new List<int>(); // for tracking unused slots List<int>[] unused_slots = new List<int>[2]; unused_slots[0] = new List<int>(); unused_slots[1] = new List<int>(); int reg_cycle = 0; // each line in the schedule for (int slot = 0; slot < m_schedule.GetLength(0); ++slot) { // odd and even pipeline for (int pipe = 0; pipe < 2; ++pipe) { // its a nop or lnop if (m_schedule[slot, pipe] == null) { unused_slots[pipe].Add(slot); continue; } if (m_schedule[slot, pipe].m_inst.m_reg_info[0].m_dependents.Count == 0 && m_schedule[slot, pipe].m_inst.m_reg_info[0].IsOutputOperand() == true) { if (do_not_touch_list.Contains(m_schedule[slot, pipe].m_inst.m_reg_info[0].m_allocated_reg) == false) { do_not_touch_list.Add(m_schedule[slot, pipe].m_inst.m_reg_info[0].m_allocated_reg); } } for (int reg = 1; reg < m_schedule[slot, pipe].m_inst.m_reg_info.Count; ++reg) { if (m_schedule[slot, pipe].m_inst.m_reg_info[reg].m_allocated_reg != -1 && m_schedule[slot, pipe].m_inst.m_reg_info[reg].m_dependency == null && m_schedule[slot, pipe].m_inst.m_reg_info[reg].IsOutputOperand() == false) { if (do_not_touch_list.Contains(m_schedule[slot, pipe].m_inst.m_reg_info[reg].m_allocated_reg) == false) { do_not_touch_list.Add(m_schedule[slot, pipe].m_inst.m_reg_info[reg].m_allocated_reg); } } } } } allowed_reg_list.RemoveAll(u => do_not_touch_list.Contains(u)); // do each iteration at a time for (int iter = 0; iter < m_total_iterations; ++iter) { // each line in the schedule for (int slot = 0; slot < m_schedule.GetLength(0); ++slot) { // odd and even pipeline for (int pipe = 0; pipe < 2; ++pipe) { // wrong iteration, or its a nop if (m_schedule[slot,pipe] == null || m_schedule[slot,pipe].m_iter != iter) { continue; } // new register choosing logic! This is gonna suck. Here goes // The registers that are problems are the ones that have dependents in later iterations and then only // if the schedule slot of the dependent instruction is > that of the instruction being scheduled. This // is most esaily explained with an example // a) add r32, r31, r31 // b) add r33, r32, 4 // c) add r34, r32, 4 // which then gets scheduled as: // 0) add r33, r32, 4 (instruction b, iteration n + 1) ; uses r32 from previous iteration // 1) add r32, r31, r31 (instruction a, iteration n) ; writes a new value in r32 // 2) add r34, r32, 4 (instruction c, iteration n + 1) ; error: _should_ use r32 from previous iteration // So lets get started. Look at each instruction's output operand, and consider each dependent. If every // dependent is in the same iteration, or in the next iteration and scheduled before the current instruction, // then we don't need any register copying and we can just use regular register assignment logic. Otherwise, // things start to suck. We have to first decide where to put the register copy, and that includes deciding // whether to use odd or even pipeline, and then fix up the instruction dependents and dependencies to include // the new copy instruction. ProgramInfo.InstructionNode current_inst = m_schedule[slot, pipe].m_inst; if (current_inst.m_reg_info.Count > 0 && current_inst.m_reg_info[0].IsOutputOperand()) { // starting out optimistic... bool register_needs_copying = false; foreach (ProgramInfo.DepPair dependent in current_inst.m_reg_info[0].m_dependents) { InstructionToScheduleMapNode schedule_slot_info = m_instruction_to_schedule_map[dependent.m_inst_num]; if (schedule_slot_info.m_iteration > iter && schedule_slot_info.m_schedule_slot > slot) { register_needs_copying = true; break; } } // common case... I hope if (!register_needs_copying) { if (!m_schedule[slot, pipe].m_inst.m_reg_info[0].m_is_locked) { int next_reg = allowed_reg_list[reg_cycle]; m_schedule[slot, pipe].m_inst.m_reg_info[0].m_allocated_reg = next_reg; for (int r = 0; r < m_schedule[slot, pipe].m_inst.m_reg_info[0].m_dependents.Count; ++r) { int dependent_inst = m_schedule[slot, pipe].m_inst.m_reg_info[0].m_dependents[r].m_inst_num; int dependent_inst_reg = m_schedule[slot, pipe].m_inst.m_reg_info[0].m_dependents[r].m_oper_num; prog_info.m_original_program[dependent_inst].m_reg_info[dependent_inst_reg].m_allocated_reg = next_reg; } reg_cycle = (reg_cycle + 1) % num_regs; } } // welcome to antidependency hell, bitches! else { // the register move has to be inserted somewhere between here and the the end of the schedule, // either in the odd pipeline or the even pipeline. So how to decide? I guess first we check // how many nop/lnops are in the schedule AFTER the current instruction (plus latency). if (slot + current_inst.m_latency >= min_ii) { return SchedulerResult.SCHEDULE_FAILED_REG_ALLOC_LIVE_TOO_LONG; } // now look for nops and lnops after this instruction. Maybe we can use // an odd copy if we don't need the results for more than 4 cycles, and // an even copy otherwise! int odd_copy_slot = -1; int even_copy_slot = -1; foreach (int s in unused_slots[1]) { // earliest spot is current instruction + latency // must be this iteration, no wrapping around // result of the copy must be done before the value is needed // note: we are only copying regs if the dependent is > current slot, and this final check reflects this. // if that logic changes, this has to change as well if (s >= slot + current_inst.m_latency && (s + 4 <= min_ii || (s + 4) % min_ii <= slot)) { odd_copy_slot = s; break; } } foreach (int s in unused_slots[0]) { if (s >= slot + current_inst.m_latency && (s + 2 <= min_ii || (s + 2) % min_ii <= slot)) { even_copy_slot = s; break; } } // nowhere valid to schedule this copy if (even_copy_slot == -1 && odd_copy_slot == -1) { return SchedulerResult.SCHEDULE_FAILED_REG_ALLOC_LIVE_TOO_LONG; } int copy_slot = odd_copy_slot != -1? odd_copy_slot : even_copy_slot; Program.Pipe copy_pipe = odd_copy_slot != -1? Program.Pipe.ODD : Program.Pipe.EVEN; // if we get in here, obviously this is not a locked register int next_reg = allowed_reg_list[reg_cycle]; reg_cycle = (reg_cycle + 1) % num_regs; m_schedule[slot, pipe].m_inst.m_reg_info[0].m_allocated_reg = next_reg; // prefer odd when possible, and eventually replace with a better metric ProgramInfo.InstructionNode regcopy = CreateRegcopyInst(copy_pipe); regcopy.m_reg_info[1].m_allocated_reg = next_reg; next_reg = allowed_reg_list[reg_cycle]; reg_cycle = (reg_cycle + 1) % num_regs; regcopy.m_reg_info[0].m_allocated_reg = next_reg; // we also have to store it in the original program at the end, just because thats where indices index into // note, these copies have to be removed (and the original program restored) on scheduling failure and retry prog_info.m_original_program.Add(regcopy); int new_copy_inst_index = prog_info.m_original_program.Count - 1; // adjust dependents and dependencies. Also needs to be restored on rescheduling for (int d = 0; d < current_inst.m_reg_info[0].m_dependents.Count; ++d) { ProgramInfo.DepPair dependent = current_inst.m_reg_info[0].m_dependents[d]; regcopy.m_reg_info[0].m_dependents.Add(new ProgramInfo.DepPair(dependent.m_inst_num, dependent.m_oper_num)); prog_info.m_original_program[dependent.m_inst_num].m_reg_info[dependent.m_oper_num].m_dependency = new ProgramInfo.DepPair(new_copy_inst_index, 0); prog_info.m_original_program[dependent.m_inst_num].m_reg_info[dependent.m_oper_num].m_allocated_reg = next_reg; } current_inst.m_reg_info[0].m_dependents.Clear(); current_inst.m_reg_info[0].m_dependents.Add(new ProgramInfo.DepPair(new_copy_inst_index, 1)); regcopy.m_reg_info[1].m_dependency = new ProgramInfo.DepPair(current_inst.m_original_inst_num, 0); m_instruction_to_schedule_map.Add(new InstructionToScheduleMapNode(copy_slot, iter)); // add it to the schedule m_schedule[copy_slot, (int)copy_pipe] = new ScheduleSlot(regcopy, iter); // now schedule the copy, fixing up all dependencies and dependents and other things m_log += string.Format("\nregister move required, inserting slot {0} pipe {1}", copy_slot, copy_pipe); } } } } } return SchedulerResult.SCHEDULE_SUCCEEDED; }
public ProgramInfo Copy() { ProgramInfo copy = new ProgramInfo(); copy.m_instruction_cnt[0] = m_instruction_cnt[0]; copy.m_instruction_cnt[1] = m_instruction_cnt[1]; copy.m_log = string.Copy(m_log); copy.m_program_status = m_program_status.Copy(); foreach (KeyValuePair<string, int> p in m_label_table) { copy.m_label_table.Add(p.Key, p.Value); } foreach (InstructionNode i in m_original_program) { copy.m_original_program.Add(i.Copy()); } return copy; }