JSL Abstract Machine Specification¶
1. Overview¶
The JSL Abstract Machine is a stack-based virtual machine designed to execute JSL postfix bytecode. It provides a simple, resumable, and network-friendly execution model.
2. Machine Architecture¶
2.1 Components¶
The abstract machine consists of:
Where: - S (Stack): Value stack for operands and results - E (Environment): Variable bindings - C (Code): Array of instructions (postfix) - D (Dump): Saved states for function calls (future) - R (Resources): Resource budget and consumption
2.2 Value Domain¶
Value v ::= n (number)
| b (boolean)
| null (null)
| s (string)
| [v₁,...,vₙ] (list)
| {k:v,...} (dictionary)
| ⟨λ,p,b,E⟩ (closure)
2.3 Instruction Set¶
Instruction i ::= v (push value)
| x (push variable)
| op (binary operator)
| (op, n) (n-ary operator)
| MARK (stack marker)
| CALL n (function call)
| RET (return)
3. Operational Semantics¶
3.1 Basic Transitions¶
Value Push¶
Where R' = R with gas decremented by 1.Variable Lookup¶
Where R' = R with gas decremented by 2.Binary Operation¶
⊕ is a binary operator
─────────────────────────────────────
⟨v₂·v₁·S, E, ⊕·C, D, R⟩ → ⟨(v₁⊕v₂)·S, E, C, D, R'⟩
N-ary Operation¶
⊕ is an n-ary operator |S| ≥ n
─────────────────────────────────────────────
⟨vₙ·...·v₁·S, E, (⊕,n)·C, D, R⟩ → ⟨⊕(v₁,...,vₙ)·S, E, C, D, R'⟩
3.2 Control Flow (Future Extension)¶
Function Call¶
v = ⟨λ,p₁...pₙ,b,E'⟩ |S| ≥ n
─────────────────────────────────────────────
⟨vₙ·...·v₁·v·S, E, CALL n·C, D, R⟩ →
⟨[], E'[p₁↦v₁,...,pₙ↦vₙ], b, (S,E,C)·D, R'⟩
Return¶
3.3 Error States¶
Stack Underflow¶
|S| < n (⊕,n) requires n arguments
─────────────────────────────────────────
⟨S, E, (⊕,n)·C, D, R⟩ → ERROR: Stack underflow
Undefined Variable¶
Resource Exhaustion¶
4. Execution Algorithm¶
4.1 Basic Execution Loop¶
def execute(code, env, resources):
S = [] # Stack
C = code # Code pointer
E = env # Environment
R = resources # Resources
while C:
if R and R.gas <= 0:
return PAUSE(S, E, C, R)
instr = C[0]
C = C[1:]
if is_value(instr):
S = [instr] + S
R.gas -= 1
elif is_variable(instr):
if instr in E:
S = [E[instr]] + S
R.gas -= 2
else:
return ERROR(f"Undefined: {instr}")
elif is_binary_op(instr):
if len(S) < 2:
return ERROR("Stack underflow")
v2, v1 = S[0], S[1]
S = [apply_binary(instr, v1, v2)] + S[2:]
R.gas -= 3
elif is_nary_op(instr):
op, n = instr
if len(S) < n:
return ERROR("Stack underflow")
args = S[:n]
S = [apply_nary(op, args)] + S[n:]
R.gas -= (3 + n)
if len(S) == 1:
return S[0]
else:
return ERROR(f"Invalid final stack: {S}")
4.2 Resumable Execution¶
def execute_resumable(state_or_code, env=None):
if is_saved_state(state_or_code):
S, E, C, R = restore_state(state_or_code)
else:
S = []
E = env or {}
C = state_or_code
R = default_resources()
result = execute_step(S, E, C, R)
if is_pause(result):
return None, save_state(result)
else:
return result, None
5. Resource Model¶
5.1 Gas Costs¶
| Operation | Gas Cost | Rationale |
|---|---|---|
| Push literal | 1 | Minimal cost |
| Variable lookup | 2 | Environment search |
| Binary operation | 3 | Computation |
| N-ary operation | 3+n | Scales with arguments |
| Function call | 10 | Context switch |
| List creation | 1+n | Allocation |
| Dictionary creation | 1+2n | Key-value pairs |
5.2 Memory Limits¶
MemoryLimit = {
max_stack_depth: 10000,
max_collection_size: 1000000,
max_string_length: 1000000,
max_env_depth: 1000
}
5.3 Time Limits¶
6. Optimization Opportunities¶
6.1 Instruction Fusion¶
Combine common patterns:
6.2 Constant Folding¶
Pre-compute constant expressions:
6.3 Stack Caching¶
Keep top stack values in registers:
6.4 Environment Optimization¶
Cache frequently accessed variables:
7. Implementation Strategies¶
7.1 Direct Threading¶
typedef void (*instruction_fn)(Machine*);
instruction_fn dispatch_table[] = {
[OP_PUSH] = do_push,
[OP_ADD] = do_add,
[OP_MUL] = do_mul,
// ...
};
void execute(Machine* m) {
while (m->pc < m->code_len) {
dispatch_table[m->code[m->pc++]](m);
}
}
7.2 Computed Goto (GCC)¶
void* dispatch_table[] = {
&&do_push,
&&do_add,
&&do_mul,
// ...
};
#define DISPATCH() goto *dispatch_table[code[pc++]]
execute:
DISPATCH();
do_push:
stack[++sp] = code[pc++];
DISPATCH();
do_add:
sp--;
stack[sp] += stack[sp+1];
DISPATCH();
7.3 JIT Compilation¶
def jit_compile(postfix):
"""Compile postfix to native code."""
native_code = []
for instr in postfix:
if isinstance(instr, int):
native_code.append(f"PUSH_IMM {instr}")
elif instr == '+':
native_code.append("POP_ADD")
# ...
return assemble(native_code)
8. Debugging Support¶
8.1 Stack Trace¶
8.2 Breakpoints¶
breakpoints = {15, 27, 42} # Instruction addresses
def execute_debug(code, env):
for pc, instr in enumerate(code):
if pc in breakpoints:
debug_prompt(stack, env, code, pc)
execute_instruction(instr)
8.3 State Inspection¶
def inspect_state(machine):
return {
'stack': machine.stack,
'env': machine.env,
'pc': machine.pc,
'gas_used': machine.initial_gas - machine.gas,
'next_instruction': machine.code[machine.pc]
}
9. Comparison with Other VMs¶
| Feature | JSL VM | JVM | Python VM | Lua VM |
|---|---|---|---|---|
| Model | Stack | Stack | Stack | Register |
| Bytecode | JSON | Binary | Binary | Binary |
| Resumable | Yes | No | No | Yes (coroutines) |
| Serializable | Yes | No | Partial | No |
| Types | Dynamic | Static | Dynamic | Dynamic |
| GC | Host | Yes | Yes | Yes |
10. Performance Characteristics¶
10.1 Time Complexity¶
| Operation | Complexity |
|---|---|
| Push | O(1) |
| Pop | O(1) |
| Binary op | O(1) |
| Variable lookup | O(log n) average |
| List creation | O(n) |
| Function call | O(1) |
10.2 Space Complexity¶
| Structure | Complexity |
|---|---|
| Stack | O(n) expressions |
| Environment | O(m) variables |
| Code | O(k) instructions |
| Total | O(n + m + k) |
11. Security Considerations¶
11.1 Resource Isolation¶
Each execution has isolated resources:
11.2 Capability Security¶
No ambient authority - all effects through capabilities:
capabilities = {
'file_read': FileReadCapability('/allowed/path'),
'network': NetworkCapability(['allowed.host'])
}
11.3 Sandboxing¶
def sandbox_execute(untrusted_code):
sandbox = Sandbox(
max_stack=1000,
max_time=1.0,
no_host_access=True
)
return sandbox.execute(untrusted_code)
12. Example Execution Traces¶
12.1 Simple Arithmetic¶
Code: [2, 3, +, 4, *]
Step | Stack | Code | Action
-----|-------|-------------|--------
0 | [] | 2 3 + 4 * | Initial
1 | [2] | 3 + 4 * | Push 2
2 | [2,3] | + 4 * | Push 3
3 | [5] | 4 * | Add
4 | [5,4] | * | Push 4
5 | [20] | ε | Multiply
12.2 With Variables¶
Code: [x, y, +] with env {x: 10, y: 20}
Step | Stack | Code | Action
-----|----------|-------|----------
0 | [] | x y + | Initial
1 | [10] | y + | Lookup x
2 | [10,20] | + | Lookup y
3 | [30] | ε | Add
13. Future Directions¶
- Tail Call Optimization: Reuse stack frames
- Lazy Evaluation: Thunks and promises
- Parallel Execution: Multiple stacks
- Native Code Generation: LLVM backend
- Persistent Data Structures: Immutable collections
This specification defines the JSL Abstract Machine v1.0, providing a formal foundation for JSL execution.