Skip to content

Instantly share code, notes, and snippets.

@neopunisher
Last active February 20, 2023 15:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neopunisher/24d22bd0145b06218d2e6d4b16ae2ba4 to your computer and use it in GitHub Desktop.
Save neopunisher/24d22bd0145b06218d2e6d4b16ae2ba4 to your computer and use it in GitHub Desktop.
const buf = new ArrayBuffer(maxBufferLen);
const dv = new DataView(buf);
class MagicWindow {
constructor(name, getter, setter) {
Object.defineProperty (window, name, {
get: getter,
set: setter
});
}
}
class Test extends MagicWindow {
constructor(dataView, offset, name) {
super(name, dataView.getInt8.bind(dataView, offset),dataView.setInt8.bind(dataView, offset));
}
}
['BigInt64', 'BigUint64', 'Int32', 'Uint32', 'Int16', 'Uint16', 'Int8', 'Uint8', 'Float32', 'Float64'].forEach(function(dataType){
Object.defineProperty (window, dataType, {
get: function(){
const getFuncName = `get${dataType}`
const setFuncName = `set${dataType}`
return (
class extends MagicWindow {
constructor(dataView, offset, name) {
super(name, dataView[getFuncName].bind(dataView, offset),dataView[setFuncName].bind(dataView, offset));
}
}
)
}
});
})
const importJs = async (url, module = { exports: {} }) => (Function('module', 'exports', await (await fetch(url)).text()).call(module, module, module.exports), module).exports
const importWasm = async (url, imports = {}) => {
const { instance } = await WebAssembly.instantiateStreaming(await fetch(url), imports);
return instance.exports;
}
const { parseScript } = await importJs('https://cdn.jsdelivr.net/npm/esprima/dist/esprima.js')
// String stuff
const encodeStr = (str) => new TextEncoder().encode(str)
const strLen = (str) => encodeStr(str).length
const stoa = (str) => Uint8Array.from(str, x => x.charCodeAt(0))
const atos = (arr) => String.fromCharCode.apply(null, arr)
// Base 2 stuff
const getBaseLog = (x, y) => (Math.log(y) / Math.log(x))
const baseTwoLog = (y) => getBaseLog(2, y)
const powTwo = (x) => Math.pow(2, x)
// byte stuff
const byteLen = powTwo(3);
const maxBufferLen = powTwo(31) - powTwo(21);
const maxIntOfBytes = (i) => (powTwo(i * 8) - 1)
const maxSignedIntOfBytes = (i) => (powTwo((i * 8) - 1) - 1)
//bit stuff
const toBits = (idx) => (idx >>> 0).toString(2).padStart(8, '0')
const maxIntOfBits = (i) => (powTwo(i) - 1)
const byteToBin = (idx) => ((idx >>> 0).toString(2).padStart(8, '0'))
// sizes
const pageSize = powTwo(12);
const onekb = powTwo(10);
const onemb = powTwo(20);
const onegb = powTwo(30);
// hex
const hexDigit = (b) => b.toString(16).padStart(2, '0')
const bufToHex = (buf) => Array.from(new Uint8Array(buf)).map(hexDigit).join('')
const mergeInt = (left, right, bits, littleEndian = false) => {
const scale = powTwo(bits * 8);
return littleEndian ? left + scale * right : scale * left + right;
}
const littleEndian = (() => {
const buffer = new ArrayBuffer(2);
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
return new Int16Array(buffer)[0] === 256;
})();
const FloatType = Symbol("Float")
const IntegerType = Symbol("Float")
const ArrayType = Symbol("Array")
const IntType = Symbol("Int")
const UintType = Symbol("Uint")
const signed = Symbol("signed")
const unsigned = Symbol("unsigned")
const _8bit = Symbol("8")
const _16bit = Symbol("16")
const _32bit = Symbol("32")
const _64bit = Symbol("64")
const Float64 = Symbol("Float64");
const Float32 = Symbol("Float32");
const Str = Symbol("String");
const Bool = Symbol("Boolean");
const Uint8 = Symbol("Uint8");
const Int8 = Symbol("Uint8");
const Uint16 = Symbol("Uint16");
const Int16 = Symbol("Int16");
const Uint32 = Symbol("Uint32");
const Int32 = Symbol("Int32");
const Uint64 = Symbol("Uint64");
const Int64 = Symbol("Int64");
const getter = Symbol("get");
const setter = Symbol("set");
const heap = Symbol("heap");
const stack = Symbol("stack");
const archs = new Set([_8bit, _16bit, _32bit, _64bit])
const floats = new Set([Float32, Float64]);
const signedInts = new Set([Int64, Int32, Int16, Int8]);
const unsignedInts = new Set([Uint64, Uint32, Uint16, Uint8]);
const dataTypes = new Set([Int64, Uint64, Int32, Uint32, Int16, Uint16, Int8, Uint8, Float32, Float64]);
const bigDataTypes = new Set([Uint64, Int64]);
const literals = {
[FloatType]: {
[_32bit]: Float32,
[_64bit]: Float64
},
[IntegerType]: {
[signed]: {
[_8bit]: Int8,
[_16bit]: Int16,
[_32bit]: Int32,
[_64bit]: Int64,
},
[unsigned]: {
[_8bit]: Uint8,
[_16bit]: Uint16,
[_32bit]: Uint32,
[_64bit]: Uint64,
}
}
}
const byteArrays = {
[FloatType]: {
[_32bit]: Float32Array,
[_64bit]: Float64Array
},
[IntegerType]: {
[signed]: {
[_8bit]: Int8Array,
[_16bit]: Int16Array,
[_32bit]: Int32Array,
[_64bit]: BigInt64Array,
},
[unsigned]: {
[_8bit]: Uint8Array,
[_16bit]: Uint16Array,
[_32bit]: Uint32Array,
[_64bit]: BigUint64Array,
}
}
}
const byteLengths = {
[Int8]: _8bit,
[Uint8]: _8bit,
[Int16]: _16bit,
[Uint16]: _16bit,
[Int32]: _32bit,
[Uint32]: _32bit,
[Int64]: _64bit,
[Uint64]: _64bit,
[Float32]: _32bit,
[Float64]: _64bit,
}
const strToDataType = {
"Int8": Int8,
"Uint8": Uint8,
"Int16": Int16,
"Uint16": Uint16,
"Int32": Int32,
"Uint32": Uint32,
"Int64": Int64,
"Uint64": Uint64,
"Float32": Float32,
"Float64": Float64,
}
const archToByteLength = {
[_8bit]: 1,
[_16bit]: 2,
[_32bit]: 3,
[_64bit]: 4
}
const archUint = {
[_8bit]: literals[IntegerType][unsigned][_8bit],
[_16bit]: literals[IntegerType][unsigned][_16bit],
[_32bit]: literals[IntegerType][unsigned][_32bit],
[_64bit]: literals[IntegerType][unsigned][_64bit]
}
const archUintArray = {
[_8bit]: byteArrays[IntegerType][unsigned][_8bit],
[_16bit]: byteArrays[IntegerType][unsigned][_16bit],
[_32bit]: byteArrays[IntegerType][unsigned][_32bit],
[_64bit]: byteArrays[IntegerType][unsigned][_64bit]
}
const strToArch = {
"8": _8bit,
"16": _16bit,
"32": _32bit,
"64": _64bit
}
const bindViewFuncs = (dataView, offset, dataType) => {
dataType = (bigDataTypes.has(dataType) ? 'Big' : '') + dataType.description;
// console.log('bindView', offset, dataType)
const setFunc = dataView[`set${dataType}`].bind(dataView, offset)
return [
dataView[`get${dataType}`].bind(dataView, offset, littleEndian),
(val) => setFunc(val, littleEndian)
];
}
class Stack {
#arr;
constructor (sm) {
this.#arr = sm.av;
const [stackGetter, stackSetter] = sm.new(archUint[sm.arch])('esp', this.elementByteLength); // stack pointer
Object.defineProperty(this, 'sp', {
get: stackGetter,
set: stackSetter
});
this.sp = maxIntOfBytes(this.elementByteLength);
sm.push = this.push.bind(this);
sm.pop = this.pop.bind(this);
}
get elementByteLength () {
return this.#arr.BYTES_PER_ELEMENT
}
push (obj) {
this.#arr[this.sp--] = obj;
}
pop () {
return this.#arr[++this.sp];
}
}
class Heap {
#arr;
#start;
constructor (sm, offset) {
this.#start = offset;
const [heapGetter, heapSetter] = sm.new(archUint[sm.arch])('ehp', 0); // heap pointer
Object.defineProperty(this, 'hp', {
get: heapGetter,
set: heapSetter
});
this.#arr = sm.av;
sm.jalloc = this.jalloc.bind(this);
sm.strAlloc = this.strAlloc.bind(this);
}
get elementByteLength () {
return this.#arr.BYTES_PER_ELEMENT
}
get start () {
return this.#start;
}
jalloc (size) {
const orig = this.hp;
this.hp += size;
return orig;
}
jfree (loc, size) {
this.hp -= size;
}
strAlloc (str) {
const loc = this.jalloc(strLen(str));
this.#arr.set(encodeStr(str), loc)
return loc;
}
}
class Register {
#dataType
#slot
constructor (sm, name, dataType) {
this.#dataType = dataType;
const slot = this.sm.jalloc(this.byteLength);
this.#slot = slot;
const [getterFunc, setterFunc] = bindViewFuncs(sm.dv, slot * this.byteLength + sm.heap.start, dataType);
Object.defineProperty(sm, name, {
get: getterFunc,
set: setterFunc
});
}
get byteLength () {
return archToByteLength[byteLengths[this.#dataType]]
}
get slot () {
return this.#slot;
}
}
class StackMachine {
#buf;
#dv;
#av;
#arch;
#stack;
#heap;
#inst;
#peek;
constructor (arch = _16bit, byteLength = maxBufferLen) {
this.#arch = arch;
const buf = new ArrayBuffer(byteLength);
const arrayView = new (archUintArray[arch])(buf, this.elementByteLength * 3);
const dataView = new DataView(buf);
this.#buf = buf;
this.#dv = dataView;
this.#av = arrayView;
this.#inst = this.getCurrentInstruction();
this.#peek = this.doPeek();
const that = this;
const strToFunc = {}
dataTypes.forEach(function (dataType) {
const func = (name, offset, target = that) => {
const [getterFunc, setterFunc] = bindViewFuncs(that.#dv, offset, dataType);
Object.defineProperty(target, name, {
get: getterFunc,
set: setterFunc
});
return [getterFunc, setterFunc];
}
that[`new_${dataType.description}`] = func;
strToFunc[dataType.description] = func;
})
this.new = (dataType) => {
if (typeof dataType === 'symbol') {
dataType = dataType.description
}
return strToFunc[dataType]
}
this.new(archUint[arch])('eip', this.elementByteLength * 2) // instruction pointer
this.#stack = new Stack(this);
this.#heap = new Heap(this, this.elementByteLength * 3);
}
get elementByteLength () {
return archToByteLength[this.#arch]
}
get buf () {
return this.#buf;
}
get dv () {
return this.#dv;
}
get av () {
return this.#av;
}
get stack () {
return this.#stack;
}
get heap () {
return this.#heap;
}
get arch () {
return this.#arch;
}
get byteLength () {
return this.#buf.byteLength
}
get dataView () {
return new Uint16Array(this.#buf, 0, powTwo(this.elementByteLength * 8) + 3)
}
* getCurrentInstruction () {
while (this.eip < this.byteLength) {
yield this.#av[this.eip++];
}
}
* doPeek () {
while (this.eip < this.byteLength) {
yield this.#av[this.eip + 1];
}
}
get currentInstruction () {
return this.#inst.next().value;
}
get currentArgument () {
return this.#peek.next().value;
}
step () {
switch (this.currentInstruction) {
case 0x00:
console.log('nop')
this.eip++;
break;
case 0x01:
console.log('add')
this.r1 = this.r1 + this.r2;
this.eip++;
break;
case 0x02:
console.log('sub')
this.r1 = this.r1 - this.r2;
this.eip++;
break;
case 0x03:
console.log('call')
this.push(this.eip + 2);
this.eip = this.currentArgument;
break;
case 0x04:
console.log('ret')
this.eip = this.pop();
this.eip++;
break;
case 0x05:
console.log('push')
this.push(this.r1);
this.eip += 2;
break;
case 0x06:
console.log('pop')
this.r1 = this.pop();
this.eip++;
break;
case 0x07:
console.log('store')
this.r1 = this.currentArgument;
this.eip += 2;
break;
case 0x08:
console.log('jmp')
this.eip = this.currentArgument;
break;
case 0x09:
console.log('jz')
if (this.r1 === 0) {
this.eip = this.currentArgument;
} else {
this.eip += 2;
}
break;
case 0x0A:
console.log('jnz')
if (this.r1 !== 0) {
this.eip = this.currentArgument;
} else {
this.eip += 2;
}
break;
case 0x0B:
console.log('je')
if (this.r1 === this.r2) {
this.eip = this.currentArgument;
} else {
this.eip += 2;
}
break;
case 0x0C:
console.log('jne')
if (this.r1 !== this.r2) {
this.eip = this.currentArgument;
} else {
this.eip += 2;
}
break;
case 0x0D:
console.log('load')
this.#av[this.currentArgument] = this.r1;
this.eip += 2;
break;
case 0x0E:
console.log('mov')
this.r1 = this.currentArgument;
this.eip += 2;
break;
case 0x0F:
console.log('jalloc')
this.r1 = this.jalloc(this.currentArgument);
this.eip += 2;
break;
case 0x10:
console.log('jfree')
this.jfree(this.currentArgument);
this.eip += 2;
break;
default:
console.log()
}
}
}
const sm = new StackMachine();
sm.push(420)
sm.push(69)
slot = sm.jalloc(1)
sm.av[slot] = 666;
let prog2 = parseScript(`
function main(){
var a = 420;
var b = 69;
var c = a + b;
return 69;}`);
const TARGET_BYTE_LENGTH = 2;
function mergeFunc(funcs, func){
if (func.name in funcs) {
if (func.body !== funcs[func.name].body) {
throw new Error('Function body mismatch')
}
if (func.args.length !== funcs[func.name].args.length) {
throw new Error('Function argument mismatch')
}
funcs[func.name].uses = func.uses.concat(func.uses)
} else {
if (!func.name) {
console.error({func})
throw new Error('Function nas no name')
}
funcs[func.name] = func;
}
}
function mergeFunctions (...mergeFuncs) {
return mergeFuncs.reduce((funcs, func) => {
if (!func){
return funcs;
} else if (!funcs && func && (typeof func === 'object')) {
return func;
} else {
mergeFunc(funcs, func)
}
return funcs;
}, {})
}
function mergeVars (...vars) {
return vars.reduce((vars, var_) => {
if (!var_){
return vars;
} else if (!vars && var_ && (typeof var_ === 'object')) {
return var_;
} else if (var_.name in vars) {
if (var_.type !== vars[var_.name].type) {
throw new Error('Variable type mismatch')
}
vars[var_.name].uses = var_.uses.concat(var_.uses)
} else {
vars[var_.name] = var_;
}
return vars;
}, {})
}
function perdictNumericType (val) {
if (parseInt(val) !== val) {
// test if the float fits into a 32bit float
if (val) {
return Float32;
} else {
return Float64;
}
} else if (val < 0 && val >= maxUnsignedIntOfBytes(1)) {
return Int8;
} else if (val < 0 && val >= -maxUnsignedIntOfBytes(2)) {
return Int16;
} else if (val < 0 && val >= -maxUnsignedIntOfBytes(3)) {
return Int32;
} else if (val < 0 && val >= -maxUnsignedIntOfBytes(4)) {
return Int64;
} else if (val <= maxIntOfBytes(1)) {
return Uint8;
} else if (val <= maxIntOfBytes(2)) {
return Uint16;
} else if (val <= maxIntOfBytes(3)) {
return Uint32;
} else if (val <= maxIntOfBytes(4)) {
return Uint64;
}
}
function perdictType (node) {
const nodeType = typeof node;
switch (nodeType) {
case 'number':
return perdictNumericType(node.value);
case 'string':
return Str;
case 'boolean':
return Bool;
default:
console.error({unknown: node});
throw new Error(`Unknown type! ${nodeType}`)
}
}
class Var {
#name;
#type;
#uses;
#addr;
#arg = -1;
#defaultValue;
constructor (name, type, uses = [], arg = false, defaultValue = null) {
this.#name = name;
this.#type = type;
this.#uses = uses;
this.#defaultValue = defaultValue;
this.#arg = arg;
}
get name () {
return this.#name;
}
get type () {
return this.#type;
}
get uses () {
return this.#uses;
}
set uses (uses) {
this.#uses = uses;
}
get addr () {
return this.#addr;
}
set addr (addr) {
if (this.#addr) {
throw new Error('Variable address already set')
}
this.#addr = addr;
}
get isArg () {
return this.#arg !== -1;
}
get argIndex () {
return this.#arg;
}
get defaultValue () {
return this.#defaultValue;
}
}
class Func {
#name;
#args = {};
#body;
#uses = [];
#vars;
#loc;
constructor (name, args, body = [], vars = {}) {
this.#name = name;
if (typeof args === 'array') {
this.#args = mergeVars(...args.map((arg, i) => new Var(arg.name, perdictType(arg.type), [0], i, node.init)));
} else if (typeof args === 'object'){
this.#args = args;
}
this.#body = body;
this.#vars = vars;
}
toBin () {
return new Bin(this.#body, {[this.#name]: this}, mergeVars(this.#vars));
}
set loc (loc) {
if (this.#loc) {
throw new Error('Function location already set')
}
this.#loc = loc;
}
get loc () {
return this.#loc;
}
get name () {
return this.#name;
}
get args () {
return this.#args;
}
get body () {
return this.#body;
}
get vars () {
return this.#vars;
}
get uses () {
return this.#uses;
}
set uses (uses) {
this.#uses = uses;
}
}
function mergeBin (bin, ...bins) {
bins.map(bin.mergeBin)
return bin
}
class Bin {
#bin = [];
#funcs;
#vars;
constructor (bin = [], funcs = {}, vars = {}) {
this.concat(bin);
this.#funcs = funcs;
this.#vars = vars;
this.mergeNode = this.mergeNode.bind(this);
this.mergeBin = this.mergeBin.bind(this);
}
push (val) {
if (typeof val === 'Bin') {
this.mergeBin(val);
} else {
this.#bin.push(val);
}
}
concat (arr) {
if (typeof arr === 'Bin') {
this.mergeBin(arr);
} else {
this.#bin = this.#bin.concat(arr);
}
}
mergeBin (bin) {
this.#bin = this.#bin.concat(bin.raw);
this.#funcs = mergeFunctions(this.#funcs, ...Object.values(bin.funcs));
this.#vars = mergeVars(this.#vars, ...Object.values(bin.vars));
}
mergeNode (node) {
this.mergeBin(astToBin(node));
}
get byteLength () {
return this.#bin.length;
}
get elementByteLength () {
return TARGET_BYTE_LENGTH;
}
get raw () {
return this.#bin;
}
get funcs () {
return this.#funcs;
}
get vars () {
return this.#vars;
}
}
function astToBin (node) {
if (typeof node === 'undefined') debugger;
const bin = new Bin();
if (Array.isArray(node)){
return node.map(bin.mergeNode);
}
switch (node.type) {
case 'FunctionDeclaration':
console.log('define func:', node.id.name)
bin.funcs[node.id.name] = new Func(node.id.name, node.arguments, astToBin(node.body).raw);
break;
case 'ReturnStatement':
if (node.argument) {
bin.concat(astToBin(node.argument))
}
bin.push(0x04)
break;
case 'ExpressionStatement':
exprs[node.expression] = astToBin(node.expression);
break;
case 'CallExpression':
bin.push(0x03)
bin.funcs[node.callee.name].uses.push(bin.byteLength);
bin.push(0x00)
break;
case 'Identifier':
bin.push(0x02)
bin.push(0x00)
break;
case 'Literal':
bin.push(0x07)
bin.push(node.value)
switch (typeof node.value) {
case 'number':
if (node.value > maxIntOfBytes(TARGET_BYTE_LENGTH)) {
throw new Error('Number too large')
}
bin.push(node.value);
break;
default:
throw new Error('Unhandled type');
}
break;
case 'EmptyStatement':
bin.push(0x00)
break;
case 'BlockStatement':
bin.concat(astToBin(node.body));
break;
case 'VariableDeclaration':
node.declarations.forEach((decl) => {
bin.vars[decl.id.name] = new Var(decl.id.name, perdictType(decl.type), [bin.byteLength], -1, decl.init);
})
break;
case 'Program':
bin.concat(astToBin(node.body))
break;
default:
console.error('Unhandled', node.type, node)
}
if (bin.funcs.length > 0) {
Array.from(funcs.keys()).forEach(function (funcName) {
const currIdx = bin.byteLength;
bin.funcs[funcName].loc = currIdx;
bin = bin.concat(funcs[funcName].raw);
})
}
return bin;
}
const bin = astToBin(prog2); // keikaku
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment