読者です 読者をやめる 読者になる 読者になる

javascriptでMIPSもどき

javascript

(本当はそんな余裕無いのですが)暇だったのでjavascriptでCPUっぽい何かを作って見ました.5時間くらいで実装.

<html>
  <head>
    <meta http-equiv="Contect-Type" content="text/html;charset=utf-8">
    <script type="text/javascript" charset="utf-8" src="javascript.js"></script>
    <title></title>
  </head>
  <body>
    <textarea id="command" cols="30" rows="30"></textarea><br>
    <input type="BUTTON" value="start" onclick="start(10)"><br>
    <input type="BUTTON" value="stop" onclick="stop()"><br>
    assembler
    <div id="command_list"></div>
    register
    <div id="reg_list"></div>
  </body>
</html>
var timer;
var command_list = new Array();
var PC = 0;
var R0 = 0; var R1 = 0; var R2 = 0; var R3 = 0;
var R4 = 0; var R5 = 0; var R6 = 0; var R7 = 0;


function $(e) { return document.getElementById(e); }

function alu(cmd, A, B) {
  var N = 'N';
  if(!(A.match(/^(R[0-7])|N$/) && B.match(/^(R[0-7])|N|(-?\d+)$/))) return 0;
  var A = eval(A);
  var B = eval(B);
  switch(cmd) {
    case "NOP": return 0; break;
    case "ADD": return A + B; break;
    case "SUB": return A - B; break;
    case "MUL": return A * B; break;
    case "DIV": return A / B; break;
    case "MOD": return A % B; break;
    case "AND": return A & B; break;
    case "OR":  return A | B; break;
    case "NOT": return ~A; break;
    case "SLL": return  A << B; break;
    case "SRL": return  A >> B; break;
    default: break;
  }
}

function write_reg(reg, data)
{
  if(reg.match(/^R[0-7]$/) && (((typeof data) == "number") || data.match(/^(R[0-7])|(-?\d+)$/))) {
    eval(reg + " = " + data);
  }
}

function next_PC(label, type, comp)
{
  var next_PC = PC + 1;
  if(label != undefined && label.match(/^[A-Z][A-Z0-9]*$/)) {
    for(var i=0; i<command_list.length; i++) {
      if(command_list[i]["label"] == label + ":") {
        if(type == "BEQ" && comp == 0) next_PC = i;
        if(type == "BNE" && comp != 0) next_PC = i;
        if(type == "BGT" && comp > 0) next_PC = i;
        if(type == "BLT" && comp < 0) next_PC = i;
        if(type == "BGE" && comp >= 0) next_PC = i;
        if(type == "BLE" && comp <= 0) next_PC = i;
        if(type == undefined) next_PC = i;
      }
    }
  }
  PC = next_PC;
}

function show_reg(id)
{
  var text = 'PC:' + PC + '<br>';
  text += 'R0:' + R0 + '<br>';
  text += 'R1:' + R1 + '<br>';
  text += 'R2:' + R2 + '<br>';
  text += 'R3:' + R3 + '<br>';
  text += 'R4:' + R4 + '<br>';
  text += 'R5:' + R5 + '<br>';
  text += 'R6:' + R6 + '<br>';
  text += 'R7:' + R7;
  $(id).innerHTML = text;
}

function show_commands(id)
{
  var text = '';
  for(var i=0; i<command_list.length; i++) {
    if(i == PC) text += '<span style="color:red;">';
    text += i + " ";
    if(command_list[i]["label"] != "") text += command_list[i]["label"] + " ";
    text += command_list[i]["command"] + " " + command_list[i]["params"];
    if(i == PC) text += '</span>';
    text += "<br>";
  }
  $(id).innerHTML = text;
}

