forked from ubclaunchpad/arithmetic-machine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.c
181 lines (171 loc) · 6.37 KB
/
main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
============================================================================
Name : Arithmetic Machine
Author : UBC Launchpad
Version : 1.0
Copyright :
Description : Stack-based virtual machine that performs simple arithmetic.
============================================================================
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define STACK_SIZE 256 // maximum number of values on the stack
#define PUSH(vm, v) (vm->stack[++vm->sp] = v) // push value onto stack
#define POP(vm) (vm->stack[vm->sp--]) // get value from top of stack
#define NCODE(vm) (vm->code[vm->pc++]) // get next bytecode
/* all opcodes that will be implemented. */
enum opcodes {
HALT = 0x00, // halt
/* in case you can't get DCONST working, you can still try things using these: */
DCONST_M1 = 0x0A, // push -1.0 onto stack
DCONST_0 = 0x0B, // push 0.0 onto stack
DCONST_1 = 0x0C, // push 1.0 onto stack
DCONST_2 = 0x0D, // push 2.0 onto stack
/* make sure you consider endianness */
DCONST = 0x0F, // push next 8 bytes onto stack as double constant
/* arithmetic operations */
ADD = 0x60, // add two doubles
SUB = 0x61, // subtract two doubles
MUL = 0x62, // multiply two doubles
DIV = 0x64, // divide two doubles
NEG = 0x70, // negate an double (e.g. if -1.0 is on the stack, NEG will turn it to 1.0 on the stack)
NOP = 0xF0, // do nothing
/* in a real VM, we'd use a function call for print, rather than having a special opcode */
PRINT = 0xF2, // pops and prints top of stack
/* store and read from registers */
ST1 = 0xF4, // pops top of stack and stores it in r1
LD1 = 0xF5, // load global from r1
ST2 = 0xF6, // pops top of stack and stores it in r2
LD2 = 0xF7, // load global from r2
};
/* defining our virtual machine */
typedef struct {
double r1, r2; // registers
char* code; // pointer to bytecode
double* stack; // stack
int pc; // program counter
int sp; // stack pointer
} VM;
VM* newVM(char* code /* pointer to bytecode */ ) {
VM* vm = malloc(sizeof(VM));
vm->code = code;
vm->pc = 0;
vm->sp = -1;
vm->r1 = vm->r2 = 0; // init registers to 0
vm->stack = malloc(sizeof(double) * STACK_SIZE);
return vm;
}
void delVM(VM* vm){
free(vm->stack);
free(vm);
}
int run(VM* vm){
for (;;) {
unsigned char opcode = NCODE(vm); // store next bytecode in `opcode'
double a, b, v; // use these to store intermediate values when implementing opcodes below
switch (opcode) { // decode
case HALT: return EXIT_SUCCESS; // exit successfully
case NOP: break; // pass
case DCONST_M1: // push -1.0 onto stack
// TODO: implement this.
PUSH(vm, -1.0)
break;
case DCONST_0: // push 0.0 onto stack
// TODO: implement this.
PUSH(vm, 0.0)
break;
case DCONST_1: // push 1.0 onto stack
// TODO: implement this.
PUSH(vm, 1.0)
break;
case DCONST_2: // push 2.0 onto stack
// TODO: implement this.
PUSH(vm, 2.0)
break;
case DCONST: // reads next 8 bytes of opcode as a double, and stores it on the stack.
// TODO: implement this.
// HINT: use memcpy to read next 8 bytes of code as a double. make sure you consider endianness.
int *value;
memcpy((void *) value, (void *) opcode, sizeof(char) * 8);
a = (double)(*value);
PUSH(vm, a);
break;
case ADD: // add two doubles from top of stack and push result back onto stack
b = POP(vm);
a = POP(vm);
PUSH(vm, a + b);
break;
case MUL: // multiply two doubles from top of stack and push result back onto stack
// TODO: implement this.
b = POP(vm);
a = POP(vm);
PUSH(vm, a * b);
break;
case SUB: // subtract two doubles from top of stack and push result back onto stack
// TODO: implement this.
b = POP(vm);
a = POP(vm);
PUSH(vm, a - b);
break;
case DIV: // divide two doubles from top of stack and push result back onto stack
//TODO: implement this.
// HINT: make sure to deal with the division by zero case.
b = POP(vm);
if (b == 0){
fprintf(stderr, "Division by zero is impermissible!");
return EXIT_FAILURE;
}
a = POP(vm);
PUSH(vm, a / b);
break;
case NEG: // negates top of stack
//TODO: implement this.
a = POP(vm);
PUSH(vm, -a);
break;
case LD1: // put value from r1 on top of stack
// TODO: implement this.
PUSH(vm, vm->r1);
break;
case ST1: // store top of stack in r1
// TODO: implement this.
vm->r1 = POP(vm);
break;
case LD2: // put value from r2 on top of stack
// TODO: implement this.
// HINT: should be similar to LD1.
PUSH(vm, vm->r2);
break;
case ST2: // store top of stack in r2
// TODO: implement this.
// HINT: should be similar to ST1.
vm->r2 = POP(vm, vm->r2);
break;
case PRINT: // print top of stack, (and discard value afterwards.)
// TODO: implement this.
a = POP(vm);
printf("%.2f\n", a);
break;
default:
printf("InvalidOpcodeError: %x\n", opcode); // terminate program at unknown opcode and show error.
return EXIT_FAILURE;
}
}
return EXIT_FAILURE;
}
int main(void) {
/* in a real VM, we'd read bytecode from a file, but for brevity's sake we'll read
from an array.
*/
// simple example: push 2 onto stack, push 1 onto stack, subtract them, print the result, exit (should print 1.0)
char bytecode[] = { DCONST_2,
DCONST_1,
SUB,
PRINT,
HALT };
VM* vm = newVM(bytecode /* program to execute */ );
int exit_status = run(vm);
delVM(vm);
return exit_status;
};