lowerIrPrototypeToLuaBytecodePrototype function
LuaBytecodePrototype
lowerIrPrototypeToLuaBytecodePrototype(
- LualikeIrPrototype prototype, {
- required String sourceName,
- bool isMainPrototype = false,
Lowers one finalized IR prototype to a Lua bytecode prototype.
Only remaps finalized IR instructions and metadata into bytecode fields. Do not add new analyses or heuristics here — put them in IR/SSA instead.
When isMainPrototype is true, LualikeIrPrototype.lineDefined is forced
to 0 regardless of IR source lines (required for correct main-chunk debug
behavior). The main IR prototype must also declare _ENV as upvalue 0 with
inStack=1 and index=0; lowering validates this closure shape rather than
inventing missing metadata.
Implementation
LuaBytecodePrototype lowerIrPrototypeToLuaBytecodePrototype(
LualikeIrPrototype prototype, {
required String sourceName,
bool isMainPrototype = false,
}) {
final debugInfo = prototype.debugInfo;
final tempBase = prototype.registerCount;
final loweredInstructions = _lowerInstructions(
prototype.instructions,
tempBase: tempBase,
);
final instructions = loweredInstructions.instructions;
final pcMap = loweredInstructions.pcMap;
final constants = List<LuaBytecodeConstant>.unmodifiable(
prototype.constants.map(_lowerConstant),
);
final loweredUpvalueNames = List<String?>.from(
debugInfo?.upvalueNames ?? const <String>[],
growable: true,
);
final loweredUpvalues = List<LuaBytecodeUpvalueDescriptor>.generate(
prototype.upvalueDescriptors.length,
(index) => _lowerUpvalueDescriptor(
prototype.upvalueDescriptors[index],
name: index < loweredUpvalueNames.length
? loweredUpvalueNames[index]
: null,
),
growable: true,
);
if (isMainPrototype) {
_validateRootEnvironmentUpvalue(prototype);
}
final upvalues = List<LuaBytecodeUpvalueDescriptor>.unmodifiable(
loweredUpvalues,
);
final children = List<LuaBytecodePrototype>.unmodifiable(
prototype.prototypes.map(
(child) => lowerIrPrototypeToLuaBytecodePrototype(
child,
sourceName: child.debugInfo?.absoluteSourcePath ?? sourceName,
),
),
);
final debugLines = _lowerDebugLines(
debugInfo?.lineInfo,
instructions.length,
pcMap,
);
// Keep register + remapped PCs. Serialize drops register; parse re-infers.
final locals = List<LuaBytecodeLocalVariableDebugInfo>.unmodifiable(
(debugInfo?.localNames ?? const <LocalDebugEntry>[]).map(
(entry) => LuaBytecodeLocalVariableDebugInfo(
name: entry.name,
startPc: _remapPc(entry.startPc, pcMap),
endPc: _remapPc(entry.endPc, pcMap),
register: entry.register,
),
),
);
final upvalueNames = List<String?>.unmodifiable(loweredUpvalueNames);
return LuaBytecodePrototype(
// Official Lua main chunks always report linedefined=0. The debug VM
// uses that to distinguish main from regular Lua functions (e.g. so
// debug.getlocal does not inject a synthetic "(vararg table)" entry).
lineDefined: isMainPrototype ? 0 : prototype.lineDefined,
lastLineDefined: prototype.lastLineDefined,
parameterCount: prototype.paramCount,
flags: _prototypeFlags(prototype, isMainPrototype: isMainPrototype),
// Reserve only scratch slots actually used by mechanical expansions.
// Do not scan raw ABC fields as registers: immediate and constant fields
// can contain values that look like register indices.
maxStackSize: math.max(
2,
prototype.registerCount + loweredInstructions.tempSlots,
),
code: List<LuaBytecodeInstructionWord>.unmodifiable(instructions),
constants: constants,
upvalues: upvalues,
prototypes: children,
source: debugInfo?.absoluteSourcePath ?? sourceName,
lineInfo: debugLines.lineInfo,
absoluteLineInfo: debugLines.absoluteLineInfo,
localVariables: locals,
upvalueNames: upvalueNames,
);
}