Skip to content

Commit

Permalink
Merge branch 'main' into breakpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
arnav-ag committed Apr 14, 2024
2 parents 90864cf + 8c16e61 commit 624b807
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 12 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,19 @@ of programs.

Ooga supports integers, floats, booleans, strings and custom Struct expressions.

WORK IN PROGRESS: Slices and Channels
Ooga also supports buffered, unbuffered channels as well as arrays and slices which are dynamically resizable arrays.

Ooga slices follow Golang's slices behaviour in which a re-allocation of memory creates a new slice.

Consider the following ooga snippet code.
```go
var x []int = make([]int, 5, 5); // allocates a slice of len 5 and capacity 5
var y []int = append(x, 5); // this causes a reallocation of a new slice of capacity 10
// x and y now point to slices of capacity 5 and 10 respectively
// y has len = 6
```

This is a test case in our suite.

### Test Suite

Expand Down
9 changes: 9 additions & 0 deletions src/parser/ooga.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ Keyword
/ DefaultToken
/ SelectToken
/ BreakpointToken
/ AppendToken


Type
Expand Down Expand Up @@ -277,6 +278,7 @@ CaseToken = "case" !IdentifierPart
DefaultToken = "default" !IdentifierPart
SelectToken = "select" !IdentifierPart
BreakpointToken = "breakpoint" !IdentifierPart
AppendToken = "append" !IdentifierPart


SingleLineComment
Expand Down Expand Up @@ -393,6 +395,13 @@ CallExpression
args: args || []
};
}
/ AppendToken __ "(" __ name:Identifier __ "," __ args:ArgumentList __ ")" {
return {
tag: "AppendExpression",
name: name,
args: args
}
}