function parse_commands()
{
  PC = 0;
  R0 = 0; R1 = 0; R2 = 0; R3 = 0;
  R4 = 0; R5 = 0; R6 = 0; R7 = 0;
  command_list = new Array();
  var commands = $("command").value.replace("\n", "").split(";");
  if(commands[commands.length -1] == '') commands.pop();

  for(var i=0; i<commands.length; i++) {
    if(commands[i].match(/^\s*([A-Z][A-Z0-9]*:)?\s*([A-Z]+)/)) {
      command_list.push({
        "label":RegExp.$1,
        "command":RegExp.$2,
        "params":RegExp.rightContext.replace(/\s/g, '').split(',')
      });
    }
  }

  show_commands("command_list");
  show_reg("reg_list");
}

function next_step()
{
  if(PC == command_list.length) { stop(); return; }
  var cmd = command_list[PC];
  var p = cmd["params"];
  switch(cmd["command"]) {
  case "MOVE": write_reg(p[0], p[1]); next_PC(); break;
  case "ADD": write_reg(p[0], alu("ADD", p[1], p[2])); next_PC(); break;
  case "SUB": write_reg(p[0], alu("SUB", p[1], p[2])); next_PC(); break;
  case "MUL": write_reg(p[0], alu("MUL", p[1], p[2])); next_PC(); break;
  case "DIV": write_reg(p[0], alu("DIV", p[1], p[2])); next_PC(); break;
  case "MOD": write_reg(p[0], alu("MOD", p[1], p[2])); next_PC(); break;
  case "AND": write_reg(p[0], alu("AND", p[1], p[2])); next_PC(); break;
  case "OR":  write_reg(p[0], alu("OR" , p[1], p[2])); next_PC(); break;
  case "NOT": write_reg(p[0], alu("NOT", p[1], 'N' )); next_PC(); break;
  case "SLL": write_reg(p[0], alu("SLL", p[1], p[2])); next_PC(); break;
  case "SRL": write_reg(p[0], alu("SRL", p[1], p[2])); next_PC(); break;
  case "J": next_PC(p[0]); break;
  case "BEQ": next_PC(p[2], "BEQ", alu("SUB", p[0], p[1])); break;
  case "BNE": next_PC(p[2], "BNE", alu("SUB", p[0], p[1])); break;
  case "BGT": next_PC(p[2], "BGT", alu("SUB", p[0], p[1])); break;
  case "BLT": next_PC(p[2], "BLT", alu("SUB", p[0], p[1])); break;
  case "BGE": next_PC(p[2], "BGE", alu("SUB", p[0], p[1])); break;
  case "BLE": next_PC(p[2], "BLE", alu("SUB", p[0], p[1])); break;
  default: break;
  }

  show_commands("command_list");
  show_reg("reg_list");
}

function start(t)
{
  parse_commands();
  timer = setInterval("next_step()", t);
}

function stop()
{
  clearInterval(timer);
}

命令はnext_step()あたりを見てもらえばそれっぽいのが書いてあると思います(やっつけで書いたので一部動かないかも).

  • MOVE rs, rt, rd(im);
  • ADD rs, rt, rd(im);
  • SUB rs, rt, rd(im);
  • MUL rs, rt, rd(im);
  • DIV rs, rt, rd(im);
  • MOD rs, rt, rd(im);
  • AND rs, rt, rd(im);
  • OR rs, rt, rd(im);
  • NOT rs, rt;
  • SLL rs, rt, rd(im);
  • SRL rs, rt, rd(im);
  • J C;
  • BEQ rs, rt, C;
  • BNE rs, rt, C;
  • BGT rs, rt, C;
  • BLT rs, rt, C;
  • BGE rs, rt, C;
  • BLE rs, rt, C;

だけ書いてみました.レジスタはR0~R7まで.

階乗くらいなら割と簡単に書けますね.

一部問題としては,javascriptなので,intとfloatの区別が無いってあたりでしょうか.DIV 1, 3みたいな事をすると0でなく0.333...となるはずです.
ただし,即値は正規表現で正負の整数値しか読み込まないようになってるのでちょっとアレです.
それと,0で割るときの処理も書いてないので0で割るのはやめましょう.