Skip to content

Commit

Permalink
Merge pull request #6 from zxrys/master
Browse files Browse the repository at this point in the history
[update] improve comments
  • Loading branch information
yang-zhongtian authored Jun 1, 2024
2 parents 5bd4b99 + 66b27b0 commit 4c6db79
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 18 deletions.
21 changes: 16 additions & 5 deletions packages/compiler/src/assembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default class BytecodeCompiler {
private readonly ir: Instruction[]
private readonly strings: string[]
bytecode: Uint8Array
lookUpTable: LookUpTable
lookUpTable: LookUpTable // used to store and check the address of the stubs

constructor(ir: Instruction[]) {
this.ir = ir
Expand All @@ -23,6 +23,12 @@ export default class BytecodeCompiler {
this.lookUpTable = {}
}

/**
* Convert a long number to a byte array with length 8
* used to store addresses in the bytecode
* @param long - the long number to convert
* @return the byte array
*/
private longToByteArray(long: number) {
const byteArray = []
for (let i = 0; i < 8; i++) {
Expand Down Expand Up @@ -60,6 +66,7 @@ export default class BytecodeCompiler {
case Header.FETCH_PARAMETER:
return [header, arg.value]
case Header.DYN_ADDR:
// Reserve space for filling in the actual address after complete compilation (done in compile() function)
return [header, arg.value, ...new Array<number>(7).fill(0)]
}
}
Expand All @@ -69,8 +76,12 @@ export default class BytecodeCompiler {
const opcode = instruction.opcode
if (opcode === undefined) throw 'UNHANDLED_OPCODE'

// If we have a dynamic address, we need to store the address in the lookup table
// it is the index of the instruction in the bytecode
if (opcode === Opcode.ADDR_STUB) {
if (instruction.args[0].type !== Header.DYN_ADDR) throw 'UNEXPECTED_HEADER'
// lookUpTable[stub.index] = bytes.length
// then, we know that the true address of the stub which can be used later
this.lookUpTable[instruction.args[0].value] = bytes.length
return
}
Expand All @@ -88,11 +99,11 @@ export default class BytecodeCompiler {
this.compileBlock(this.ir, bytes)
for (let index = 0; index < bytes.length; index++) {
if (bytes[index] === Header.DYN_ADDR) {
let addr = this.lookUpTable[bytes[index + 1]]
let addr = this.lookUpTable[bytes[index + 1]] // get the address from the lookup table
if (addr === undefined) throw 'DYNAMIC_ADDRESS_NOT_FOUND'
bytes[index] = Header.LOAD_NUMBER
bytes.splice(index + 1, 8, ...this.longToByteArray(addr))
index += 8
bytes[index] = Header.LOAD_NUMBER // load the address as a number
bytes.splice(index + 1, 8, ...this.longToByteArray(addr)) // replace the dynamic address with the actual address
index += 8 // skip the address
}
}

Expand Down
50 changes: 38 additions & 12 deletions packages/compiler/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@ import * as babel from '@babel/core'
import { parse } from '@babel/parser'
import { Header, Opcode } from './constant.js'

/**
* Header is used to identify the type of the argument
* @example LOAD_NUMBER, LOAD_STRING, FETCH_VARIABLE, FETCH_DEPENDENCY
*/
export interface InstructionArgument {
type: Header
value: any
}

/**
* Opcode is used to identify the type of the instruction
* @example ADD, SUB, MUL, DIV, MOD, NOT, POS, NEG, STORE, GET_PROPERTY, SET_PROPERTY
*/
export interface Instruction {
opcode: Opcode
args: InstructionArgument[]
}

/**
* Stub is a placeholder for a jump instruction
* @example CONDITION_ELSE, CONDITION_END, LOOP_START, LOOP_UPDATE, LOOP_END, FUNCTION_START
*/
interface Stub {
index: number,
index: number, // index in the instruction array
type: StubType
}

Expand Down Expand Up @@ -83,7 +95,7 @@ export default class Compiler {
dependenciesUnderWindow: string[]
ir: Instruction[]
private readonly stubStack: Stub[]
private stubCounter: number
private stubCounter: number // used to differentiate between stubs, not the index of 'stubStack'
private readonly functionTable: Map<string, Stub>
private oepStub?: Stub

Expand Down Expand Up @@ -974,6 +986,13 @@ export default class Compiler {
}

private translateForLoop(node: babel.types.ForStatement) {
// init => begin => test = true => body => update => begin
// ||
// false
// ||
// \/
// end

if (node.init) {
if (node.init.type === 'VariableDeclaration') {
this.buildIR([node.init])
Expand All @@ -989,6 +1008,7 @@ export default class Compiler {

this.appendStubInstruction(this.createAddrStubArgument(stub_begin))

// node.test != true => jump to stub_end
this.appendPushInstruction(this.translateExpression(node.test))
this.appendPushInstruction(this.createAddrStubArgument(stub_end))
this.appendJmpZeroInstruction()
Expand All @@ -1005,7 +1025,7 @@ export default class Compiler {

this.appendStubInstruction(this.createAddrStubArgument(stub_end))

this.stubStack.splice(-3)
this.stubStack.splice(-3) // remove the stub for this loop from the stack, prevent confusion
}

/**
Expand All @@ -1024,19 +1044,20 @@ export default class Compiler {
* @private
*/
private translateIfStatement(node: babel.types.IfStatement) {
this.appendPushInstruction(this.translateExpression(node.test))
this.appendPushInstruction(this.translateExpression(node.test)) // push result of 'test' onto the stack

if (!node.alternate) {
if (!node.alternate) { // if there is no 'else' block
const stub = this.makeStub(StubType.CONDITION_END)
this.appendPushInstruction(this.createAddrStubArgument(stub))
this.appendJmpZeroInstruction()
this.appendPushInstruction(this.createAddrStubArgument(stub)) // push the address of the end of the condition onto the stack
this.appendJmpZeroInstruction() // jump to the end of the condition if the test is false
// or else, build the consequent
if (node.consequent.type === 'BlockStatement') {
this.buildIR(node.consequent.body)
} else {
this.buildIR([node.consequent])
}
this.appendStubInstruction(this.createAddrStubArgument(stub))
} else {
this.appendStubInstruction(this.createAddrStubArgument(stub)) // append the address of the end of the condition
} else { // if there is an 'else' block
const stub_else = this.makeStub(StubType.CONDITION_ELSE)
const stub_end = this.makeStub(StubType.CONDITION_END)
this.appendPushInstruction(this.createAddrStubArgument(stub_else))
Expand All @@ -1045,7 +1066,7 @@ export default class Compiler {
this.buildIR(node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent])

this.appendPushInstruction(this.createAddrStubArgument(stub_end))
this.appendJmpInstruction()
this.appendJmpInstruction() // jump to the end of the condition if the test is false
this.appendStubInstruction(this.createAddrStubArgument(stub_else))

this.buildIR(node.alternate.type === 'BlockStatement' ? node.alternate.body : [node.alternate])
Expand Down Expand Up @@ -1118,7 +1139,7 @@ export default class Compiler {
this.functionTable.set(node.id.name, stub)
this.appendStubInstruction(this.createAddrStubArgument(stub))

this.context.push()
this.context.push() // push a new context for the function
node.params.forEach((param, index) => {
switch (param.type) {
case 'Identifier':
Expand Down Expand Up @@ -1146,6 +1167,11 @@ export default class Compiler {
this.context.pop()
}

/**
* Build Intermediate Representation
* @param statements List of statements
* @param isMain Is the main body
*/
private buildIR(statements: babel.types.Statement[], isMain = false) {
if (isMain) {
const target = this.context.incr()
Expand Down Expand Up @@ -1208,7 +1234,7 @@ export default class Compiler {
}
this.translateVariableAssignment(statement.expression.left, statement.expression.right)
break
case 'UpdateExpression':
case 'UpdateExpression': // ++i, i++, --i, i--
this.translateExpression(statement.expression)
// Pop the update result
this.appendPopInstruction(this.createUndefinedArgument())
Expand Down
8 changes: 7 additions & 1 deletion packages/compiler/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export const enum Header {
FETCH_PARAMETER,
LOAD_UNDEFINED,
LOAD_OBJECT,

// Dynamic address, used for jump
// if it is used in ADDR_STUB instruction: the address of the stub(differentiated by its index (value)) is determined
// by the index of the stub in the bytecode and stored in the lookup table
// if it is used in Other instruction: the address is determined by looking up the value in the lookup table
// if you can not completely understand this, please check file 'assembler.ts'
DYN_ADDR,
}

Expand All @@ -19,7 +25,7 @@ export const enum Opcode {
NOT,
POS,
NEG,
STORE,
STORE, // Store the value in the stack to the variable
GET_PROPERTY,
SET_PROPERTY,
EXISTS,
Expand Down

0 comments on commit 4c6db79

Please sign in to comment.