MakeArguments
= "," __ args:ArgumentList {
Expand Down
105 changes: 103 additions & 2 deletions src/tests/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ v := Vertex{1};
w := Vertex{2};
go func() {
for i := 0; i < 1000; i++ { // iterate for 1000 times to prove that waitgroup works as intended
for i := 0; i < 100; i++ { // iterate for 100 times to prove that waitgroup works as intended
}
v.Add(w);
}()
Expand Down Expand Up @@ -883,7 +883,8 @@ w := Vertex{2};
wg := WaitGroup{1};
go func() {
for i := 0; i < 1000; i++ { // iterate for 1000 times to prove that waitgroup works as intended
for i := 0; i < 100; i++ { // iterate for 100 times to prove that waitgroup works as intended
}
v.Add(w);
wg.Done();
Expand All @@ -892,6 +893,7 @@ go func() {
wg.Wait();
v.X; //3
`,
3,
'',
Expand Down Expand Up @@ -1667,3 +1669,102 @@ for {
'',
defaultNumWords
);

// Testing slices
testProgram(
`
var x []int = make([]int, 5, 10); // create a slice of len 5 and capacity 10
x = append(x, 5);
for i := 0; i < len(x); i++ {
print(x[i]);
}
x[5];
`,
5,
'0\n0\n0\n0\n0\n5\n',
defaultNumWords
);

// Testing re-allocation
testProgram(
`
var x []int = make([]int, 5, 5); // create a slice of len 5 and capacity 5
var y []int = append(x, 10); // this should point to a new y
print(y[5]); // shud be 10
print(len(x)); // should be 5
y[0]; // should be 0
`,
0,
'10\n5\n',
defaultNumWords
);

// Testing default initialization
testProgram(
`
var x []bool = make([]bool, 5, 5); // create a slice of len 5 and capacity 5
for i := 0; i < len(x); i++ {
print(x[i]);
}
0;
`,
0,
'false\nfalse\nfalse\nfalse\nfalse\n',
defaultNumWords
);

testProgram(
`
var x []string = make([]string, 5, 5); // create a slice of len 5 and capacity 5
x = append(x, "Jotham");
for i := 0; i < len(x); i++ {
print(x[i]);
}
0;
`,
0,
'""\n""\n""\n""\n""\n"Jotham"\n',
defaultNumWords
);

testProgram(
`
type Vector struct {
x int
y int
}
var vs []Vector = make([]Vector, 5, 10);
for i := 0; i < len(vs); i++ {
print(vs[i]); // null 5 times
}
10;
`,
10,
'null\nnull\nnull\nnull\nnull\n',
defaultNumWords
);

// Test out of bounds error
testProgram(
`
var x []int = make([]int, 5, 5);
x[6];
`,
'Array out of bounds error!',
'',
defaultNumWords
);

testProgram(
`
var x []int = make([]int, 5, 10);
x[6]; // still garbage data
`,
'Array out of bounds error!',
'',
defaultNumWords
);
19 changes: 17 additions & 2 deletions src/vm/oogavm-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -895,8 +895,18 @@ const compileComp = {
compile(comp.args[0], ce);
instrs[wc++] = { tag: Opcodes.CREATE_BUFFERED };
} else if (is_type(comp.type, ArrayType)) {
// Slice (currently not supporting dynamically resizable array)
// so this is just a default initialized array at the moment, that means
// Slice which is of initial len and capacity and can grow by appending
// The format is len, capacity
if (comp.args.length === 1) {
// If the user only provides a single value, capacity == len
// this means that len == capacity
compile(comp.args[0], ce); // len
compile(comp.args[0], ce); // capacity
} else {
compile(comp.args[0], ce); // len
compile(comp.args[1], ce); // capacity
}
instrs[wc++] = { tag: Opcodes.CREATE_SLICE, elementType: comp.type.elem_type };
} else {
throw new OogaError('Unsupported make type at the moment!');
}
Expand All @@ -913,6 +923,11 @@ const compileComp = {
instrs[wc++] = { tag: Opcodes.WRITE_CHANNEL };
instrs[wc++] = { tag: Opcodes.CHECK_CHANNEL };
},
AppendExpression: (comp, ce) => {
compile(comp.args[0], ce);
compile(comp.name, ce);
instrs[wc++] = { tag: Opcodes.APPEND };
},
BreakpointStatement: (comp, ce) => {
instrs[wc++] = { tag: Opcodes.BREAKPOINT };
},
Expand Down
121 changes: 119 additions & 2 deletions src/vm/oogavm-heap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import debug from 'debug';
import { HeapError, HeapOutOfMemoryError, OogaError } from './oogavm-errors.js';
import { HeapError, HeapOutOfMemoryError, OogaError, RuntimeError } from './oogavm-errors.js';
import { getRoots, E, OS, RTS } from './oogavm-machine.js';
import { head } from '../utils/utils';

Expand Down Expand Up @@ -28,6 +28,7 @@ export enum Tag {
STRING,
MUTEX,
ARRAY,
SLICE,
BUFFERED,
UNBUFFERED,
}
Expand Down Expand Up @@ -75,6 +76,8 @@ function getTagString(tag: Tag): string {
return 'MUTEX';
case Tag.ARRAY:
return 'ARRAY';
case Tag.SLICE:
return 'SLICE';
case Tag.BUFFERED:
return 'BUFFERED';
case Tag.UNBUFFERED:
Expand Down Expand Up @@ -461,6 +464,7 @@ function isStruct(address: number) {
// *********************
function allocateNumber(n: number): number {
const numAddress = allocate(Tag.NUMBER, 3);
log('Allocating number ' + n + ' to address ' + numAddress);
setWord(numAddress + headerSize, n);
return numAddress;
}
Expand Down Expand Up @@ -588,7 +592,8 @@ export function getArrayValue(address: number): any[] {
let result: any[] = [];
for (let i = 0; i < arrayLength; i++) {
const arrayElementAddress = getWordOffset(address, i + headerSize);
result.push(arrayElementAddress);
const arrayElementValue = addressToTSValue(arrayElementAddress);
result.push(arrayElementValue);
}
return result;
}
Expand All @@ -605,6 +610,106 @@ export function getArrayValueAtIndex(arrayAddress: number, idx: number): any {
return getWordOffset(arrayAddress, idx + headerSize);
}

// ********************************
// Slice
// ********************************
// A slice is a resizable array.
// 3rd word is for current number of elements inside.
export function allocateSlice(len: number, initialCapacity: number): number {
if (len > initialCapacity) {
throw new RuntimeError('Cannot allocate slice with len more than capacity');
}
const address = allocate(Tag.SLICE, initialCapacity + headerSize + 1);
// starts with len
heap.setUint32((address + headerSize) * wordSize, len);
return address;
}

export function setSliceValue(sliceAddress: number, idx: number, value: number) {
// Check does not exceed capacity
const capacity = getSliceCapacity(sliceAddress);
log('Slice capacity is ' + capacity);
if (idx >= capacity) {
throw new OogaError(
'Indexing out of bounds. Indexed ' + idx + ' on a slice of capacity ' + capacity + '.'
);
}
setWord(sliceAddress + idx + headerSize + 1, value);
log('Set word at index ' + idx + ' of slice at ' + sliceAddress + ' to ' + value);
}

// Attempt to append to a slice at latest value.
// If hit capacity, will reallocate a new slice of double the capacity
// and return the address of the reallocated slice.
// This aligns with how golang actually does slices.
// Cos this can trigger GC, params are lists
export function appendToSlice(sliceAddress: number[], value: number[]): number {
const sliceLength = getSliceLength(sliceAddress[0]);
const sliceCapacity = getSliceCapacity(sliceAddress[0]);
log('Slice length is ' + sliceLength);
log('Slice cap is ' + sliceCapacity);
if (sliceLength === sliceCapacity) {
log('Copying over slice');
let returnAddress = allocateSlice(sliceLength, sliceLength * 2);
// Copy over all elements
for (let i = 0; i < sliceLength; i++) {
const sliceElement = getSliceValueAtIndex(sliceAddress[0], i);
setSliceValue(returnAddress, i, sliceElement);
}
// finally append
setSliceValue(returnAddress, sliceLength, value[0]);
heap.setUint32((returnAddress + headerSize) * wordSize, sliceLength + 1);
return returnAddress;
}
log('Just allocating directly.');
// no allocation, just set latest value and update length
setSliceValue(sliceAddress[0], sliceLength, value[0]);
heap.setUint32((sliceAddress[0] + headerSize) * wordSize, sliceLength + 1);
return sliceAddress[0];
}

// Returns the addresses of the values in the array
export function getSliceValue(address: number): any[] {
const sliceLength = getSliceLength(address);
let result: any[] = [];
for (let i = 0; i < sliceLength; i++) {
const arrayElementAddress = getWordOffset(address, i + headerSize + 1);
const arrayElementValue = addressToTSValue(arrayElementAddress);
result.push(arrayElementValue);
}
return result;
}

// returns the address of the element at said index
export function getSliceValueAtIndex(sliceAddress: number, idx: number): any {
const sliceLength: number = getSliceLength(sliceAddress);
if (idx < 0) {
throw new OogaError('Negative indexing not allowed!');
}
if (idx >= sliceLength) {
throw new OogaError('Array out of bounds error!');
}
return getWordOffset(sliceAddress, idx + headerSize + 1);
}

export function isSlice(address: number): boolean {
return getTag(address) === Tag.SLICE;
}

export function getSliceCapacity(address: number): number {
if (getTag(address) !== Tag.SLICE) {
throw new OogaError('Called getArrayLength on non array type');
}
return getSize(address) - headerSize - 1;
}

export function getSliceLength(address: number): number {
if (getTag(address) !== Tag.SLICE) {
throw new OogaError('Called getArrayLength on non array type');
}
return heap.getUint32((address + headerSize) * wordSize);
}

// ********************************
// Channels
// ********************************
Expand Down Expand Up @@ -828,6 +933,16 @@ export function debugHeap(): void {
log('Child ' + i + ': ' + getWord(curr + headerSize + 1 + i));
}
break;
case Tag.ARRAY:
for (let i = 0; i < getArrayLength(curr); i++) {
log('Child ' + i + ': ' + getArrayValueAtIndex(curr, i));
}
break;
case Tag.SLICE:
for (let i = 0; i < getSliceLength(curr); i++) {
log('Child ' + i + ': ' + getSliceValueAtIndex(curr, i));
}
break;
default:
break;
}
Expand Down Expand Up @@ -993,6 +1108,8 @@ export function addressToTSValue(address: number) {
return getStringValue(address);
} else if (isArray(address)) {
return getArrayValue(address);
} else if (isSlice(address)) {
return getSliceValue(address);
} else if (isBufferedChannel(address)) {
return '<bufferedChannel>';
} else if (isUnbufferedChannel(address)) {
Expand Down
Loading

0 comments on commit 624b807

Please sign in to comment.