Would you read a wall of text without going insane?
I think that you won’t read a boring wall of text, and hence why I kept the humorous “all sunshine and rainbows” theme of the topic.
Accusing someone of using AI because of emojis is insane, man.
A big part of the luau bytecode compiler being dumb is that the compiler itself needs to be extremely fast because Roblox recompiles bytecode each server startup
So the actual source code gets stored and then on RCC start it will compile all the bytecode. That means you can’t do fancy stuff
W post though
Thank you so much for making this because I am making Luau VM in JS and it is super helpful
*Bytecode.h Lil broooo
Later,
Later,
Even more later,
Previously:
0 complexity, 0 presentation
“Frreee Executor Roblox”
What is this supposed to mean lil bro
What a good presentation
Letting your users think it is malware at first glance and providing download links in a Discord server? Good stuff
…?
I will not gatekeep like this Dottik noob So here guys
Hopefully it helps somebody
// add luau tuple support with js array (i think it's supported but just in case)
// dupclosure is a little messy so fix it
// work on namecall and call
import ByteEditor from "/modules/ByteEditor.js";
import { enforcePropertyConstructor, randomString } from "/modules/utilities.js";
const opList = [
["NOP", 0, 0, false],
["BREAK", 0, 0, false],
["LOADNIL", 1, 0, false],
["LOADB", 3, 0, false],
["LOADN", 4, 0, false],
["LOADK", 4, 3, false],
["MOVE", 2, 0, false],
["GETGLOBAL", 1, 1, true],
["SETGLOBAL", 1, 1, true],
["GETUPVAL", 2, 0, false],
["SETUPVAL", 2, 0, false],
["CLOSEUPVALS", 1, 0, false],
["GETIMPORT", 4, 4, true],
["GETTABLE", 3, 0, false],
["SETTABLE", 3, 0, false],
["GETTABLEKS", 3, 1, true],
["SETTABLEKS", 3, 1, true],
["GETTABLEN", 3, 0, false],
["SETTABLEN", 3, 0, false],
["NEWCLOSURE", 4, 0, false],
["NAMECALL", 3, 1, true],
["CALL", 3, 0, false],
["RETURN", 2, 0, false],
["JUMP", 4, 0, false],
["JUMPBACK", 4, 0, false],
["JUMPIF", 4, 0, false],
["JUMPIFNOT", 4, 0, false],
["JUMPIFEQ", 4, 0, true],
["JUMPIFLE", 4, 0, true],
["JUMPIFLT", 4, 0, true],
["JUMPIFNOTEQ", 4, 0, true],
["JUMPIFNOTLE", 4, 0, true],
["JUMPIFNOTLT", 4, 0, true],
["ADD", 3, 0, false],
["SUB", 3, 0, false],
["MUL", 3, 0, false],
["DIV", 3, 0, false],
["MOD", 3, 0, false],
["POW", 3, 0, false],
["ADDK", 3, 2, false],
["SUBK", 3, 2, false],
["MULK", 3, 2, false],
["DIVK", 3, 2, false],
["MODK", 3, 2, false],
["POWK", 3, 2, false],
["AND", 3, 0, false],
["OR", 3, 0, false],
["ANDK", 3, 2, false],
["ORK", 3, 2, false],
["CONCAT", 3, 0, false],
["NOT", 2, 0, false],
["MINUS", 2, 0, false],
["LENGTH", 2, 0, false],
["NEWTABLE", 2, 0, true],
["DUPTABLE", 4, 3, false],
["SETLIST", 3, 0, true],
["FORNPREP", 4, 0, false],
["FORNLOOP", 4, 0, false],
["FORGLOOP", 4, 8, true],
["FORGPREP_INEXT", 4, 0, false],
["FASTCALL3", 3, 1, true],
["FORGPREP_NEXT", 4, 0, false],
["DEP_FORGLOOP_NEXT", 0, 0, false],
["GETVARARGS", 2, 0, false],
["DUPCLOSURE", 4, 9, false],
["PREPVARARGS", 1, 0, false],
["LOADKX", 1, 1, true],
["JUMPX", 5, 0, false],
["FASTCALL", 3, 0, false],
["COVERAGE", 5, 0, false],
["CAPTURE", 2, 0, false],
["SUBRK", 3, 7, false],
["DIVRK", 3, 7, false],
["FASTCALL1", 3, 0, false],
["FASTCALL2", 3, 0, true],
["FASTCALL2K", 3, 1, true],
["FORGPREP", 4, 0, false],
["JUMPXEQKNIL", 4, 5, true],
["JUMPXEQKB", 4, 5, true],
["JUMPXEQKN", 4, 6, true],
["JUMPXEQKS", 4, 6, true],
["IDIV", 3, 0, false],
["IDIVK", 3, 2, false],
];
const LUA_MULTRET = -1;
// const LUA_GENERALIZED_TERMINATOR = -2; // unneeded?
function getLuauType(object) {
if (object == null) return "nil";
switch (typeof object) {
case "bigint":
case "number": return "number";
case "string": return "string";
case "boolean": return "boolean";
case "object": return "table";
case "function": return "function";
case "symbol":
default: return "userdata"; // maybe
};
};
// const bit_band = (a, b) => (a & b) >>> 0;
// const bit_rshift = (v, s) => (v >>> s) >>> 0;
const bit_extract = (v, pos, size) => ((v >>> pos) & ((1 << size) - 1)) >>> 0;
export default class LuauEngine {
name = `[string "${randomString(7, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopwrstuvwxyz1234567890")}"]`;
globals = Object.create(null);
useImportConstants = false;
vectorConstructor = () => { throw new Error("Vector not implemented yet") };
constructor() {
enforcePropertyConstructor(this, "name", String);
enforcePropertyConstructor(this, "globals", Object);
enforcePropertyConstructor(this, "useImportConstants", Boolean);
[
"vectorConstructor",
"bindToLuauThis"
].forEach(name => enforcePropertyConstructor(this, name, Function));
}
bindToLuauThis(jsFunction) {
return function (self, ...args) {
return jsFunction.apply(self, args);
}
}
deserialize(bytecode) {
const engine = this;
const { useImportConstants, globals } = engine;
const editor = new ByteEditor(bytecode);
const luauVersion = editor.readUint8();
let typesVersion = 0;
if (luauVersion === 0) {
throw new SyntaxError(engine.name + editor.readString(editor.bytesLeft)); // new Error("the provided bytecode is an error message");
} else if (luauVersion < 3 || luauVersion > 6) {
throw new TypeError(`unsupported luau bytecode, version ${luauVersion}`);
} else if (luauVersion >= 4) {
typesVersion = editor.readUint8();
}
const stringCount = editor.readVarInt32();
const stringList = Array.from({ length: stringCount }, () =>
editor.readString(editor.readVarInt32()) // stored indices in bytecode are 1-based -> subtract 1 when used; here we read strings sequentially
); // 0-based
function readInstruction(codeList) {
const value = editor.readUint32(true); // decodeOp
const opcode = (value & 0xFF);
const opinfo = opList[opcode];
if (!opinfo) throw new TypeError("Unknown opcode " + opcode);
const [opname, opmode, kmode, usesAux] = opinfo;
const inst = {
opcode,
opname,
opmode,
kmode,
usesAux
};
codeList.push(inst);
switch (opmode) {
case 1: inst.A = ((value >>> 8) & 0xFF); break; // A
case 2: { // AB
inst.A = ((value >>> 8) & 0xFF);
inst.B = ((value >>> 16) & 0xFF);
break;
};
case 3: { // ABC
inst.A = ((value >>> 8) & 0xFF);
inst.B = ((value >>> 16) & 0xFF);
inst.C = ((value >>> 24) & 0xFF);
};
case 4: { // AD
inst.A = ((value >>> 8) & 0xFF);
const temp = ((value >>> 16) & 0xFFFF);
inst.D = (temp < 0x8000) ? temp : (temp - 0x10000);
break;
};
case 5: { // AE
const temp = ((value >>> 8) & 0xFFFFFF);
inst.E = (temp < 0x800000) ? temp : (temp - 0x1000000);
break;
}
}
if (usesAux) {
const aux = editor.readUint32(true);
inst.aux = aux;
// mirror Lua behaviour of adding aux pseudo (so codeList lengths match)
codeList.push({ value: aux, opname: "auxvalue" });
}
return usesAux;
};
function checkkmode(inst, k) {
const { kmode, aux, C, D, K0, K1, K2 } = inst;
switch (kmode) {
case 1: inst.K = k[aux]; break; // AUX
case 2: inst.K = k[C]; break; // C
case 3: inst.K = k[D]; break; // D
case 4: { // AUX import
// const extend = aux;
const count = (aux >>> 30);
const id0 = ((aux >>> 20) & 0x3FF);
inst.K0 = k[id0];
inst.KC = count;
switch (count) {
// case 1?
case 2: {
const id1 = ((aux >>> 10) & 0x3FF);
inst.K1 = k[id1];
break
};
case 3: {
const id1 = ((aux >>> 10) & 0x3FF);
const id2 = ((aux >>> 0) & 0x3FF);
inst.K1 = k[id1];
inst.K2 = k[id2];
break;
};
};
if (useImportConstants) {
let res = globals[K0];
if (count > 1) res = res?.[K1];
if (count > 2) res = res?.[K2];
inst.K = res;
/*
function resolveImportConstant(staticEnv, count, k0, k1, k2) {
let res = staticEnv[k0];
if (count < 2 || res == null) return res;
res = res[k1];
if (count < 3 || res == null) return res;
res = res[k2];
return res;
};
inst.K = resolveImportConstant(
globals,
count, inst.K0, inst.K1, inst.K2
);
*/
};
break;
};
case 5: { // AUX boolean low 1 bit
inst.K = (bit_extract(aux, 0, 1) === 1);
inst.KN = (bit_extract(aux, 31, 1) === 1);
break;
};
case 6: { // AUX number low 24 bits
inst.K = k[bit_extract(aux, 0, 24)];
inst.KN = (bit_extract(aux, 31, 1) === 1);
break;
};
case 7: inst.K = k[B]; break; // B
case 8: inst.K = (aux & 0xF); break; // AUX number low 16 bits
case 9: inst.K = { Index: k[D], Closure: null, Upvalues: null }; break; // CLOSURE
}
}
function readProto(bytecodeid) {
const maxstacksize = editor.readUint8();
const numparams = editor.readUint8();
const nups = editor.readUint8();
const isvararg = editor.readBoolean(); // editor.readUint8() !== 0;
if (luauVersion >= 4) {
editor.readUint8(); // flags
const typesize = editor.readVarInt32();
editor.move(typesize); // skip typesize bytes
}
const sizecode = editor.readVarInt32();
const codelist = []; // 0-based
let skipnext = false;
for (let i = 0; i < sizecode; i++) {
if (skipnext) {
skipnext = false;
continue;
};
skipnext = readInstruction(codelist);
}
// comebine codelist and debugcodelist init?
const debugcodelist = Array.from({ length: sizecode }, (_, i) =>
codelist[i].opcode
);
const sizek = editor.readVarInt32();
const klist = Array.from({ length: sizek }, () => {
const kt = editor.readUint8();
switch (kt) {
case 0: return undefined; // nil
case 1: return editor.readBoolean(); // boolean
case 2: return editor.readFloat64(true); // number (double?)
case 3: return stringList[editor.readVarInt32() - 1]; // string
case 4: return editor.readUint32(true); // import
case 5: return Array.from({ length: editor.readVarInt32() }, () => // table
editor.readVarInt32()
);
case 6: return editor.readVarInt32(); // Closure
case 7: {
// vectorSize
const x = editor.readFloat32(true), y = editor.readFloat32(true), z = editor.readFloat32(true), w = editor.readFloat32(true);
return engine.vectorConstructor(x, y, z, w); // await
};
default: throw new TypeError("Unknown constant type " + kt);
};
}); // 0-based
// 2nd pass to replace constant references in the instruction
for (let i = 0; i < codelist.length; i++) {
checkkmode(codelist[i], klist);
}
const sizep = editor.readVarInt32();
const protolist = Array.from({ length: sizep }, () =>
editor.readVarInt32() // bytecode stores proto refs as 0-based typically; previous code added +1 — now keep raw
); // 0-based
const linedefined = editor.readVarInt32();
const debugnameindex = editor.readVarInt32();
const debugname = debugnameindex !== 0 ? stringList[debugnameindex - 1] : "(??)";
// lineinfo
const lineinfoenabled = editor.readBoolean(); // editor.readUint8() !== 0;
let instructionlineinfo;
if (lineinfoenabled) {
const linegaplog2 = editor.readUint8();
const intervals = ((sizecode - 1) >>> linegaplog2) + 1;
let lastoffset = 0;
const lineinfo = Array.from({ length: sizecode }, () =>
(lastoffset += editor.readUint8())
);
let lastline = 0;
const abslineinfo = Array.from({ length: intervals }, () =>
(lastline += editor.readUint32(true)) >>> 0
);
instructionlineinfo = Array.from({ length: sizecode }, (_, i) => // base
abslineinfo[i >>> linegaplog2] + lineinfo[i]
);
}
// debuginfo optional
if (editor.readBoolean()) { // editor.readUint8() !== 0
const sizel = editor.readVarInt32();
for (let i = 0; i < sizel; i++) {
editor.readVarInt32();
editor.readVarInt32();
editor.readVarInt32();
editor.readUint8();
};
const sizeupvalues = editor.readVarInt32();
for (let i = 0; i < sizeupvalues; i++) {
editor.readVarInt32();
}
}
// Build prototype object (all 0-based)
return {
maxstacksize,
numparams,
nups,
isvararg,
linedefined,
debugname,
sizecode,
code: codelist, // 0-based
debugcode: debugcodelist, // 0-based
sizek,
k: klist, // 0-based
sizep,
protos: protolist, // 0-based proto indices
lineinfoenabled,
instructionlineinfo,
bytecodeid
};
}
// userdataRemapping (not used)
if (typesVersion === 3) {
while (editor.readBoolean()) editor.readVarInt32();
/*
let index = editor.readUint8();
while (index !== 0) {
editor.readVarInt32();
index = editor.readUint8();
}
*/
}
const protoCount = editor.readVarInt32();
const protoList = Array.from({ length: protoCount }, (_, i) =>
readProto(i)
); // 0-based
const mainProtoIndex = editor.readVarInt32();
const mainProto = protoList[mainProtoIndex];
if (editor.bytesLeft !== 0) {
throw new Error("deserializer cursor position mismatch");
}
mainProto.debugname = "(main)";
return {
stringList,
protoList,
mainProto,
typesVersion
};
};
newclosure(module, proto = module.mainProto, upvals = []) {
// async so that we can implement task.wait
const engine = this;
const { protoList } = module;
const { numparams, maxstacksize, protos, code, debugcode: debugopcodes, k: constants, instructionlineinfo } = proto; // debugname
const { globals, useImportConstants } = engine; // extensions
class LuauError extends Error {
constructor(message, pc) {
const lineNumber = instructionlineinfo?.[pc] ?? -1;
super(`${engine.name}:${lineNumber}: ${message}`);
this.name = 'LuauError';
this.lineNumber = lineNumber;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, LuauError);
}
}
};
return async function (...args) {
const varargs = []; // { len: 0, list: [] };
const stack = new Array(maxstacksize); // .fill(undefined), length: Math.min(numparams, args.length)
for (let i = 0; i < numparams && i < args.length; i++) {
stack[i] = args[i];
}
if (numparams < args.length) {
const start = numparams;
const len = args.length - numparams;
// varargs.len = len;
for (let i = 0; i < len; i++) varargs[i] = args[start + i]; // varargs.list[i]
}
const debugging = { pc: 0, name: "NONE" };
const open_upvalues = new Map();
const generalized_iterators = new WeakMap(); // Map
let handlingBreak = false;
let top = -1, pc = 0;
let inst, op;
function getUpvalues(nups, duplicate) {
const upvalues = Array.from({ length: nups }, () => {
const { A: upvalueType, B: upvalueIndex } = code[pc++]; // pseudo =
switch (upvalueType) {
case 0: { // value
const upvalue = { value: stack[upvalueIndex], index: "value" };
upvalue.store = upvalue;
return upvalue;
};
case 1: { // reference
if (!duplicate) {
return open_upvalues.get(upvalueIndex) ?? // prev
open_upvalues.set(upvalueIndex, {
index: upvalueIndex,
store: stack
}).get(upvalueIndex);
};
};
case 2: return upvals[upvalueIndex]; // upvalue
}
});
return upvalues;
}
while (true) { // alive
if (!handlingBreak) {
inst = code[pc];
if (!inst) throw new RangeError(`PC out of range: ${pc}`);
op = inst.opcode;
};
handlingBreak = false;
const { aux, opname, A, B, C, D, E, K, KC, K0, K1, K2, KN } = inst;
debugging.pc = pc;
debugging.top = top;
debugging.name = opname;
pc++;
switch (op) { // opname
case 0: break; // NOP
case 1: { // BREAK
pc -= 1;
op = debugopcodes[pc];
handlingBreak = true;
break;
}
case 2: stack[A] = undefined; break; // LOADNIL
case 3: { // LOADB
stack[A] = B === 1;
pc += C;
break;
}
case 4: stack[A] = D; break; // LOADN
case 5: stack[A] = K; break; // LOADK
case 6: stack[A] = stack[B]; break; // MOVE
case 7: { // GETGLOBAL
stack[A] = globals[K]; // extensions[K] ||
pc++;
break;
}
case 8: { // SETGLOBAL
globals[K] = stack[A];
pc++;
break;
}
case 9: { // GETUPVAL
const uv = upvals[B];
stack[A] = uv.store[uv.index];
break;
}
case 10: { // SETUPVAL
const uv = upvals[B];
uv.store[uv.index] = stack[A];
break;
}
case 11: { // CLOSEUPVALS
// convert any open upvalues with index >= inst.A into closed upvalues
for (const [idx, uv] of open_upvalues) {
if (uv.index >= A) {
uv.value = uv.store[uv.index];
uv.store = uv;
uv.index = "value";
open_upvalues.delete(idx);
}
};
break;
}
case 12: { // GETIMPORT
if (useImportConstants) {
stack[A] = K;
} else {
// count = KC
const importWrap = globals[K0]; // extensions[k0] ||
switch (KC) {
case 1: stack[A] = importWrap; break;
case 2: stack[A] = importWrap[K1]; break;
case 3: stack[A] = importWrap[K1][K2]; break;
};
};
pc++;
break;
}
case 13: stack[A] = stack[B][stack[C]]; break; // GETTABLE
case 14: stack[B][stack[C]] = stack[A]; break; // SETTABLE
case 15: { // GETTABLEKS
// index = K
stack[A] = stack[B][K];
pc++;
break;
}
case 16: { // SETTABLEKS
// index = K
stack[B][K] = stack[A];
pc++;
break;
}
/*
Luau table sequence is 1-based; incoming instruction used inst.C + 1 previously
keep the language semantics (table[1] maps to obj[1] in JS). We assume sequences stored as JS arrays
*/
case 17: stack[A] = stack[B][C]; break; // GETTABLEN
case 18: stack[B][C] = stack[A]; break; // SETTABLEN
case 19: { // NEWCLOSURE
// protos array holds indices into protoList; inst.D is the proto index
const newPrototype = protoList[protos[D]];
stack[A] = engine.newclosure(module, newPrototype, getUpvalues(newPrototype.nups, false));
break;
}
// (TODO, make NAMECALL and CALL work with func(self, ...args) and func.apply(self, args))
case 20: { // NAMECALL
const obj = stack[B];
const method = obj[K];
stack[A] = method;
stack[A + 1] = obj;
pc++;
break;
}
case 21: { // CALL
const parameterCount = (B === 0) ? (top - A) : (B - 1);
const func = stack[A];
if (typeof func !== "function") {
throw new LuauError(`attempt to call a ${getLuauType(func)} value`, pc);
}
const args = stack.slice(A + 1, A + 1 + parameterCount);
const ret_list = await func(...args);
// normalize return values
const retArr = Array.isArray(ret_list) ? ret_list : [ret_list];
let ret_num = retArr.length;
if (C === 0) {
top = A + ret_num - 1;
} else {
ret_num = C - 1;
}
for (let i = 0; i < ret_num; i++) {
stack[A + i] = retArr[i];
};
break;
};
case 22: { // RETURN
let nresults;
if ((B - 1) === LUA_MULTRET) {
nresults = top - A + 1;
} else {
nresults = B - 1;
};
return stack.slice(A, A + nresults);
};
case 23: pc += D; break; // JUMP
case 24: pc += D; break; // JUMPBACK
case 25: pc += (stack[A]) ? D : 0; break; // JUMPIF // if (stack[A]) pc += D;
case 26: pc += (!stack[A]) ? D : 0; break; // JUMPIFNOT // if (!stack[A]) pc += D;
case 27: pc += (stack[A] === stack[aux]) ? D : 1; break; // JUMPIFEQ
case 28: pc += (stack[A] <= stack[aux]) ? D : 1; break; // JUMPIFLE
case 29: pc += (stack[A] < stack[aux]) ? D : 1; break; // JUMPIFLT
case 30: pc += (stack[A] !== stack[aux]) ? D : 1; break; // JUMPIFNOTEQ
case 31: pc += (stack[A] > stack[aux]) ? D : 1; break; // JUMPIFNOTLE // <= 1 ? D
case 32: pc += (stack[A] >= stack[aux]) ? D : 1; break; // JUMPIFNOTLT // < 1 ? D
case 33: stack[A] = stack[B] + stack[C]; break; // ADD
case 34: stack[A] = stack[B] - stack[C]; break; // SUB
case 35: stack[A] = stack[B] * stack[C]; break; // MUL
case 36: stack[A] = stack[B] / stack[C]; break; // DIV
case 37: stack[A] = stack[B] % stack[C]; break; // MOD
case 38: stack[A] = stack[B] ** stack[C]; break; // POW
case 39: stack[A] = stack[B] + K; break; // ADDK
case 40: stack[A] = stack[B] - K; break; // SUBK
case 41: stack[A] = stack[B] * K; break; // MULK
case 42: stack[A] = stack[B] / K; break; // DIVK
case 43: stack[A] = stack[B] % K; break; // MODK
case 44: stack[A] = stack[B] ** K; break; // POWK
// || false
case 45: stack[A] = stack[B] && stack[C]; break; // AND // stack[B] ? stack[C] || false : false // stack[A] = value ? (stack[C] || false) : value;
case 46: stack[A] = stack[B] || stack[C]; break; // OR // value ? value : (stack[C] || false);
case 47: stack[A] = stack[B] && K; break; // ANDK
case 48: stack[A] = stack[B] || K; break; // ORK // value ? value : (inst.K || false);
/*
// fallback
let s = stack[B];
for (let i = B + 1; i <= C; i++) s += stack[i];
stack[A] = s;
*/
case 49: stack[A] = stack.slice(B, C + 1).join(""); break; // CONCAT
case 50: stack[A] = !stack[B]; break; // NOT
case 51: stack[A] = -stack[B]; break; // MINUS
case 52: stack[A] = stack[B].length; break; // LENGTH // ?.length
case 53: { // NEWTABLE
stack[A] = Object.create(null); // new Array(aux); (todo)
pc++;
break;
}
case 54: { // DUPTABLE
// template = K
const serialized = Object.create(null); // {}
for (const ID of Object.values(K)) { // id of K
serialized[constants[ID]] = undefined; // null
};
stack[A] = serialized;
break;
}
case 55: { // SETLIST
let count = C - 1;
if (count === LUA_MULTRET) { // (C - 1) === LUA_MULTRET
count = top - B + 1;
};
// table_move(stack, B, B + c - 1, inst.aux, stack[A])
const dest = stack[A];
for (let i = 0; i < count; i++) {
dest[aux + i] = stack[B + i];
};
pc++;
break;
}
case 56: { // FORNPREP
const limit = stack[A] = Number(stack[A]);
if (Number.isNaN(limit)) throw new LuauError("invalid 'for' limit (number expected)", pc);
const step = stack[A + 1] = Number(stack[A + 1]);
if (Number.isNaN(step)) throw new LuauError("invalid 'for' step (number expected)", pc);
const index = stack[A + 2] = Number(stack[A + 2]);
if (Number.isNaN(index)) throw new LuauError("invalid 'for' index (number expected)", pc);
if (step > 0) {
if (index > limit) pc += D;
} else if (limit > index) pc += D;
/*
if (Math.sign(step) * (index - limit) > 0) pc += D;
if (step > 0) {
if (!(index <= limit)) pc += inst.D;
} else {
if (!(limit <= index)) pc += inst.D;
}
*/
break;
}
case 57: { // FORNLOOP
const limit = stack[A];
const step = stack[A + 1];
const index = stack[A + 2] += step;
/*
const index = stack[A + 2] + step;
stack[A + 2] = index;
*/
if (step > 0) {
if (index <= limit) pc += D;
} else if (limit <= index) pc += D;
break;
}
case 58: { // FORGLOOP
// res = K
top = A + 6;
const it = stack[A];
if (typeof it === "function") { // luau_settings.generalizedIteration === false
// Traditional iterator
let vals = await it(stack[A + 1], stack[A + 2]);
// Normalize iterator return:
// - undefined/null -> loop termination
// - non-array value -> treat as single value
// - array -> multi-return as-is
if (vals == null) {
for (let i = 0; i < K; i++) stack[A + 3 + i] = undefined;
pc++; // exit loop
} else {
if (!Array.isArray(vals)) vals = [vals];
for (let i = 0; i < K; i++) {
stack[A + 3 + i] = vals[i];
}
if (stack[A + 3] != null) {
stack[A + 2] = stack[A + 3];
pc += D;
} else {
pc++;
};
};
} else {
const gen =
generalized_iterators.get(inst) ??
generalized_iterators.set(inst, (function* () {
if (typeof it === "object") {
for (const entry of Object.entries(it)) {
yield entry;
}
}
})()).get(inst); // default
if (!gen) throw new TypeError("invalid iterator for forgloop");
const { value, done } = await gen.next();
if (!done && value != null) {
let out = value;
if (!Array.isArray(out)) out = [out];
for (let i = 0; i < K; i++) stack[A + 3 + i] = out[i];
stack[A + 2] = stack[A + 3];
pc += D;
} else {
pc++; // exit loop
}
};
break;
}
case 59: { // FORGPREP_INEXT
if (typeof stack[A] !== "function") {
throw new TypeError(`attempt to iterate over a ${getLuauType(stack[A])} value`);
};
pc += D;
break;
}
case 60: pc++; break; // FASTCALL3
case 61: { // FORGPREP_NEXT
if (typeof stack[A] !== "function") {
throw new TypeError(`attempt to iterate over a ${getLuauType(stack[A])} value`);
};
pc += D;
break;
}
case 63: { // GETVARARGS
let count = B - 1;
if (count === LUA_MULTRET) { // (B - 1) === LUA_MULTRET
count = varargs.length; // varargs.len;
top = A + count - 1;
};
for (let i = 0; i < count; i++) {
stack[A + i] = varargs[i]; // varargs.list[i]
};
break;
}
case 64: { // DUPCLOSURE
const { Closure: originalClosure, Index: protoIndex, Upvalues: originalUpvalues } = K;
const newPrototype = protoList[protoIndex];
const { nups } = newPrototype;
const temporaryUpvalues = getUpvalues(nups, true);
let reused = false;
if (originalClosure && Array.isArray(originalUpvalues) && originalUpvalues.length === nups) {
let same = true;
for (let i = 0; i < nups; i++) {
const ou = originalUpvalues[i];
const tu = temporaryUpvalues[i];
const ov = (ou && ou.store) ? ou.store[ou.index] : undefined;
const tv = (tu && tu.store) ? tu.store[tu.index] : undefined;
if (ov !== tv) {
same = false;
break;
}
}
if (same) {
stack[A] = originalClosure;
reused = true;
}
}
if (!reused) {
K.Upvalues = temporaryUpvalues;
K.Closure = engine.newclosure(module, newPrototype, temporaryUpvalues);
stack[A] = K.Closure;
}
break;
}
case 65: break; // PREPVARARGS // no-op?
case 66: { // LOADKX
stack[A] = K;
pc++;
break;
}
case 67: pc += E; break; // JUMPX
/*
inst.E ||= 0;
inst.E++;
*/
case 69: inst.E = (E || 0) + 1; break; // COVERAGE
case 70: throw new Error("encountered unhandled CAPTURE"); // CAPTURE
case 71: stack[A] = K - stack[C]; break; // SUBRK
case 72: stack[A] = K / stack[C]; break; // DIVRK
case 73: break; // FASTCALL1
case 74: pc++; break; // FASTCALL2
case 75: pc++; break; // FASTCALL2K
case 76: { // FORGPREP // luau_settings.generalizedIteration
const iterator = stack[A];
let gen;
if (typeof iterator === "object") {
gen = await Object.entries(iterator)[Symbol.iterator]();
} else if (typeof iterator?.next === "function") { // assume it is a generator function
gen = iterator;
} else {
throw new TypeError("Unsupported generalized iterator: must be generator or iterable")
}
generalized_iterators.set(inst, gen);
pc += D;
break;
}
case 77: pc += ((stack[A] == null) !== KN) ? D : 1; break; // JUMPXEQKNIL
case 78: { // JUMPXEQKB
const ra = stack[A];
pc += ((typeof ra === "boolean" && ra === K) !== KN) ? D : 1;
break;
}
case 79: pc += ((stack[A] === K) !== KN) ? D : 1; break; // JUMPXEQKN
case 80: pc += ((stack[A] === K) !== KN) ? D : 1; break; // JUMPXEQKS
case 81: stack[A] = Math.floor(stack[B] / stack[C]); break; // IDIV
case 82: stack[A] = Math.floor(stack[B] / K); break; // IDIVK
default: throw new TypeError("Unsupported Opcode: " + opname + " op: " + op);
}
} // end while
// never reached
// close open upvalues gracefully
for (const [i, uv] of open_upvalues) {
uv.value = uv.store[uv.index];
uv.store = uv;
uv.index = "value";
open_upvalues.delete(i);
}
}
}
}
I can see this being a valuable resource for me. I’ve previously modified the Lua compiler to include a def reserved word (defined boolean to check if something is not nil but will evaluate to true even if the value is false) and allowing for quickly checking the key index inside of a table (print('hello' in {'chomp','lol','hello','silly'}) evaluates to 3). It was a long and very arduous task that likely would have been easier with a guide like this. Thanks for the in-depth bytecode instructions.
Thanks for feedback.
Godspeed with learning bytecode compiler ![]()
- ignoring everything I have posted in the past
- don’t care, people can choose what they do, even then, I have moved updates to GitHub, the binary isn’t packed, and even then now comes with symbols, anyone with three braincells can check the binary.
I don’t see the point of the post you made other than ragebaiting on a post that’s almost a week old lol
Literally gatekeeping lil bro
![]()
Bold of you to say he’s gatekeeping while he made the last 2 iterations of the tool open-source… lol!
I seriously don’t understand your excessive ego or your need to try and call everyone a gate keeper.
Even if I were to open source it, you’d not understand anything in it. It won’t help you at all. I already open sourced three iterations, all of which ended up helping the wrong group in general, so if your smartest argument is “gatekeeping” “
”, then stop responding to my posts, because you’re only making a ridicule of yourself by talking and not knowing to who you’re talking and what they’ve done on the past.
Cheers.
This post would be a lot more useful if it explained how the VM works in full, and not just rehash the Bytecode.h file.
The VM is both register and stack based, registers are enclosed within an executing function, while the stack is used to keep track of the register state of whoevers calling a function (aka, basically every computer).
Each bytecode file contains an anonymous variadic entry function, which the VM uses as an entry point for that script. This usually appears at the bottom of a bytecode blob (but not always).
The VM pushes back so many registers for the given prototype, defined as maxstacksize (silly name!!), then sets the program counter to 0 for that prototype.[1].
When the VM hits CALL or NAMECALL, everything in the allocated registers is put onto the stack, then the VM allocates new registers for the child closure[2]. When that closure returns, the register state is cleared, and the stack is popped to restore the previous stack state.
really incredible resource, we need more of this stuff, i hope you write more in the future ![]()
Thanks. Have been writing (for about ~2 days now) tutorial about animating and rigging for animating on roblox through blender cause turns out nobody made any rigging tutorials yet
and all have been using pre-made rigs which i think is kinds lazy.
So yeah more cool tutorials coming dw
althrough will be more rare since im developing own game rn.
OPTIMIZE OPTIMIZE OPTIMIZE OPTIMIZE ![]()
Yo Yarik W post, I actually learned a lot. Thank you for sharing knowledge with the community, and don’t take the harsh criticism to personally of people who are trying to make dev forum into a non beginner friendly enviorment like stack overflow ![]()
The emojis are also a nice touch, I find them helpfull to make the post less dull and souless.
Keep spreading and sharing your posts, they mad interesting to read.
Btw sorry for bumping
Thank you for reporting a spike in edgy metrics™. Your input has been logged for review. Additional data is welcome if available.
Hey, if you still don’t understand bytes, I made a post that hopefully fills that gap in knowledge:
Hey, good luck! Out of curiosity, I wanted to check out this post and see the great experience of our esteemed friend here. Because of the post, I notice the same pattern, and I don’t mean to be annoying, I’m not the only one who seems to share this opinion about Yarik. It’s nothing personal, but Dottik has a point: YOU CAN’T CALL ASSUMERISTS STUPID. My friend, your posts are often aggressive, attacking to gain a foothold. Again, the same pattern makes me realize why you’re so rude to me, Yarik. I really don’t understand why this guy is still allowed to make new posts with his messages. If Roblox is aware of his writing style and his tendency to argue and make offensive posts, perhaps they haven’t reported him yet… but I see that some people have noticed your aggressive way of inflating your ego so everyone thinks you’re the best OP and the best programmer. So, if you don’t have a friend, you really need to try to unite everyone so they can bring new things together.
Not to be rude, but… I don’t really like people who say one thing here but do another elsewhere. So what? Dude, I feel like you’re destroying yourself with your ego. I hope you realize it. You’re the first one to assume and accuse others of using AI, when here you say not to be accused of using AI.

