// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// TrueType bytecode interpreter (work-in-progress port).
///
/// This is being incrementally ported from `fontations/skrifa`:
/// `src/outline/glyf/hint/engine/*` (Apache-2.0 OR MIT).
///|
/// Maximum number of executed instructions in a single run.
const TT_MAX_RUN_INSTRUCTIONS : Int = 1_000_000
///|
/// Maximum number of bytes in a function/instruction definition.
///
/// This matches upstream's `u16::MAX` limit, and is only enforced in pedantic
/// mode.
const TT_MAX_DEFINITION_SIZE : Int = 0xFFFF
///|
/// Tracks budgets for loops to limit execution time.
///
/// Ported from `fontations/skrifa/src/outline/glyf/hint/engine/mod.rs`.
priv struct TtLoopBudget {
limit : Int
mut backward_jumps : Int
mut loop_calls : Int
}
///|
fn TtLoopBudget::TtLoopBudget(
cvt_len : Int,
point_count : Int?,
) -> TtLoopBudget {
// Compute limits for loop calls and backward jumps.
// See FreeType's ttinterp.c "neg_jump_counter_max/loopcall_counter_max".
let limit = match point_count {
Some(n) =>
// (point_count * 10).max(50) + (cvt_len / 10).max(50)
(n * 10).max(50) + (cvt_len / 10).max(50)
None =>
// 300 + 22 * cvt_len
300 + 22 * cvt_len
}
{ limit, backward_jumps: 0, loop_calls: 0 }
}
///|
fn TtLoopBudget::reset(self : TtLoopBudget) -> Unit {
self.backward_jumps = 0
self.loop_calls = 0
}
///|
fn TtLoopBudget::doing_backward_jump(
self : TtLoopBudget,
) -> Result[Unit, HintError] {
self.backward_jumps = self.backward_jumps + 1
if self.backward_jumps > self.limit {
Err(ExceededExecutionBudget)
} else {
Ok(())
}
}
///|
fn TtLoopBudget::doing_loop_call(
self : TtLoopBudget,
count : Int,
) -> Result[Unit, HintError] {
self.loop_calls = self.loop_calls + count
if self.loop_calls > self.limit {
Err(ExceededExecutionBudget)
} else {
Ok(())
}
}
///|
priv struct TtEngine {
program : TtProgramState
value_stack : TtValueStack
mut functions : TtDefinitionSet
mut instructions : TtDefinitionSet
graphics : TtGraphicsState
cvt : Array[Int]
storage : Array[Int]
loop_budget : TtLoopBudget
axis_count : Int
coords : ArrayView[@moon_skrifa.NormalizedCoord]
}
///|
fn TtEngine::TtEngine(
font_code : BytesView,
prep_code : BytesView,
glyph_code : BytesView,
retained : TtRetainedGraphicsState,
cvt : Array[Int],
storage : Array[Int],
point_count : Int?,
axis_count : Int,
coords : ArrayView[@moon_skrifa.NormalizedCoord],
max_stack : Int,
max_call_depth : Int,
max_function_defs : Int,
max_instruction_defs : Int,
) -> TtEngine {
let stack_buf = Array::make(max_stack, 0)
let graphics = TtGraphicsState::default()
graphics.retained = retained
graphics.update_projection_state()
let loop_budget = TtLoopBudget(cvt.length(), point_count)
{
program: TtProgramState(
font_code,
prep_code,
glyph_code,
Font,
max_call_depth,
),
value_stack: TtValueStack(stack_buf, true),
functions: TtDefinitionSet(max_function_defs),
instructions: TtDefinitionSet(max_instruction_defs),
graphics,
cvt,
storage,
loop_budget,
axis_count,
coords,
}
}
///|
fn TtEngine::reset(
self : TtEngine,
program : TtProgram,
is_pedantic : Bool,
) -> Unit {
self.program.reset(program)
self.value_stack.clear()
// Match fontations: stack under/overflow is only an error in pedantic mode.
self.value_stack.check = is_pedantic
self.graphics.reset()
self.graphics.is_pedantic = is_pedantic
self.loop_budget.reset()
match program {
Font => {
self.functions.reset()
self.instructions.reset()
}
ControlValue => self.graphics.backward_compatibility = false
Glyph => {
// Instruct control bit 1 says we reset retained graphics state to default.
if (self.graphics.retained.instruct_control & 2) != 0 {
self.graphics.reset_retained()
}
// Set backward compatibility mode.
if self.graphics.retained.target.preserve_linear_metrics() {
self.graphics.backward_compatibility = true
} else if self.graphics.retained.target.is_smooth() {
self.graphics.backward_compatibility = (
self.graphics.retained.instruct_control & 0x4
) ==
0
} else {
self.graphics.backward_compatibility = false
}
}
}
}
///|
fn TtEngine::run_program(
self : TtEngine,
program : TtProgram,
is_pedantic : Bool,
) -> Result[Unit, HintError] {
self.reset(program, is_pedantic)
self.run()
}
///|
fn TtEngine::run(self : TtEngine) -> Result[Unit, HintError] {
let mut count = 0
while true {
let ins0 = match self.program.decoder.decode() {
Err(e) => return Err(e)
Ok(None) =>
if self.program.call_stack.is_empty() {
break
} else {
// Implicit return at end-of-code.
match self.program.pop_frame() {
Err(e) => return Err(e)
Ok(_) => ()
}
continue
}
Ok(Some(ins)) => ins
}
match self.dispatch(ins0) {
Ok(_) => ()
Err(e) => {
if e is ValueStackUnderflow {
let tag = match self.program.current {
Font => "fpgm"
ControlValue => "prep"
Glyph => "glyf"
}
println(
"tt_hint underflow tag=" +
tag +
" pc=" +
ins0.pc.to_string() +
" op=0x" +
ins0.opcode.to_string(radix=16) +
" stack_len=" +
self.value_stack.length().to_string(),
)
}
return Err(e)
}
}
count = count + 1
if count > TT_MAX_RUN_INSTRUCTIONS {
return Err(ExceededExecutionBudget)
}
}
Ok(())
}
///|
fn TtEngine::op_unknown(
self : TtEngine,
op : Int,
pc : Int,
) -> Result[Unit, HintError] {
match self.instructions.get(op) {
None => Err(UnhandledOpcode(op, pc))
Some(d) =>
match d.slice() {
None => Err(InvalidDefinition(op))
Some(code) => {
let return_pc = self.program.decoder.pc
self.program.push_frame(code, 0, return_pc)
}
}
}
}
///|
fn TtEngine::dispatch(
self : TtEngine,
ins : TtInstruction,
) -> Result[Unit, HintError] {
let op = ins.opcode
// Push instructions.
if (op >= 0xB0 && op <= 0xBF) || op == 0x40 || op == 0x41 {
for v in ins.inline_operands.iter() {
match self.value_stack.push(v) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
return Ok(())
}
// MIRP/MDRP ranges (FreeType style).
if op >= 0xE0 {
return self.op_mirp(op)
}
if op >= 0xC0 && op <= 0xDF {
return self.op_mdrp(op)
}
match op {
// SVTCA/SPVTCA/SFVTCA
0x00 => self.op_svtca(op)
0x01 => self.op_svtca(op)
0x02 => self.op_svtca(op)
0x03 => self.op_svtca(op)
0x04 => self.op_svtca(op)
0x05 => self.op_svtca(op)
// SPVTL/SFVTL
0x06 => self.op_svtl(op)
0x07 => self.op_svtl(op)
0x08 => self.op_svtl(op)
0x09 => self.op_svtl(op)
// SPVFS/SFVFS/GPV/GFV
0x0A => self.op_spvfs()
0x0B => self.op_sfvfs()
0x0C => self.op_gpv()
0x0D => self.op_gfv()
// SFVTPV/ISECT
0x0E => self.op_sfvtpv()
0x0F => self.op_isect()
// SRP0/SRP1/SRP2
0x10 => self.op_srp0()
0x11 => self.op_srp1()
0x12 => self.op_srp2()
// SZP0/SZP1/SZP2/SZPS
0x13 => self.op_szp0()
0x14 => self.op_szp1()
0x15 => self.op_szp2()
0x16 => self.op_szps()
// SLOOP
0x17 => self.op_sloop()
// RTG/RTHG
0x18 => self.op_rtg()
0x19 => self.op_rthg()
// SMD
0x1A => self.op_smd()
// FDEF
0x2C => self.op_fdef(ins.pc)
// ENDF
0x2D => self.op_endf(ins.pc)
// CALL
0x2B => self.op_call(ins.pc)
// LOOPCALL
0x2A => self.op_loopcall(ins.pc)
// IDEF
0x89 => self.op_idef(ins.pc)
// SCVTCI
0x1D => self.op_scvtci()
// SSWCI/SSW
0x1E => self.op_sswci()
0x1F => self.op_ssw()
// DUP
0x20 => self.op_dup()
// POP
0x21 => {
match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(_) => ()
}
Ok(())
}
// SWAP
0x23 => self.op_swap()
// CLEAR
0x22 => {
self.value_stack.clear()
Ok(())
}
// DEPTH
0x24 => {
match self.value_stack.push(self.value_stack.length()) {
Err(e) => return Err(e)
Ok(_) => ()
}
Ok(())
}
// CINDEX
0x25 => self.op_cindex()
// MINDEX
0x26 => self.op_mindex()
// ALIGNPTS
0x27 => self.op_alignpts()
// UTP
0x29 => self.op_utp()
// MDAP
0x2E => self.op_mdap(op)
0x2F => self.op_mdap(op)
// IUP
0x30 => self.op_iup(op)
0x31 => self.op_iup(op)
// SHP
0x32 => self.op_shp(op)
0x33 => self.op_shp(op)
// SHC
0x34 => self.op_shc(op)
0x35 => self.op_shc(op)
// SHZ
0x36 => self.op_shz(op)
0x37 => self.op_shz(op)
// SHPIX
0x38 => self.op_shpix()
// IP
0x39 => self.op_ip()
// MSIRP
0x3A => self.op_msirp(op)
0x3B => self.op_msirp(op)
// ALIGNRP
0x3C => self.op_alignrp()
// RTDG
0x3D => self.op_rtdg()
// MIAP
0x3E => self.op_miap(op)
0x3F => self.op_miap(op)
// WS/RS/WCVTP/RCVT/GC/SCFS/MD
0x42 => self.op_ws()
0x43 => self.op_rs()
0x44 => self.op_wcvtp()
0x45 => self.op_rcvt()
0x46 => self.op_gc(op)
0x47 => self.op_gc(op)
0x48 => self.op_scfs()
0x49 => self.op_md(op)
0x4A => self.op_md(op)
// MPS
0x4C => self.op_mps()
// FLIPON/FLIPOFF
0x4D => self.op_flipon()
0x4E => self.op_flipoff()
// DEBUG
0x4F =>
match self.value_stack.pop() {
Err(e) => Err(e)
Ok(_) => Ok(())
}
// ROLL
0x8A => self.op_roll()
// ADD
0x60 => self.op_add()
// SUB
0x61 => self.op_sub()
// MUL
0x63 => self.op_mul()
// DIV
0x62 => self.op_div()
// NEG
0x65 => self.op_neg()
// ABS
0x64 => self.op_abs()
// FLOOR/CEILING/ROUND
0x66 => self.op_floor()
0x67 => self.op_ceiling()
0x68 => self.op_round()
0x69 => self.op_round()
0x6A => self.op_round()
0x6B => self.op_round()
0x6C => Ok(())
0x6D => Ok(())
0x6E => Ok(())
0x6F => Ok(())
// WCVTF
0x70 => self.op_wcvtf()
// DELTAP2/3
0x71 => self.op_deltap(op)
0x72 => self.op_deltap(op)
// DELTAC1/2/3
0x73 => self.op_deltac(op)
0x74 => self.op_deltac(op)
0x75 => self.op_deltac(op)
// SROUND/S45ROUND
0x76 => self.op_sround()
0x77 => self.op_s45round()
// ROFF/RUTG/RDTG
0x7A => self.op_roff()
0x7C => self.op_rutg()
0x7D => self.op_rdtg()
// SANGW/AA/FLIP*
0x7E => self.op_sangw()
0x7F => Ok(())
0x80 => self.op_flippt()
0x81 => self.op_fliprgon()
0x82 => self.op_fliprgoff()
// SCANCTRL/SDPVTL/GETINFO/MAX/MIN/SCANTYPE/INSTCTRL
0x85 => self.op_scanctrl()
0x86 => self.op_sdpvtl(op)
0x87 => self.op_sdpvtl(op)
0x88 => self.op_getinfo()
0x8B => self.op_max()
0x8C => self.op_min()
0x8D => self.op_scantype()
0x8E => self.op_instctrl()
0x91 => self.op_getvariation(ins.pc)
0x92 => self.op_getdata(ins.pc)
// IF
0x58 => self.op_if()
// ELSE
0x1B => self.op_else(ins.pc)
// EIF
0x59 => self.op_eif(ins.pc)
// JMPR
0x1C => self.op_jmpr(ins.pc)
// JROT
0x78 => self.op_jrot(ins.pc)
// JROF
0x79 => self.op_jrof(ins.pc)
// AND/OR/NOT + DELTAP1/SDB/SDS
0x5A => self.op_and()
0x5B => self.op_or()
0x5C => self.op_not()
0x5D => self.op_deltap(op)
0x5E => self.op_sdb()
0x5F => self.op_sds()
// MPPEM
0x4B => self.op_mppem()
// LT/LTEQ/GT/GTEQ/EQ/NEQ
0x50 => self.op_cmp_lt()
0x51 => self.op_cmp_lteq()
0x52 => self.op_cmp_gt()
0x53 => self.op_cmp_gteq()
0x54 => self.op_cmp_eq()
0x55 => self.op_cmp_neq()
0x56 => self.op_odd()
0x57 => self.op_even()
_ => self.op_unknown(op, ins.pc)
}
}
///|
fn TtEngine::op_mppem(self : TtEngine) -> Result[Unit, HintError] {
self.value_stack.push(self.graphics.retained.ppem)
}
///|
fn tt_bool_i32(v : Bool) -> Int {
if v {
1
} else {
0
}
}
///|
fn TtEngine::op_cmp_lt(self : TtEngine) -> Result[Unit, HintError] {
let e2 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let e1 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(tt_bool_i32(e1 < e2))
}
///|
fn TtEngine::op_cmp_lteq(self : TtEngine) -> Result[Unit, HintError] {
let e2 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let e1 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(tt_bool_i32(e1 <= e2))
}
///|
fn TtEngine::op_cmp_gt(self : TtEngine) -> Result[Unit, HintError] {
let e2 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let e1 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(tt_bool_i32(e1 > e2))
}
///|
fn TtEngine::op_cmp_gteq(self : TtEngine) -> Result[Unit, HintError] {
let e2 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let e1 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(tt_bool_i32(e1 >= e2))
}
///|
fn TtEngine::op_cmp_eq(self : TtEngine) -> Result[Unit, HintError] {
let e2 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let e1 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(tt_bool_i32(e1 == e2))
}
///|
fn TtEngine::op_cmp_neq(self : TtEngine) -> Result[Unit, HintError] {
let e2 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let e1 = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(tt_bool_i32(e1 != e2))
}
///|
fn TtEngine::op_dup(self : TtEngine) -> Result[Unit, HintError] {
match self.value_stack.peek(0) {
Ok(v) => self.value_stack.push(v)
Err(ValueStackUnderflow) =>
if self.value_stack.check {
Err(ValueStackUnderflow)
} else {
self.value_stack.push(0)
}
Err(e) => Err(e)
}
}
///|
fn TtEngine::op_swap(self : TtEngine) -> Result[Unit, HintError] {
let a = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let b = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
match self.value_stack.push(a) {
Err(e) => return Err(e)
Ok(_) => ()
}
self.value_stack.push(b)
}
///|
fn TtEngine::op_cindex(self : TtEngine) -> Result[Unit, HintError] {
// Match fontations/FreeType: use the top element as an index without popping.
// Index 0 is valid and is a no-op.
let top = self.value_stack.length()
if top <= 0 {
return Err(ValueStackUnderflow)
}
let top_ix = top - 1
let idx = self.value_stack.buf.at(top_ix)
if idx < 0 {
return Err(ValueStackUnderflow)
}
let element_ix = top_ix - idx
if element_ix < 0 {
return Err(ValueStackUnderflow)
}
self.value_stack.buf.set(top_ix, self.value_stack.buf.at(element_ix))
Ok(())
}
///|
fn TtEngine::op_mindex(self : TtEngine) -> Result[Unit, HintError] {
// Match fontations/FreeType: use the top element as an index without popping.
// Index 0 moves the top element to the new top, effectively dropping one slot.
let top = self.value_stack.length()
if top <= 0 {
return Err(ValueStackUnderflow)
}
let top_ix = top - 1
let idx = self.value_stack.buf.at(top_ix)
if idx < 0 {
return Err(ValueStackUnderflow)
}
let element_ix = top_ix - idx
if element_ix < 0 {
return Err(ValueStackUnderflow)
}
let new_top_ix = top_ix - 1
if new_top_ix < 0 {
return Err(ValueStackUnderflow)
}
let v = self.value_stack.buf.at(element_ix)
// Remove the element by shifting values above it down (including the index slot).
for i in element_ix..<(top - 1) {
self.value_stack.buf.set(i, self.value_stack.buf.at(i + 1))
}
self.value_stack.buf.set(new_top_ix, v)
self.value_stack.len = self.value_stack.len - 1
Ok(())
}
///|
fn TtEngine::op_roll(self : TtEngine) -> Result[Unit, HintError] {
let a = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let b = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let c = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
match self.value_stack.push(b) {
Err(e) => return Err(e)
Ok(_) => ()
}
match self.value_stack.push(a) {
Err(e) => return Err(e)
Ok(_) => ()
}
self.value_stack.push(c)
}
///|
fn TtEngine::op_add(self : TtEngine) -> Result[Unit, HintError] {
let b = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let a = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(a + b)
}
///|
fn TtEngine::op_sub(self : TtEngine) -> Result[Unit, HintError] {
let b = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let a = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(a - b)
}
///|
fn TtEngine::op_mul(self : TtEngine) -> Result[Unit, HintError] {
// 26.6 * 26.6 => 26.6 (matches fontations/FreeType).
let b = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let a = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(tt_hint_mul_div_16_16(a, b, 64))
}
///|
fn TtEngine::op_div(self : TtEngine) -> Result[Unit, HintError] {
let b = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let a = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
if b == 0 {
return Err(DivideByZero)
}
// 26.6 / 26.6 => 26.6 (truncates rather than rounds).
self.value_stack.push(tt_hint_mul_div_no_round(a, 64, b))
}
///|
fn TtEngine::op_neg(self : TtEngine) -> Result[Unit, HintError] {
let a = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(-a)
}
///|
fn TtEngine::op_abs(self : TtEngine) -> Result[Unit, HintError] {
let a = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
self.value_stack.push(a.abs())
}
// Control flow: skip-based IF/ELSE/EIF, aligned with fontations/FreeType.
///|
fn TtEngine::op_if(self : TtEngine) -> Result[Unit, HintError] {
let cond = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
if cond != 0 {
return Ok(())
}
// Condition is false: jump to next ELSE (same nesting) or EIF.
let mut nest_depth = 1
let mut done = false
while !done {
let ins = match self.program.decoder.decode() {
Err(e) => return Err(e)
Ok(None) => return Err(UnexpectedEndOfBytecode)
Ok(Some(i)) => i
}
match ins.opcode {
0x58 => nest_depth = nest_depth + 1 // IF
0x1B => done = nest_depth == 1 // ELSE
0x59 => {
nest_depth = nest_depth - 1 // EIF
done = nest_depth == 0
}
_ => ()
}
}
Ok(())
}
///|
fn TtEngine::op_else(self : TtEngine, _pc : Int) -> Result[Unit, HintError] {
// We executed the true-branch and hit ELSE: skip to matching EIF.
let mut nest_depth = 1
while nest_depth != 0 {
let ins = match self.program.decoder.decode() {
Err(e) => return Err(e)
Ok(None) => return Err(UnexpectedEndOfBytecode)
Ok(Some(i)) => i
}
match ins.opcode {
0x58 => nest_depth = nest_depth + 1 // IF
0x59 => nest_depth = nest_depth - 1 // EIF
_ => ()
}
}
Ok(())
}
///|
fn TtEngine::op_eif(_self : TtEngine, _pc : Int) -> Result[Unit, HintError] {
// No-op: handled by skip logic.
Ok(())
}
///|
fn TtEngine::op_jmpr(self : TtEngine, _pc : Int) -> Result[Unit, HintError] {
let off = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
// Offset is relative to the JMPR instruction, but decoder.pc currently
// points at the next instruction, so subtract one.
let jump = off - 1
if jump < 0 {
if jump == -1 {
// If the offset is -1, we'll loop in place forever.
return Err(InvalidJump)
}
match self.loop_budget.doing_backward_jump() {
Err(e) => return Err(e)
Ok(_) => ()
}
}
// Upstream uses wrapping pc arithmetic and does not bounds-check.
self.program.decoder.pc = self.program.decoder.pc + jump
Ok(())
}
///|
fn TtEngine::op_jrot(self : TtEngine, pc : Int) -> Result[Unit, HintError] {
pc |> ignore
let cond = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let off = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
if cond != 0 {
let jump = off - 1
if jump < 0 {
if jump == -1 {
return Err(InvalidJump)
}
match self.loop_budget.doing_backward_jump() {
Err(e) => return Err(e)
Ok(_) => ()
}
}
self.program.decoder.pc = self.program.decoder.pc + jump
Ok(())
} else {
Ok(())
}
}
///|
fn TtEngine::op_jrof(self : TtEngine, pc : Int) -> Result[Unit, HintError] {
pc |> ignore
let cond = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let off = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
if cond == 0 {
let jump = off - 1
if jump < 0 {
if jump == -1 {
return Err(InvalidJump)
}
match self.loop_budget.doing_backward_jump() {
Err(e) => return Err(e)
Ok(_) => ()
}
}
self.program.decoder.pc = self.program.decoder.pc + jump
Ok(())
} else {
Ok(())
}
}
// Definitions & calls
///|
fn tt_engine_find_endf(code : BytesView, start : Int) -> Result[Int, HintError] {
let dec = TtDecoder::{ code, pc: start }
while true {
let ins = match dec.decode() {
Err(e) => return Err(e)
Ok(None) => return Err(UnexpectedEndOfBytecode)
Ok(Some(i)) => i
}
if ins.opcode == 0x2C || ins.opcode == 0x89 {
return Err(NestedDefinition)
} else if ins.opcode == 0x2D {
// Return the end offset (pc after ENDF).
return Ok(dec.pc)
}
}
Err(UnexpectedEndOfBytecode)
}
///|
fn TtEngine::op_fdef(self : TtEngine, pc : Int) -> Result[Unit, HintError] {
if self.program.current is Glyph {
return Err(DefinitionInGlyphProgram)
}
let fn_id = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let start = self.program.decoder.pc
let end_pc = match tt_engine_find_endf(self.program.decoder.code, start) {
Err(e) => return Err(e)
Ok(v) => v
}
if self.graphics.is_pedantic && end_pc - start > TT_MAX_DEFINITION_SIZE {
return Err(DefinitionTooLarge)
}
// end_pc is pc after ENDF.
match
self.functions.set_definition(
fn_id,
self.program.decoder.code,
start,
end_pc,
) {
Err(e) => return Err(e)
Ok(_) => ()
}
// Skip body + ENDF opcode byte.
self.program.decoder.pc = end_pc
pc |> ignore
Ok(())
}
///|
fn TtEngine::op_idef(self : TtEngine, pc : Int) -> Result[Unit, HintError] {
if self.program.current is Glyph {
return Err(DefinitionInGlyphProgram)
}
let opcode = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let start = self.program.decoder.pc
let end_pc = match tt_engine_find_endf(self.program.decoder.code, start) {
Err(e) => return Err(e)
Ok(v) => v
}
if self.graphics.is_pedantic && end_pc - start > TT_MAX_DEFINITION_SIZE {
return Err(DefinitionTooLarge)
}
match
self.instructions.set_definition(
opcode,
self.program.decoder.code,
start,
end_pc,
) {
Err(e) => return Err(e)
Ok(_) => ()
}
self.program.decoder.pc = end_pc
pc |> ignore
Ok(())
}
///|
fn TtEngine::op_endf(self : TtEngine, pc : Int) -> Result[Unit, HintError] {
// Returning from a call: pop call frame.
pc |> ignore
self.program.pop_frame()
}
///|
fn TtEngine::op_call(self : TtEngine, _pc : Int) -> Result[Unit, HintError] {
let fn_id = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
match self.functions.get(fn_id) {
None => Err(InvalidDefinition(fn_id))
Some(d) =>
match d.slice() {
None => Err(InvalidDefinition(fn_id))
Some(code) => {
let return_pc = self.program.decoder.pc
self.program.push_frame(code, 0, return_pc)
}
}
}
}
///|
fn TtEngine::op_loopcall(self : TtEngine, _pc : Int) -> Result[Unit, HintError] {
// Stack order: f (top), count (next).
let fn_id = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
let count = match self.value_stack.pop() {
Err(e) => return Err(e)
Ok(v) => v
}
if count <= 0 {
return Ok(())
}
match self.loop_budget.doing_loop_call(count) {
Err(e) => return Err(e)
Ok(_) => ()
}
match self.functions.get(fn_id) {
None => Err(InvalidDefinition(fn_id))
Some(d) =>
match d.slice() {
None => Err(InvalidDefinition(fn_id))
Some(code) => {
let return_pc = self.program.decoder.pc
// Execute the function `count` times; subsequent iterations are
// resumed by the loopcall frame on return.
self.program.push_loopcall_frame(code, count - 1, 0, return_pc)
}
}
}
}