Files
openharmony_ci 031d76da4d !114 ISA/ISAPI minor fixes
Merge pull request !114 from bolshov.maxim/master
2022-03-30 10:58:53 +00:00

3206 lines
101 KiB
YAML

# Copyright (c) 2021-2022 Huawei Device Co., Ltd.
# 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.
---
chapters:
- name: General design
text: >
VM is register based with dedicated accumulator register, which serves as an implicit operand to instructions.
- name: Registers
text: >
Registers are wide enough to hold a single reference when working with objects.
When used for primitive types, registers width should be considered as 64 bits.
When used for object types, registers should be considered wide enough to hold a reference to an object.
The scope of a register is function frame (also known as activation record). If instruction defines not all 64
bits of a register, undefined bits shall not be accessed in verified code.
Register field width in instruction encoding could be 4 (16 addressable registers), 8 (256 registers) or
16 (65536 registers) bits.
- name: Accumulator
text: >
Accumulator is a special register which is implicitly used by instructions as a source and/or destination operand.
The main goal of using accumulator is to improve encoding density without losing much in performance. Therefore,
the general intuition regarding accumulator usage is to utilize the accumulator as much as possible taking it as
a source from previous instruction result and passing it to the next instruction in its destination operand.
Moreover, when instruction has more than one source operands, the value which lives shorter should be passed
through the accumulator. If it is profitable however, variations of instructions that don't write into the
accumulator are also introduced. For example, moving arguments for `call.range` instruction may be done by
register-to-register moves.
- name: Calling sequence
text: >
On execution of a call bytecode a new function frame is created. All necessary arguments are copied from the
caller frame onto the top of the callee frame. For example, the last argument is placed in the register with the largest
index and the first argument is placed into the register with the index equal to the size of frame subtracted by
the number of arguments. Accumulator value is considered as undefined and shall not be read in verified bytecode.
On return, callee frame is destroyed. If function return value is non-void, it is passed to caller via
accumulator. Otherwise accumulator content in caller frame is considered as undefined and shall not
be read in verified bytecode.
- name: Supported primitive types
text: |
VM support operations on registers with i32 and i64 integral values. However, 8-bit and 16-bit integral values
can be loaded/stored into records and arrays with corresponding bytecodes. In that case, VM will extend/truncate
value to match storage size with i32. Similarly, passing an 8-bit or 16-bit value to a function can be emulated by
passing a value, which is zero or sign-extended to i32.
VM support operations on registers with f32 and f64 values, which corresponds to IEEE-754 single and double precision
floating-point represenation.
Primitive data type of a register is not tracked by VM and is interpreted by separate bytecodes.
Integral values are not inherently signed or unsigned, signedness is interpreted by bytecodes as well.
If bytecode treats register value as signed integer, it uses two's complement representation.
To denote that bytecode treats register values as unsigned integer, u32/u64 notation is used.
For moves, loads and stores it is not always possible to denote a type of result, because it depends on type
of source object. In that case, bNN notation is used, where NN is bit size of result. Therefore, for example,
b64 is a union of f64 and i64.
### Floating-point literals
Decimal floating-point literals can have the following parts:
- Sign ("`+`" or "`-`")
- Whole number part
- Decimal point
- Fractional part
- Exponent indicator ("`e`")
- Exponent sign
- Exponent
Decimal floating-point literals must have at least one digit and either decimal point or exponent part.
Special values:
- Positive zero (+0.0, hexadecimal representation is `0x0000000000000000`)
- Negative zero (-0.0, hexadecimal representation is `0x8000000000000000`)
- Minimal positive value (4.9E-324, hexadecimal representation is `0x0000000000000001`)
- Maximal negative value (-4.9E-324, hexadecimal representation is `0x8000000000000001`)
- Maximal positive value (1.7976931348623157e308, hexadecimal representation is `0x7fefffffffffffff`)
- Minimal negative value (-1.7976931348623157e308, hexadecimal representation is `0xffefffffffffffff`)
- Positive infinity (hexadecimal representation is `0x7ff0000000000000`)
- Negative infinity (hexadecimal representation is `0xfff0000000000000`)
- Not a number - set of all NaN values (one of hexadecimal representations is `0x7ff8000000000000`)
- name: Language-dependent types
text: >
Panda VM supports type hierarchies according to the language it executes. That way, creation (or loading
from constant pool) of strings, arrays, exception objects results into an object of type native to language,
including inheritance relations.
- name: Dynamically-typed languages support
text: >
Panda VM supports languages with dynamic types. It represents dynamic values through special 'any' values,
which wraps a value itself (both primitive and objects) and corresponding type info. VM tracks type of registers
that hold 'any' value, whether they are primitive or not. Virtual registers and accumulator are wide enough
to hold 'any' value. When VM executes code inside dynamically-typed language context, regular static instructions
also may be used.
# File format and ISA versioning
min_version: 0.0.0.2
version: 0.0.0.2
properties:
- tag: acc_none
description: Doesn't use accumulator register.
- tag: acc_read
description: Use accumulator as a first source operand.
- tag: acc_write
description: Use accumulator as a destination operand.
- tag: type_id
description: Use an id which resolves into a type constant.
- tag: method_id
description: Use an id which resolves into a method constant.
- tag: string_id
description: Use an id which resolves into a string constant.
- tag: literalarray_id
description: Use an id which resolves into a constant literalarray.
- tag: field_id
description: Use an id which resolves into a field reference.
- tag: call
description: Pass control to the callee method.
- tag: call_virt
description: Pass control to the callee method via virtual call.
- tag: return
description: Pass control to the caller method.
- tag: jump
description: Pass control to another bytecode in a current method.
- tag: conditional
description: Operate based on computed condition, otherwise is no operation.
- tag: float
description: Perform floating point operation.
- tag: dynamic
description: Operate on 'any' values.
- tag: maybe_dynamic
description: May operate on 'any' values depending on language context.
- tag: language_type
description: Create objects of type depending on language context.
exceptions:
- tag: x_none
description: Bytecode doesn't throw exceptions.
- tag: x_null
description: Bytecode throws NullPointerException in case of null reference as a source.
- tag: x_bounds
description: Bytecode throws ArrayIndexOutOfBoundsException if index is out of bounds of an array.
- tag: x_negsize
description: Bytecode throws NegativeArraySizeException if index is less than zero.
- tag: x_store
description: Bytecode throws ArrayStoreException if element isn't instance of array's element type.
- tag: x_abstract
description: Bytecode throws AbstractMethodError if resolved method has no implementation.
- tag: x_arith
description: Bytecode throws ArithmeticException if the divisor is 0.
- tag: x_cast
description: Bytecode throws ClassCastException if type cast failed.
- tag: x_classdef
description: Bytecode throws NoClassDefFoundError if type cast failed.
- tag: x_oom
description: Bytecode throws OutOfMemoryError if failed to allocate object.
- tag: x_init
description: Bytecode throws ExceptionInInitializerError if unexpected exception occurred in a static initializer.
verification:
- tag: none
description: Instruction is always valid.
- tag: v1_array
description: First operand contains a reference to an array.
- tag: v1_object
description: First operand contains a reference to an object (other than array).
- tag: v1_array_type
description: First operand contains a reference to an array of elements of type corresponding to bytecode.
- tag: v1_i32
description: First operand contains a value of i32 type.
- tag: v1_type
description: First operand contains a value of type corresponding to bytecode.
- tag: v1_obj_or_null
description: First operand contains a reference to an object or null.
- tag: v2_i32
description: Second operand contains a value of i32 type.
- tag: v2_object
description: Second operand contains a reference to an object (other than array).
- tag: v2_type
description: Second operand contains a value of type corresponding to bytecode.
- tag: acc_i32
description: Accumulator contains a value of i32 type.
- tag: acc_type
description: Accumulator contains a value of type corresponding to bytecode.
- tag: acc_return_type
description: Accumulator type is compatible with method return type.
- tag: v1_throw_type
description: First operand contains a reference to an instance of class Throwable or of a subclass of Throwable.
- tag: acc_obj_or_null
description: Accumulator contains a reference to an object or null.
- tag: type_id_array
description: Type_id operand must correspond to an array type.
- tag: type_id_object
description: Type_id operand must correspond to an object type (other than array).
- tag: type_id_any_object
description: Type_id operand must correspond to any object type.
- tag: method_id_static
description: Method_id must resolve to a static method.
- tag: method_id_non_static
description: Method_id must resolve to a non-static method.
- tag: method_id_non_abstract
description: Method_id must resolve to a method that has implementation.
- tag: method_id_accessible
description: Method_id must resolve to a method which is accessible.
- tag: constant_string_id
description: Id must resolve into a constant-pool string.
- tag: constant_literalarray_id
description: Id must resolve into a constant literalarray.
- tag: compatible_arguments
description: Arguments provided to a method must be of compatible types.
- tag: method_init_obj
description: Method_id must resolve into initializer for a type other than one-dimensional array.
- tag: branch_target
description: Branch target should point to a beginning of an instruction of the same method.
- tag: field_id_non_static
description: Field_id must resolve to a non-static object field.
- tag: field_id_static
description: Field_id must resolve to a static field.
- tag: field_id_size
description: Field_id must resolve to a field of size corresponding to bytecode.
- tag: valid_in_dynamic_context
description: Instruction is valid only for dynamically-typed language context.
prefixes:
- name: bit
description: Bit operations.
opcode_idx: 0xef
- name: unsigned
description: Unsigned operations.
opcode_idx: 0xee
- name: cast
description: Cast operations.
opcode_idx: 0xed
- name: f32
description: IEEE-754 single precision floating-point operations.
opcode_idx: 0xec
groups:
- title: No operation
description: Perform an operation without behavior.
properties:
- acc_none
exceptions:
- x_none
verification:
- none
pseudo: |
skip
semantics: |-
skip
instructions:
- sig: nop
acc: none
format: [op_none]
opcode_idx: [0x0]
- title: Move register-to-register
description: Move values between registers.
properties:
- acc_none
exceptions:
- x_none
verification:
- v2_type
pseudo: |
vd = vs
semantics: |-
raise exception(WrongRegisterValue) if true that Op == "mov" and type_of(@Vs) is not i32;
raise exception(WrongRegisterValue) if true that Op == "mov.64" and (type_of(@Vs) is not u64 and type_of(@Vs) is not i64);
raise exception(WrongRegisterValue) if true that Op == "mov.obj" and (type_of(@Vs) is not object and type_of(@Vs) is not array);
raise exception(UndefinedSemantics) if true that Op not in [[ "mov", "mov.64", "mov.obj" ]];
Vd <~ Vs
instructions:
- sig: mov v1:out:b32, v2:in:b32
acc: none
format: [op_v1_4_v2_4, op_v1_8_v2_8, op_v1_16_v2_16]
opcode_idx: [0x1, 0x2, 0x3]
- sig: mov.64 v1:out:b64, v2:in:b64
acc: none
format: [op_v1_4_v2_4, op_v1_16_v2_16]
opcode_idx: [0x4, 0x5]
- sig: mov.obj v1:out:ref, v2:in:ref
acc: none
format: [op_v1_4_v2_4, op_v1_8_v2_8, op_v1_16_v2_16]
opcode_idx: [0x6, 0x7, 0x8]
- title: Move immediate-to-register
description: >
Move integer immediate into a register. For short formats immediate is sign extended to operand size.
properties:
- acc_none
exceptions:
- x_none
verification:
- none
pseudo: >
vd = imm
semantics: |-
cases
when Op == "movi" do
raise exception(WrongImmediateValue) if undefined int_of(Imm);
Vd <~ value_of(signed(32), int_of(Imm))
end when;
when Op == "movi.64" do
raise exception(WrongImmediateValue) if undefined int_of(Imm);
Vd <~ value_of(signed(64), int_of(Imm))
end when;
when Op == "fmovi" do
raise exception(WrongImmediateValue) if undefined float_of(Imm);
Vd <~ value_of(float, float_of(Imm))
end when;
when Op == "fmovi.64" do
raise exception(WrongImmediateValue) if undefined float_of(Imm);
Vd <~ value_of(float, float_of(Imm))
end when
else raise exception(UndefinedSemantics) // missed opcode
undefined raise exception(InternalError) // Op is not string
end cases
instructions:
- sig: movi v:out:i32, imm:i32
acc: none
format: [op_v_4_imm_4, op_v_8_imm_8, op_v_8_imm_16]
opcode_idx: [0x9, 0xa, 0xb]
- sig: movi v:out:i32, imm:i32
acc: none
format: [op_v_8_imm_32]
opcode_idx: [0xc]
- sig: movi.64 v:out:i64, imm:i64
acc: none
format: [op_v_8_imm_64]
opcode_idx: [0xd]
- sig: fmovi v:out:f32, imm:f32
acc: none
prefix: f32
format: [pref_op_v_8_imm_32]
opcode_idx: [0x0]
properties: [acc_none, float]
- sig: fmovi.64 v:out:f64, imm:f64
acc: none
format: [op_v_8_imm_64]
opcode_idx: [0xe]
properties: [acc_none, float]
- title: Move null reference into register
description: Move null reference into a register.
properties:
- acc_none
exceptions:
- x_none
verification:
- none
pseudo: >
vd = null
semantics: |-
skip
instructions:
- sig: mov.null v:out:ref
acc: none
format: [op_v_8]
opcode_idx: [0xf]
- title: Load accumulator from register
description: Moves register content into an accumulator.
properties:
- acc_write
exceptions:
- x_none
verification:
- v1_type
pseudo: |
acc = vs
semantics: |-
cases
when Op == "lda" do
raise exception(WrongRegisterValue) if false that type_of(@Vs) is i32;
acc <~ Vs
end when;
when Op == "lda.64" do
raise exception(WrongRegisterValue) if false that type_of(@Vs) is i64 or type_of(@Vs) is u64;
acc <~ Vs
end when;
when Op == "lda.obj" do
raise exception(WrongRegisterValue) if false that type_of(@Vs) is object or type_of(@Vs) is array;
acc <~ Vs
end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: lda v:in:i32
acc: out:i32
format: [op_v_8]
opcode_idx: [0x10]
- sig: lda.64 v:in:b64
acc: out:b64
format: [op_v_8]
opcode_idx: [0x11]
- sig: lda.obj v:in:ref
acc: out:ref
format: [op_v_8]
opcode_idx: [0x12]
- title: Load accumulator from immediate
description: >
Load immediate into accumulator. For short formats immediate is sign extended to operand size.
properties:
- acc_write
exceptions:
- x_none
verification:
- none
pseudo: |
acc = imm
semantics: |-
cases
when Op == "ldai" do
raise exception(WrongImmediateValue) if undefined int_of(Imm);
acc <~ value_of(i32, int_of(Imm))
end when;
when Op == "ldai.64" do
raise exception(WrongImmediateValue) if undefined int_of(Imm);
acc <~ value_of(i64, int_of(Imm))
end when;
when Op == "fldai" do
raise exception(WrongImmediateValue) if undefined float_of(Imm);
acc <~ value_of(float, float_of(Imm))
end when
when Op == "fldai.64" do
raise exception(WrongImmediateValue) if undefined float_of(Imm);
acc <~ value_of(float, float_of(Imm))
end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: ldai imm:i32
acc: out:i32
format: [op_imm_8, op_imm_16]
opcode_idx: [0x13, 0x14]
- sig: ldai imm:i32
acc: out:i32
format: [op_imm_32]
opcode_idx: [0x15]
- sig: ldai.64 imm:i64
acc: out:i64
format: [op_imm_64]
opcode_idx: [0x16]
- sig: fldai imm:f32
acc: out:f32
prefix: f32
format: [pref_op_imm_32]
opcode_idx: [0x1]
properties: [acc_write, float]
- sig: fldai.64 imm:f64
acc: out:f64
format: [op_imm_64]
opcode_idx: [0x17]
properties: [acc_write, float]
- title: Load accumulator from string constant pool
description: >
Load string specified by id into accumulator. In dynamically-typed language context
load string as 'any' value.
properties:
- acc_write
- string_id
- language_type
- maybe_dynamic
exceptions:
- x_none
verification:
- constant_string_id
pseudo: |
acc = load(id)
semantics: |-
acc <~ value_of(string, strOf(StringId))
instructions:
- sig: lda.str string_id
acc: out:ref
format: [op_id_32]
opcode_idx: [0x18]
- title: Create and initialize new constant array
description: >
Create a new single-dimensional constant literal array and put a reference to it into register.
properties:
- acc_none
- literalarray_id
exceptions:
- x_none
verification:
- constant_literalarray_id
pseudo: |
v = load(id)
semantics: |-
skip
instructions:
- sig: lda.const v:out:ref, literalarray_id
acc: none
format: [op_v_8_id_32]
opcode_idx: [0x19]
- title: Load accumulator from type constant pool
description: Load type specified by id into accumulator.
properties:
- acc_write
- type_id
- language_type
exceptions:
- x_none
verification:
- type_id_any_object
pseudo: |
acc = load(id)
semantics: |-
skip
instructions:
- sig: lda.type type_id
acc: out:ref
format: [op_id_16]
opcode_idx: [0x1a]
- title: Load null reference into accumulator
description: Load null reference into accumulator.
properties:
- acc_write
exceptions:
- x_none
verification:
- none
pseudo: |
acc = null
semantics: |-
skip
instructions:
- sig: lda.null
acc: out:ref
format: [op_none]
opcode_idx: [0x1b]
- title: Store accumulator
description: Moves accumulator content into a register.
properties:
- acc_read
exceptions:
- x_none
verification:
- acc_type
pseudo: |
vd = acc
semantics: |-
raise exception(WrongRegisterValue) if true that Op == "sta" and type_of(@acc) is not i32;
raise exception(WrongRegisterValue) if true that Op == "sta.64" and (type_of(@acc) is not i64 and type_of(@acc) is not u64);
raise exception(WrongRegisterValue) if true that Op == "sta.obj" and (type_of(@acc) is not object and type_of(@acc) is not array);
raise exception(UndefinedSemantics) if true that Op not in [[ "sta", "sta.64", "sta.obj" ]];
// raise exception(InternalError) if true that Op is not string // ambig. mixing TypeExp and Exp here... subsort TypeExp < Exp ?
Vd <~ acc
instructions:
- sig: sta v:out:i32
acc: in:i32
format: [op_v_8]
opcode_idx: [0x1c]
- sig: sta.64 v:out:b64
acc: in:b64
format: [op_v_8]
opcode_idx: [0x1d]
- sig: sta.obj v:out:ref
acc: in:ref
format: [op_v_8]
opcode_idx: [0x1e]
- title: Integer comparison
description: Perform specified signed or unsigned integer comparison between register and accumulator.
properties:
- acc_read
- acc_write
exceptions:
- x_none
verification:
- acc_type
- v1_type
pseudo: |
if acc < vs then
acc = -1
else if acc == vs then
acc = 0
else
acc = 1
end
semantics: |-
raise exception(WrongRegisterValue) if false that type_of(@acc) is type_of(@Vs);
raise exception(WrongRegisterValue) if true that Op == "cmp.64" and type_of(@acc) is not i64;
raise exception(WrongRegisterValue) if true that Op == "ucmp" and type_of(@acc) is not u32;
raise exception(WrongRegisterValue) if true that Op == "ucmp.64" and type_of(@acc) is not u64;
raise exception(UndefinedSemantics) if true that Op not in [[ "cmp.64", "ucmp", "ucmp.64" ]];
cases
when @acc < @Vs do acc <~ value(i32, -1) end when;
when @acc == @Vs do acc <~ value(i32, 0) end when;
when @acc > @Vs do acc <~ value(i32, 1) end when
undefined raise exception(InternalError)
end cases
instructions:
- sig: cmp.64 v:in:i64
acc: inout:i64->i32
format: [op_v_8]
opcode_idx: [0x1f]
- sig: ucmp v:in:u32
acc: inout:u32->i32
prefix: unsigned
format: [pref_op_v_8]
opcode_idx: [0x0]
- sig: ucmp.64 v:in:u64
acc: inout:u64->i32
prefix: unsigned
format: [pref_op_v_8]
opcode_idx: [0x1]
- title: Floating-point comparison
description: Perform specified floating point comparison between register and accumulator.
properties:
- acc_read
- acc_write
- float
exceptions:
- x_none
verification:
- acc_type
- v1_type
pseudo: |
if isnan(acc) or isnan(vs) then
switch (op)
case fcmpg:
case fcmpg.64:
acc = 1
case fcmpl:
case fcmpl.64:
acc = -1
else
if acc < vs then
acc = -1
else if acc == vs then
acc = 0
else
acc = 1
end
end
semantics: |-
// strange semantics...
if isnan(@acc) or isnan(@Vs) then
cases
when Op in [[ "fcmpg", "fcmpg.64" ]] do acc <~ value(i32, 1) end when;
when Op in [[ "fcmpl", "fcmpl.64" ]] do acc <~ value(i32, -1) end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
else
cases
when @acc < @Vs do acc <~ value(i32, -1) end when;
when @acc == @Vs do acc <~ value(i32, 0) end when;
when @acc > @Vs do acc <~ value(i32, 1) end when
undefined raise exception(InternalError)
end cases
undefined raise exception(WrongRegisterValue)
end if
instructions:
- sig: fcmpl v:in:f32
acc: inout:f32->i32
prefix: f32
format: [pref_op_v_8]
opcode_idx: [0x2]
- sig: fcmpl.64 v:in:f64
acc: inout:f64->i32
format: [op_v_8]
opcode_idx: [0x20]
- sig: fcmpg v:in:f32
acc: inout:f32->i32
prefix: f32
format: [pref_op_v_8]
opcode_idx: [0x3]
- sig: fcmpg.64 v:in:f64
acc: inout:f64->i32
format: [op_v_8]
opcode_idx: [0x21]
- title: Unconditional jump
description: >
Unconditionally transfer execution to an instruction at offset bytes from the beginning of the current
instruction. Offset is sign extended to the size of instruction address.
properties:
- acc_none
- jump
exceptions:
- x_none
verification:
- branch_target
pseudo: |
pc += imm
semantics: |-
perform_jump(LabelId)
instructions:
- sig: jmp imm:i32
acc: none
format: [op_imm_8, op_imm_16, op_imm_32]
opcode_idx: [0x22, 0x23, 0x24]
- title: Conditional object comparison jump
description: >
Transfer execution to an instruction at offset bytes from the beginning of the current instruction if
object references in accumulator and source register compare as specified. Offset is sign extended to the size of
instruction address.
properties:
- acc_read
- jump
- conditional
exceptions:
- x_none
verification:
- branch_target
- acc_obj_or_null
- v1_obj_or_null
pseudo: |
switch (cc)
case eq:
if acc == vs then
pc += imm
end
case ne:
if acc != vs then
pc += imm
end
semantics:
skip
instructions:
- sig: jeq.obj v:in:ref, imm:i32
acc: in:ref
format: [op_v_8_imm_8, op_v_8_imm_16]
opcode_idx: [0x25, 0x26]
- sig: jne.obj v:in:ref, imm:i32
acc: in:ref
format: [op_v_8_imm_8, op_v_8_imm_16]
opcode_idx: [0x27, 0x28]
- title: Conditional compared to null jump
description: >
Transfer execution to an instruction at offset bytes from the beginning of the current instruction if
object reference in accumulator compares with null as specified. Offset is sign extended to the size of
instruction address.
properties:
- acc_read
- jump
- conditional
exceptions:
- x_none
verification:
- branch_target
- acc_obj_or_null
pseudo: |
switch (cc)
case eq:
if acc == null then
pc += imm
end
case ne:
if acc != null then
pc += imm
end
semantics:
skip
instructions:
- sig: jeqz.obj imm:i32
acc: in:ref
format: [op_imm_8, op_imm_16]
opcode_idx: [0x29, 0x2a]
- sig: jnez.obj imm:i32
acc: in:ref
format: [op_imm_8, op_imm_16]
opcode_idx: [0x2b, 0x2c]
- title: Conditional compared to zero jump
description: >
Transfer execution to an instruction at offset bytes from the beginning of the current instruction if
signed 32-bit integer in accumulator compares with 0 as specified. Offset is sign extended to the size
of instruction address.
properties:
- acc_read
- jump
- conditional
exceptions:
- x_none
verification:
- branch_target
- acc_type
pseudo: |
switch (cc)
case eq:
if acc == 0 then
pc += imm
end
case ne:
if acc != 0 then
pc += imm
end
case lt:
if acc < 0 then
pc += imm
end
case gt:
if acc > 0 then
pc += imm
end
case le:
if acc <= 0 then
pc += imm
end
case ge:
if acc >= 0 then
pc += imm
end
semantics: |-
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
Acc := int_of(@acc);
cases
when Op == "jeqz" and Acc == 0 do perform_jump(LabelId) end when;
when Op == "jnez" and Acc != 0 do perform_jump(LabelId) end when;
when Op == "jltz" and Acc < 0 do perform_jump(LabelId) end when;
when Op == "jgtz" and Acc > 0 do perform_jump(LabelId) end when;
when Op == "jlez" and Acc <= 0 do perform_jump(LabelId) end when;
when Op == "jgez" and Acc >= 0 do perform_jump(LabelId) end when
else skip //branch not taken
undefined raise exception(InternalError)
end cases
instructions:
- sig: jeqz imm:i32
acc: in:i32
format: [op_imm_8, op_imm_16]
opcode_idx: [0x2d, 0x2e]
- sig: jnez imm:i32
acc: in:i32
format: [op_imm_8, op_imm_16]
opcode_idx: [0x2f, 0x30]
- sig: jltz imm:i32
acc: in:i32
format: [op_imm_8, op_imm_16]
opcode_idx: [0x31, 0x32]
- sig: jgtz imm:i32
acc: in:i32
format: [op_imm_8, op_imm_16]
opcode_idx: [0x33, 0x34]
- sig: jlez imm:i32
acc: in:i32
format: [op_imm_8, op_imm_16]
opcode_idx: [0x35, 0x36]
- sig: jgez imm:i32
acc: in:i32
format: [op_imm_8, op_imm_16]
opcode_idx: [0x37, 0x38]
- title: Conditional compared to register jump
description: >
Transfer execution to an instruction at offset bytes from the beginning of the current instruction if signed
32-bit integers in accumulator and register compare as specified. Offset is sign extended to the size of
instruction address.
properties:
- acc_read
- jump
- conditional
exceptions:
- x_none
verification:
- branch_target
- acc_type
- v1_type
pseudo: |
diff = signed_cmp(acc, vs)
switch (cc)
case eq:
if diff == 0 then
pc += imm
end
case ne:
if diff != 0 then
pc += imm
end
case lt:
if diff < 0 then
pc += imm
end
case gt:
if diff > 0 then
pc += imm
end
case le:
if diff <= 0 then
pc += imm
end
case ge:
if diff >= 0 then
pc += imm
end
semantics: |-
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
raise exception(WrongRegisterValue) if false that type_of(@Vs) is i32;
Acc := int_of(@acc);
Vs := int_of(@Vs);
cases
when Op == "jeq" and Acc == Vs do perform_jump(LabelId) end when;
when Op == "jne" and Acc != Vs do perform_jump(LabelId) end when;
when Op == "jlt" and Acc < Vs do perform_jump(LabelId) end when;
when Op == "jgt" and Acc > Vs do perform_jump(LabelId) end when;
when Op == "jle" and Acc <= Vs do perform_jump(LabelId) end when;
when Op == "jge" and Acc >= Vs do perform_jump(LabelId) end when
else skip //branch not taken
undefined raise exception(InternalError)
end cases
instructions:
- sig: jeq v:in:i32, imm:i32
acc: in:i32
format: [op_v_8_imm_8, op_v_8_imm_16]
opcode_idx: [0x39, 0x3a]
- sig: jne v:in:i32, imm:i32
acc: in:i32
format: [op_v_8_imm_8, op_v_8_imm_16]
opcode_idx: [0x3b, 0x3c]
- sig: jlt v:in:i32, imm:i32
acc: in:i32
format: [op_v_8_imm_8, op_v_8_imm_16]
opcode_idx: [0x3d, 0x3e]
- sig: jgt v:in:i32, imm:i32
acc: in:i32
format: [op_v_8_imm_8, op_v_8_imm_16]
opcode_idx: [0x3f, 0x40]
- sig: jle v:in:i32, imm:i32
acc: in:i32
format: [op_v_8_imm_8, op_v_8_imm_16]
opcode_idx: [0x41, 0x42]
- sig: jge v:in:i32, imm:i32
acc: in:i32
format: [op_v_8_imm_8, op_v_8_imm_16]
opcode_idx: [0x43, 0x44]
- title: Floating-point unary
description: Perform specified floating-point operation on accumulator
properties:
- acc_read
- acc_write
- float
exceptions:
- x_none
verification:
- acc_type
pseudo: |
switch (op)
case fneg:
case fneg.64:
acc = -acc
semantics: |-
raise exception(WrongRegisterValue) if false that type_of(@acc) is float;
acc <~ neg(@acc)
instructions:
- sig: fneg
acc: inout:f32
prefix: f32
format: [pref_op_none]
opcode_idx: [0x4]
- sig: fneg.64
acc: inout:f64
format: [op_none]
opcode_idx: [0x45]
- title: Unary
description: Perform specified operation on accumulator
properties:
- acc_read
- acc_write
exceptions:
- x_none
verification:
- acc_type
pseudo: |
switch (op)
case neg:
case neg.64:
acc = -acc
case not:
case not.64:
acc = ~acc
semantics: |-
Acc := @acc;
cases
when Op == "neg" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32;
acc <~ neg(Acc)
end when;
when Op == "neg.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64;
acc <~ neg(Acc)
end when;
when Op == "not" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32;
acc <~ neg(Acc) - value(i32, 1)
end when;
when Op == "not.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64;
acc <~ neg(Acc) - value(i64, 1)
end when;
else raise exception (UndefinedSemantics)
undefined raise exception (InternalError)
end cases
instructions:
- sig: neg
acc: inout:i32
format: [op_none]
opcode_idx: [0x46]
- sig: neg.64
acc: inout:i64
format: [op_none]
opcode_idx: [0x47]
- sig: not
acc: inout:i32
prefix: bit
format: [pref_op_none]
opcode_idx: [0x0]
- sig: not.64
acc: inout:i64
prefix: bit
format: [pref_op_none]
opcode_idx: [0x1]
- title: Two address binary operation on accumulator
description: Perform specified binary operation on accumulator and register and store result into accumulator
properties:
- acc_read
- acc_write
exceptions:
- x_none
verification:
- acc_type
- v1_type
pseudo: |
switch (op)
case add2:
acc = (acc + vs) % 2^32
case add2.64:
acc = (acc + vs) % 2^64
case sub2:
acc = (acc - vs) % 2^32
case sub2.64:
acc = (acc - vs) % 2^64
case mul2:
acc = (acc * vs) % 2^32
case mul2.64:
acc = (acc * vs) % 2^64
case and2:
acc = acc & vs
case and2.64:
acc = acc & vs
case or2:
acc = acc | vs
case or2.64:
acc = acc | vs
case xor2:
acc = acc ^ vs
case xor2.64:
acc = acc ^ vs
case shl2:
acc = acc << (vs & 0x1f)
case shl2.64:
acc = acc << (vs & 0x3f)
case shr2:
acc = acc >> (vs & 0x1f)
case shr2.64:
acc = acc >> (vs & 0x3f)
case ashr2:
acc = arith_shift_right(acc, vs & 0x1f)
case ashr2.64:
acc = arith_shift_right(acc, vs & 0x3f)
semantics: |-
Acc := @acc;
Vs := @Vs;
cases
when Op == "add2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc + Vs)
end when;
when Op == "add2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc + Vs)
end when;
when Op == "sub2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc - Vs)
end when;
when Op == "sub2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc - Vs)
end when;
when Op == "mul2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc * Vs)
end when;
when Op == "mul2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc * Vs)
end when;
when Op == "and2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc & Vs)
end when;
when Op == "and2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc & Vs)
end when;
when Op == "or2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc | Vs)
end when;
when Op == "or2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc | Vs)
end when;
when Op == "xor2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc ^ Vs)
end when;
when Op == "xor2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc ^ Vs)
end when;
when Op == "shl2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc << Vs)
end when;
when Op == "shl2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc << Vs)
end when;
when Op == "shr2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc >> Vs)
end when;
when Op == "shr2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc >> Vs)
end when
// case ashr2:
// acc = arith_shift_right(acc, vs & 0x1f)
// case ashr2.64:
// acc = arith_shift_right(acc, vs & 0x3f)
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: add2 v:in:i32
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x48]
- sig: add2.64 v:in:i64
acc: inout:i64
format: [op_v_8]
opcode_idx: [0x49]
- sig: sub2 v:in:i32
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x4a]
- sig: sub2.64 v:in:i64
acc: inout:i64
format: [op_v_8]
opcode_idx: [0x4b]
- sig: mul2 v:in:i32
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x4c]
- sig: mul2.64 v:in:i64
acc: inout:i64
format: [op_v_8]
opcode_idx: [0x4d]
- sig: and2 v:in:i32
acc: inout:i32
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0x2]
- sig: and2.64 v:in:i64
acc: inout:i64
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0x3]
- sig: or2 v:in:i32
acc: inout:i32
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0x4]
- sig: or2.64 v:in:i64
acc: inout:i64
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0x5]
- sig: xor2 v:in:i32
acc: inout:i32
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0x6]
- sig: xor2.64 v:in:i64
acc: inout:i64
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0x7]
- sig: shl2 v:in:i32
acc: inout:i32
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0x8]
- sig: shl2.64 v:in:i64
acc: inout:i64
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0x9]
- sig: shr2 v:in:i32
acc: inout:i32
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0xa]
- sig: shr2.64 v:in:i64
acc: inout:i64
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0xb]
- sig: ashr2 v:in:i32
acc: inout:i32
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0xc]
- sig: ashr2.64 v:in:i64
acc: inout:i64
prefix: bit
format: [pref_op_v_8]
opcode_idx: [0xd]
- title: Two address floating-point binary operation on accumulator
description: |
Perform specified floating-point binary operation on accumulator and register and store result into accumulator.
The results of instructions correspond IEEE-754 arithmetic rules.
- Any operation with NaN results to NaN value.
- The sum of opposite infinities or the difference of the same sign infinities is NaN.
- Division is NaN if Infinity is divided by Infinity.
- Division is NaN if zero is divided by zero.
- Multiplication is NaN if zero is multiplied by infinity.
- The sign of division or multiplication result is positive if both values have the same sign, negative if the values have different sign.
- Division is infinity if any non zero value is divided by zero.
- Multiplication is infinity if any non zero value is multiplied by infinity.
- The sum of two infinities of the same sign or the difference of two infinities of opposite sign is infinity. The sign of result matches the sign of accumulator value.
- The sum or the difference of infinity and any finite value is infinity. The sign of result matches the sign of infinity.
- The sum or the difference of zeros is zero. Exceptions are the sum of two negative zeros and the difference of negative and positive zero - the result is negative zero.
properties:
- acc_read
- acc_write
- float
exceptions:
- x_none
verification:
- acc_type
- v1_type
pseudo: |
switch (op)
case fadd2:
case fadd2.64:
acc = acc + vs
case fsub2:
case fsub2.64:
acc = acc - vs
case fmul2:
case fmul2.64:
acc = acc * vs
case fdiv2:
case fdiv2.64:
acc = acc / vs
case fmod2:
case fmod2.64:
acc = acc % vs
semantics: |-
raise exception(WrongRegisterValue) if false that type_of(@acc) is float;
raise exception(WrongRegisterValue) if false that type_of(@Vs) is float;
Acc := float_of(@acc);
Vs := float_of(@Vs);
cases
when Op == "fadd2" or Op == "fadd2.64" do acc <~ Acc + Vs end when;
when Op == "fsub2" or Op == "fsub2.64" do acc <~ Acc - Vs end when;
when Op == "fmul2" or Op == "fmul2.64" do acc <~ Acc * Vs end when;
when Op == "fdiv2" or Op == "fdiv2.64" do acc <~ Acc / Vs end when;
when Op == "fmod2" or Op == "fmod2.64" do acc <~ Acc % Vs end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: fadd2 v:in:f32
acc: inout:f32
prefix: f32
format: [pref_op_v_8]
opcode_idx: [0x5]
- sig: fadd2.64 v:in:f64
acc: inout:f64
format: [op_v_8]
opcode_idx: [0x4e]
- sig: fsub2 v:in:f32
acc: inout:f32
prefix: f32
format: [pref_op_v_8]
opcode_idx: [0x6]
- sig: fsub2.64 v:in:f64
acc: inout:f64
format: [op_v_8]
opcode_idx: [0x4f]
- sig: fmul2 v:in:f32
acc: inout:f32
prefix: f32
format: [pref_op_v_8]
opcode_idx: [0x7]
- sig: fmul2.64 v:in:f64
acc: inout:f64
format: [op_v_8]
opcode_idx: [0x50]
- sig: fdiv2 v:in:f32
acc: inout:f32
prefix: f32
format: [pref_op_v_8]
opcode_idx: [0x8]
- sig: fdiv2.64 v:in:f64
acc: inout:f64
format: [op_v_8]
opcode_idx: [0x51]
- sig: fmod2 v:in:f32
acc: inout:f32
prefix: f32
format: [pref_op_v_8]
opcode_idx: [0x9]
- sig: fmod2.64 v:in:f64
acc: inout:f64
format: [op_v_8]
opcode_idx: [0x52]
- title: Two address integer division and modulo on accumulator
description: Perform integer division or modulo on accumulator and register and store result into accumulator
properties:
- acc_read
- acc_write
exceptions:
- x_arith
verification:
- acc_type
- v1_type
pseudo: |
if vs == 0 then
throw ArithmeticException
end
switch (op)
case div:
if acc == INT32_MIN and vs == -1 then
acc = INT32_MIN
else
acc = acc / vs
end
case div.64:
if acc == INT64_MIN and vs == -1 then
acc = INT64_MIN
else
acc = acc / vs
end
case divu:
case divu.64:
acc = acc / vs
case mod:
if acc == INT32_MIN and vs == -1 then
acc = 0
else
acc = acc % vs
end
case mod.64:
if acc == INT64_MIN and vs == -1 then
acc = 0
else
acc = acc % vs
end
case modu:
case modu.64:
acc = acc % vs
semantics: |-
Acc := @acc;
Vs := @Vs;
cases
when Op == "div2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc / Vs)
end when;
when Op == "div2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc / Vs)
end when;
when Op == "mod2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32 and type_of(Vs) is i32;
acc <~ truncate_int(Acc % Vs)
end when;
when Op == "mod2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is i64 and type_of(Vs) is i64;
acc <~ truncate_int(Acc % Vs)
end when;
when Op == "divu2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is u32 and type_of(Vs) is u32;
acc <~ truncate_int(Acc / Vs)
end when;
when Op == "divu2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is u64 and type_of(Vs) is u64;
acc <~ truncate_int(Acc / Vs)
end when;
when Op == "modu2" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is u32 and type_of(Vs) is u32;
acc <~ truncate_int(Acc % Vs)
end when;
when Op == "modu2.64" do
raise exception(WrongRegisterValue) if false that type_of(Acc) is u64 and type_of(Vs) is u64;
acc <~ truncate_int(Acc % Vs)
end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: div2 v:in:i32
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x53]
- sig: div2.64 v:in:i64
acc: inout:i64
format: [op_v_8]
opcode_idx: [0x54]
- sig: mod2 v:in:i32
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x55]
- sig: mod2.64 v:in:i64
acc: inout:i64
format: [op_v_8]
opcode_idx: [0x56]
- sig: divu2 v:in:u32
acc: inout:u32
prefix: unsigned
format: [pref_op_v_8]
opcode_idx: [0x2]
- sig: divu2.64 v:in:u64
acc: inout:u64
prefix: unsigned
format: [pref_op_v_8]
opcode_idx: [0x3]
- sig: modu2 v:in:u32
acc: inout:u32
prefix: unsigned
format: [pref_op_v_8]
opcode_idx: [0x4]
- sig: modu2.64 v:in:u64
acc: inout:u64
prefix: unsigned
format: [pref_op_v_8]
opcode_idx: [0x5]
- title: Two address binary operation with immediate on accumulator
description: >
Perform specified binary operation on accumulator and immediate and store result into accumulator.
Immediate is sign extended to operand size.
properties:
- acc_write
- acc_read
exceptions:
- x_none
verification:
- acc_type
pseudo: |
switch (op)
case addi:
acc = (acc + imm) % 2^32
case subi:
acc = (acc - imm) % 2^32
case muli:
acc = (acc * imm) % 2^32
case andi:
acc = acc & imm
case ori:
acc = acc | imm
case xori:
acc = acc ^ imm
case shli:
acc = acc << (imm & 0x1f)
case shri:
acc = acc >> (imm & 0x1f)
case ashri:
acc = arith_shift_right(acc, imm & 0x1f)
semantics: |-
Acc := @acc;
raise exception(WrongRegisterValue) if false that type_of(Acc) is i32;
Imm := value_of(i32, int_of(Imm));
raise exception(WrongImmediateValue) if undefined Imm;
cases
when Op == "addi" do
acc <~ truncate_int(Acc + Imm)
end when;
when Op == "subi" do
acc <~ truncate_int(Acc - Imm)
end when;
when Op == "muli" do
acc <~ truncate_int(Acc * Imm)
end when;
when Op == "andi" do
acc <~ Acc & Imm
end when;
when Op == "ori" do
acc <~ Acc | Imm
end when;
when Op == "xori" do
acc <~ Acc ^ Imm
end when;
when Op == "shli" do
acc <~ truncate_int(Acc << Imm)
end when;
when Op == "shri" do
acc <~ Acc >> Imm
end when
//when Op == "ashri" do
// acc <~
//end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: addi imm:i32
acc: inout:i32
format: [op_imm_8]
opcode_idx: [0x57]
- sig: subi imm:i32
acc: inout:i32
format: [op_imm_8]
opcode_idx: [0x58]
- sig: muli imm:i32
acc: inout:i32
format: [op_imm_8]
opcode_idx: [0x59]
- sig: andi imm:i32
acc: inout:i32
format: [op_imm_32]
opcode_idx: [0x5a]
- sig: ori imm:i32
acc: inout:i32
format: [op_imm_32]
opcode_idx: [0x5b]
- sig: xori imm:i32
acc: inout:i32
prefix: bit
format: [pref_op_imm_32]
opcode_idx: [0xe]
- sig: shli imm:i32
acc: inout:i32
format: [op_imm_8]
opcode_idx: [0x5c]
- sig: shri imm:i32
acc: inout:i32
format: [op_imm_8]
opcode_idx: [0x5d]
- sig: ashri imm:i32
acc: inout:i32
format: [op_imm_8]
opcode_idx: [0x5e]
- title: Two address integer division or modulo with immediate on accumulator
description: >
Perform two address integer division or modulo on accumulator and immediate and store result into accumulator.
Immediate is sign extended to operand size.
properties:
- acc_write
- acc_read
exceptions:
- x_arith
verification:
- acc_type
pseudo: |
if imm == 0 then
throw ArithmeticException
end
switch(op)
case divi:
if acc == INT32_MIN and imm == -1 then
acc = INT32_MIN
else
acc = acc / imm
end
case modi:
if acc == INT32_MIN and imm == -1 then
acc = 0
else
acc = acc % imm
end
semantics: |-
Vs := @Vs;
Imm := value_of(i32, int_of(Imm));
raise exception(WrongRegisterValue) if false that type_of(Vs) is i32;
raise exception(WrongImmediateValue) if undefined Imm;
cases
when Op == "div" do
acc <~ Vs / Imm
end when;
when Op == "mod" do
acc <~ Vs % Imm
end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: divi imm:i32
acc: inout:i32
format: [op_imm_8]
opcode_idx: [0x5f]
- sig: modi imm:i32
acc: inout:i32
format: [op_imm_8]
opcode_idx: [0x60]
- title: Three address binary operation
description: Perform specified binary operation on two registers and store result into accumulator
properties:
- acc_write
exceptions:
- x_none
verification:
- v1_type
- v2_type
pseudo: |
switch (op)
case add:
acc = (vs1 + vs2) % 2^32
case sub:
acc = (vs1 - vs2) % 2^32
case mul:
acc = (vs1 * vs2) % 2^32
case and:
acc = vs1 & vs2
case or:
acc = vs1 | vs2
case xor:
acc = vs1 ^ vs2
case shl:
acc = vs1 << (vs2 & 0x1f)
case shr:
acc = vs1 >> (vs2 & 0x1f)
case ashr:
acc = arith_shift_right(vs1, vs2 & 0x1f)
semantics: |-
Vs1 := @Vs1;
Vs2 := @Vs2;
raise exception(WrongRegisterValue) if false that type_of(Vs1) is i32 and type_of(Vs2) is i32;
cases
when Op == "add" do
acc <~ truncate_int(Vs1 + Vs2)
end when;
when Op == "sub" do
acc <~ truncate_int(Vs1 - Vs2)
end when;
when Op == "mul" do
acc <~ truncate_int(Vs1 * Vs2)
end when;
when Op == "and" do
acc <~ truncate_int(Vs1 & Vs2)
end when;
when Op == "or" do
acc <~ truncate_int(Vs1 | Vs2)
end when;
when Op == "xor" do
acc <~ truncate_int(Vs1 ^ Vs2)
end when;
when Op == "shl" do
acc <~ truncate_int(Vs1 << Vs2)
end when;
when Op == "shr" do
acc <~ truncate_int(Vs1 >> Vs2)
end when
// when Op == "ashr" do
// end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: add v1:in:i32, v2:in:i32
acc: out:i32
format: [op_v1_4_v2_4]
opcode_idx: [0x61]
- sig: sub v1:in:i32, v2:in:i32
acc: out:i32
format: [op_v1_4_v2_4]
opcode_idx: [0x62]
- sig: mul v1:in:i32, v2:in:i32
acc: out:i32
format: [op_v1_4_v2_4]
opcode_idx: [0x63]
- sig: and v1:in:i32, v2:in:i32
acc: out:i32
prefix: bit
format: [pref_op_v1_4_v2_4]
opcode_idx: [0xf]
- sig: or v1:in:i32, v2:in:i32
acc: out:i32
prefix: bit
format: [pref_op_v1_4_v2_4]
opcode_idx: [0x10]
- sig: xor v1:in:i32, v2:in:i32
acc: out:i32
prefix: bit
format: [pref_op_v1_4_v2_4]
opcode_idx: [0x11]
- sig: shl v1:in:i32, v2:in:i32
acc: out:i32
prefix: bit
format: [pref_op_v1_4_v2_4]
opcode_idx: [0x12]
- sig: shr v1:in:i32, v2:in:i32
acc: out:i32
prefix: bit
format: [pref_op_v1_4_v2_4]
opcode_idx: [0x13]
- sig: ashr v1:in:i32, v2:in:i32
acc: out:i32
prefix: bit
format: [pref_op_v1_4_v2_4]
opcode_idx: [0x14]
- title: Three address integer division or modulo
description: Perform integer division or modulo on two registers and store result into accumulator
properties:
- acc_write
exceptions:
- x_arith
verification:
- v1_type
- v2_type
pseudo: |
if vs2 == 0 then
throw ArithmeticException
end
switch(op)
case div:
if vs1 == INT32_MIN and vs2 == -1 then
acc = INT32_MIN
else
acc = vs1 / vs2
end
case mod:
if vs1 == INT32_MIN and vs2 == -1 then
acc = 0
else
acc = vs1 % vs2
end
semantics: |-
Vs1 := @Vs1;
Vs2 := @Vs2;
raise exception(WrongRegisterValue) if false that type_of(Vs1) is i32 and type_of(Vs2) is i32;
cases
when Op == "div" do
acc <~ Vs1 / Vs2
end when;
when Op == "mod" do
acc <~ Vs1 % Vs2
end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: div v1:in:i32, v2:in:i32
acc: out:i32
format: [op_v1_4_v2_4]
opcode_idx: [0x64]
- sig: mod v1:in:i32, v2:in:i32
acc: out:i32
format: [op_v1_4_v2_4]
opcode_idx: [0x65]
- title: Increment register with immediate
description: >
Increment virtual register by specified immediate. Immediate is sign extended to operand size.
properties:
- acc_none
exceptions:
- x_none
verification:
- v1_i32
pseudo: |
vx = (vx + imm) % 2^32
semantics: |-
V := @Vx;
Imm := value_of(i32, int_of(Imm));
raise exception(WrongRegisterValue) if false that type_of(V) is i32;
raise exception(WrongImmediateValue) if undefined int_of(Imm);
Vx <~ V + Imm
instructions:
- sig: inci v:inout:i32, imm:i32
acc: none
format: [op_v_4_imm_4]
opcode_idx: [0x66]
- title: Conversions between integer and floating point types
description: |
Perform specified primitive type conversion of accumulator.
Conversion from floating-point types to integer one obeys the following rules.
- It is rounding toward zero.
- If converted integer is less than minimal value for destination type, the result is minimal value for that type.
- If source is negative infinity, the result is minimal value for destination type.
- If converted integer is greater than maximum value for destination type, the result is maximum value for that type.
- If source is positive infinity, the result is maximum value for destination type.
- If source is NaN, the result is equal to 0.
properties:
- acc_read
- acc_write
- float
exceptions:
- x_none
verification:
- acc_type
pseudo: |
(dest_type) acc = (src_type) acc
semantics: |-
cases
when Op == "i32tof32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
acc <~ i32tof32(@acc)
end when;
when Op == "i32tof64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
acc <~ i32tof64(@acc)
end when;
when Op == "u32tof32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u32;
acc <~ u32tof32(@acc)
end when;
when Op == "u32tof64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u32;
acc <~ u32tof64(@acc)
end when;
when Op == "i64tof32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i64;
acc <~ i64tof32(@acc)
end when;
when Op == "i64tof64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i64;
acc <~ i64tof64(@acc)
end when;
when Op == "u64tof32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u64;
acc <~ u64tof32(@acc)
end when;
when Op == "u64tof64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u64;
acc <~ u64tof64(@acc)
end when;
when Op == "f32toi32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is f32;
acc <~ f32toi32(@acc)
end when;
when Op == "f32tou32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is f32;
acc <~ f32tou32(@acc)
end when;
when Op == "f32toi64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is f32;
acc <~ f32toi64(@acc)
end when;
when Op == "f32tou64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is f32;
acc <~ f32tou64(@acc)
end when
when Op == "f64toi32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is f64;
acc <~ f64toi32(@acc)
end when;
when Op == "f64tou32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is f64;
acc <~ f64tou32(@acc)
end when;
when Op == "f64toi64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is f64;
acc <~ f64toi64(@acc)
end when;
when Op == "f64tou64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is f64;
acc <~ f64tou64(@acc)
end when
when Op == "f64tof32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is f64;
acc <~ f64tof32(@acc)
end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: i32tof32
acc: inout:i32->f32
prefix: f32
format: [pref_op_none]
opcode_idx: [0xa]
- sig: i32tof64
acc: inout:i32->f64
prefix: cast
format: [pref_op_none]
opcode_idx: [0x0]
- sig: u32tof32
acc: inout:u32->f32
prefix: f32
format: [pref_op_none]
opcode_idx: [0xb]
- sig: u32tof64
acc: inout:u32->f64
prefix: cast
format: [pref_op_none]
opcode_idx: [0x1]
- sig: i64tof32
acc: inout:i64->f32
prefix: f32
format: [pref_op_none]
opcode_idx: [0xc]
- sig: i64tof64
acc: inout:i64->f64
prefix: cast
format: [pref_op_none]
opcode_idx: [0x2]
- sig: u64tof32
acc: inout:u64->f32
prefix: f32
format: [pref_op_none]
opcode_idx: [0xd]
- sig: u64tof64
acc: inout:u64->f64
prefix: cast
format: [pref_op_none]
opcode_idx: [0x3]
- sig: f32tof64
acc: inout:f32->f64
prefix: f32
format: [pref_op_none]
opcode_idx: [0xe]
- sig: f32toi32
acc: inout:f32->i32
prefix: f32
format: [pref_op_none]
opcode_idx: [0xf]
- sig: f32toi64
acc: inout:f32->i64
prefix: f32
format: [pref_op_none]
opcode_idx: [0x10]
- sig: f32tou32
acc: inout:f32->u32
prefix: f32
format: [pref_op_none]
opcode_idx: [0x11]
- sig: f32tou64
acc: inout:f32->u64
prefix: f32
format: [pref_op_none]
opcode_idx: [0x12]
- sig: f64toi32
acc: inout:f64->i32
prefix: cast
format: [pref_op_none]
opcode_idx: [0x4]
- sig: f64toi64
acc: inout:f64->i64
prefix: cast
format: [pref_op_none]
opcode_idx: [0x5]
- sig: f64tou32
acc: inout:f64->u32
prefix: cast
format: [pref_op_none]
opcode_idx: [0x6]
- sig: f64tou64
acc: inout:f64->u64
prefix: cast
format: [pref_op_none]
opcode_idx: [0x7]
- sig: f64tof32
acc: inout:f64->f32
prefix: f32
format: [pref_op_none]
opcode_idx: [0x13]
- title: Conversions from integer types to u1
description: |
Conversion from integer types to u1 obeys the following rules.
- If converted integer is not equal to zero, the result is 1.
- Otherwise the result is 0.
properties:
- acc_read
- acc_write
exceptions:
- x_none
verification:
- acc_type
pseudo: |
(dest_type) acc = (src_type) acc
semantics: |-
cases
when Op == "i32tou1" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
acc <~ i32tou1(@acc)
end when
when Op == "i64tou1" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i64;
acc <~ i64tou1(@acc)
end when
when Op == "u32tou1" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u32;
acc <~ u32tou1(@acc)
end when
when Op == "u64tou1" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u64;
acc <~ u64tou1(@acc)
end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: i32tou1
acc: inout:i32->u1
prefix: cast
format: [pref_op_none]
opcode_idx: [0x8]
- sig: i64tou1
acc: inout:i64->u1
prefix: cast
format: [pref_op_none]
opcode_idx: [0x9]
- sig: u32tou1
acc: inout:u32->u1
prefix: cast
format: [pref_op_none]
opcode_idx: [0xa]
- sig: u64tou1
acc: inout:u64->u1
prefix: cast
format: [pref_op_none]
opcode_idx: [0xb]
- title: Integer truncations and extensions.
description: >
Perform specified integer extension or truncations of accumulator.
Truncations discard all but N lowest-order bits, where N is the bit size of destination type.
If extension bytecode treats its source as signed integer, the value is sign-extended to destination type.
If extension bytecode treats its source as unsigned integer, the value is zero-extended to destination type.
properties:
- acc_read
- acc_write
exceptions:
- x_none
verification:
- acc_type
pseudo: |
(dest_type) acc = (src_type) acc
semantics: |-
cases
when Op == "i32toi64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
acc <~ i32toi64(@acc)
end when;
when Op == "i32toi16" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
acc <~ i32toi16(@acc)
end when;
when Op == "i32tou16" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
acc <~ i32tou16(@acc)
end when;
when Op == "i32toi8" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
acc <~ i32toi8(@acc)
end when;
when Op == "i32tou8" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i32;
acc <~ i32tou8(@acc)
end when;
when Op == "i64toi32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is i64;
acc <~ i64toi32(@acc)
end when;
when Op == "u32toi64" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u32;
acc <~ u32toi64(@acc)
end when
when Op == "u32toi16" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u32;
acc <~ u32toi16(@acc)
end when
when Op == "u32tou16" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u32;
acc <~ u32tou16(@acc)
end when
when Op == "u32toi8" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u32;
acc <~ u32toi8(@acc)
end when
when Op == "u32tou8" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u32;
acc <~ u32tou8(@acc)
end when
when Op == "u64toi32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u64;
acc <~ u64toi32(@acc)
end when
when Op == "u64tou32" do
raise exception(WrongRegisterValue) if false that type_of(@acc) is u64;
acc <~ u64tou32(@acc)
end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: i32toi64
acc: inout:i32->i64
prefix: cast
format: [pref_op_none]
opcode_idx: [0xc]
- sig: i32toi16
acc: inout:i32->i16
prefix: cast
format: [pref_op_none]
opcode_idx: [0xd]
- sig: i32tou16
acc: inout:i32->u16
prefix: cast
format: [pref_op_none]
opcode_idx: [0xe]
- sig: i32toi8
acc: inout:i32->i8
prefix: cast
format: [pref_op_none]
opcode_idx: [0xf]
- sig: i32tou8
acc: inout:i32->u8
prefix: cast
format: [pref_op_none]
opcode_idx: [0x10]
- sig: i64toi32
acc: inout:i64->i32
prefix: cast
format: [pref_op_none]
opcode_idx: [0x11]
- sig: u32toi64
acc: inout:u32->i64
prefix: cast
format: [pref_op_none]
opcode_idx: [0x12]
- sig: u32toi16
acc: inout:u32->i16
prefix: cast
format: [pref_op_none]
opcode_idx: [0x13]
- sig: u32tou16
acc: inout:u32->u16
prefix: cast
format: [pref_op_none]
opcode_idx: [0x14]
- sig: u32toi8
acc: inout:u32->i8
prefix: cast
format: [pref_op_none]
opcode_idx: [0x15]
- sig: u32tou8
acc: inout:u32->u8
prefix: cast
format: [pref_op_none]
opcode_idx: [0x16]
- sig: u64toi32
acc: inout:u64->i32
prefix: cast
format: [pref_op_none]
opcode_idx: [0x17]
- sig: u64tou32
acc: inout:u64->u32
prefix: cast
format: [pref_op_none]
opcode_idx: [0x18]
- title: Load from array
description: >
Load an element from array using accumulator as an index and puts it into accumulator.
If element size is smaller then 32 bits, it will be zero or sign extended (depending on bytecode)
to i32.
verification:
- v1_array_type
- acc_i32
properties:
- acc_read
- acc_write
exceptions:
- x_null
- x_bounds
pseudo: |
if vs == null then
throw NullPointerException
end
if acc < 0 || acc >= len(vs) then
throw ArrayIndexOutOfBoundsException
end
if op == ldarr.8 then
acc = i8toi32(vs[acc])
else if op == ldarru.8 then
acc = u8toi32(vs[acc])
else if op == ldarr.16 then
acc = i16toi32(vs[acc])
else if op == ldarru.16 then
acc = u16toi32(vs[acc])
else
acc = vs[acc]
end
semantics: |-
acc <~ (@Vs) [@acc]
instructions:
- sig: ldarr.8 v:in:i8[]
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x67]
- sig: ldarru.8 v:in:u8[]
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x68]
- sig: ldarr.16 v:in:i16[]
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x69]
- sig: ldarru.16 v:in:u16[]
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x6a]
- sig: ldarr v:in:i32[]
acc: inout:i32
format: [op_v_8]
opcode_idx: [0x6b]
- sig: ldarr.64 v:in:i64[]
acc: inout:i32->i64
format: [op_v_8]
opcode_idx: [0x6c]
- sig: fldarr.32 v:in:f32[]
acc: inout:i32->f32
format: [op_v_8]
opcode_idx: [0x6d]
properties: [acc_read, acc_write, float]
- sig: fldarr.64 v:in:f64[]
acc: inout:i32->f64
format: [op_v_8]
opcode_idx: [0x6e]
properties: [acc_read, acc_write, float]
- sig: ldarr.obj v:in:ref[]
acc: inout:i32->ref
format: [op_v_8]
opcode_idx: [0x6f]
- title: Store to array
description: >
Store accumulator content into array slot pointed by index.
If size of element to store is less than 32, accumulator content will be truncated
to storage size before storing.
verification:
- v1_array_type
- v2_i32
- acc_type
properties:
- acc_read
exceptions:
- x_null
- x_bounds
- x_store
pseudo: |
if vs1 == null then
throw NullPointerException
end
if vs2 < 0 || vs2 >= len(vs1) then
throw ArrayIndexOutOfBoundsException
end
if op == starr.obj && !has_type(acc, component_type(vs1))
throw ArrayStoreException
end
if op == starr.8 then
vs1[vs2] = truncateto8(acc)
else if op == starr.16 then
vs1[vs2] = truncateto16(acc)
else
vs1[vs2] = acc
end
semantics: |-
(@Vs1) <~ (@Vs1) [ (@Vs2) ] <- (@acc)
instructions:
- sig: starr.8 v1:in:i8[], v2:in:i32
acc: in:i8
format: [op_v1_4_v2_4]
opcode_idx: [0x70]
- sig: starr.16 v1:in:i16[], v2:in:i32
acc: in:i16
format: [op_v1_4_v2_4]
opcode_idx: [0x71]
- sig: starr v1:in:i32[], v2:in:i32
acc: in:i32
format: [op_v1_4_v2_4]
opcode_idx: [0x72]
- sig: starr.64 v1:in:i64[], v2:in:i32
acc: in:i64
format: [op_v1_4_v2_4]
opcode_idx: [0x73]
- sig: fstarr.32 v1:in:f32[], v2:in:i32
acc: in:f32
format: [op_v1_4_v2_4]
opcode_idx: [0x74]
properties: [acc_read, float]
- sig: fstarr.64 v1:in:f64[], v2:in:i32
acc: in:f64
format: [op_v1_4_v2_4]
opcode_idx: [0x75]
properties: [acc_read, float]
- sig: starr.obj v1:in:ref[], v2:in:i32
acc: in:ref
format: [op_v1_4_v2_4]
opcode_idx: [0x76]
- title: Array length
description: Get length of an array and put it into accumulator.
properties:
- acc_write
verification:
- v1_array
exceptions:
- x_null
pseudo: |
if x == null then
throw NullPointerException
end
acc = len(vs)
semantics: |-
acc <~ array_length(@Vs)
instructions:
- sig: lenarr v:in:top[]
acc: out:i32
format: [op_v_8]
opcode_idx: [0x77]
- title: Create new array
description: >
Create a new single-dimensional array of given type and size and put a reference to it into register.
Elements of array are initialized to a default value, i.e. 0 for primitive types and null for objects.
verification:
- type_id_array
- v2_i32
exceptions:
- x_negsize
- x_oom
properties:
- acc_none
- type_id
- language_type
pseudo: |
if v2 < 0 then
throw NegativeArraySizeException
end
type = resolve(type_id)
v1 = new_array(v2, type)
if v1 == null then
throw OutOfMemoryError
end
semantics: |-
@V1 <~ new_array(TypeId, @V2)
instructions:
- sig: newarr v1:out:ref, v2:in:i32, type_id
acc: none
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x78]
- title: Create new object
description: |
Resolve class type from type_id, allocate memory for an object, initialize its fields with their
default values (i.e. 0 for primitives and null for objects) and put a reference to the newly created
object into register. The object should be initialized by calling initialization method before
further usage.
verification:
- type_id_object
exceptions:
- x_oom
properties:
- acc_none
- type_id
pseudo: |
type = resolve(type_id)
v = new(type)
if v == null then
throw OutOfMemoryError
end
semantics: |-
skip
instructions:
- sig: newobj v:out:ref, type_id
acc: none
format: [op_v_8_id_16]
opcode_idx: [0x79]
- title: Create new object and call initializer
description: >
Resolve class type from initializer method_id, allocate memory for an object, initialize its fields with their
default values (i.e. 0 for primitives and null for objects), call specified initializer and put a reference to
the newly created object into accumulator. method_id should resolve to an initializer.
Non-range instructions can be used to pass up to 4 arguments.
Unused register slot values will be discarded and corresponding registers will not be passed to initializer.
For methods with more arguments range kinds of instruction are to be used, which takes the needed number of
arguments starting from 'vs' register.
verification:
- method_id_non_static
- method_id_accessible
- method_init_obj
- compatible_arguments
exceptions:
- x_abstract
- x_oom
properties:
- acc_write
- method_id
pseudo: |
init, type = resolve(method_id)
acc = new(type)
if acc == null then
throw OutOfMemoryError
end
init(acc, args)
semantics: |-
skip
instructions:
- sig: initobj.short method_id, v1:in:none, v2:in:none
acc: out:ref
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x7a]
- sig: initobj method_id, v1:in:none, v2:in:none, v3:in:none, v4:in:none
acc: out:ref
format: [op_v1_4_v2_4_v3_4_v4_4_id_16]
opcode_idx: [0x7b]
- sig: initobj.range method_id, v:in:none
acc: out:ref
format: [op_v_8_id_16]
opcode_idx: [0x7c]
- title: Get field from object to accumulator
description: >
Get field value from an object by field id and put it into accumulator.
For non-object variant, the size of the field is determined by the field_id,
most significant bits are sign or unsigned extended based on the field type to fit accumulator size.
If field type size is less than 32, then loaded value is sign or zero extended to i32 depending on field type.
verification:
- v1_object
- field_id_non_static
- field_id_size
properties:
- acc_write
- field_id
exceptions:
- x_null
pseudo: |
if vs == null then
throw NullPointerException
end
field = resolve(field_id)
if op == ldobj and size(field) < 32 then
acc = extendto32(vs.get(field))
else
acc = vs.get(field)
end
semantics: |-
skip
instructions:
- sig: ldobj v:in:ref, field_id
acc: out:b32
format: [op_v_8_id_16]
opcode_idx: [0x7d]
- sig: ldobj.64 v:in:ref, field_id
acc: out:b64
format: [op_v_8_id_16]
opcode_idx: [0x7e]
- sig: ldobj.obj v:in:ref, field_id
acc: out:ref
format: [op_v_8_id_16]
opcode_idx: [0x7f]
- title: Store accumulator content into object field
description: >
Store accumulator content into object field by field_id. For non-object variant the size of actually stored
value is determined by field_id, other accumulator bits are discarded.
If field type size is less than 32, accumulator content will be truncated to storage size before storing.
verification:
- v1_object
- field_id_non_static
- field_id_size
- acc_type
properties:
- acc_read
- field_id
exceptions:
- x_null
pseudo: |
if vs == null then
throw NullPointerException
end
field = resolve(field_id)
if op == stobj and size(field) < 32 then
vs.set(field, truncate(field, acc))
else
vs.set(field, acc)
end
semantics: |-
skip
instructions:
- sig: stobj v:in:ref, field_id
acc: in:b32
format: [op_v_8_id_16]
opcode_idx: [0x80]
- sig: stobj.64 v:in:ref, field_id
acc: in:b64
format: [op_v_8_id_16]
opcode_idx: [0x81]
- sig: stobj.obj v:in:ref, field_id
acc: in:ref
format: [op_v_8_id_16]
opcode_idx: [0x82]
- title: Get field from object to register
description: >
Get field value from an object by field id and put it into register.
For non-object variant, the size of the field is determined by the field_id,
most significant bits are sign or unsigned extended based on the field type to fit register size.
If field type size is less than 32, then loaded value is sign or zero extended to i32 depending on field type.
If field type is f32, load will implicitly convert value into f64 type.
verification:
- v2_object
- field_id_non_static
- field_id_size
properties:
- acc_none
- field_id
exceptions:
- x_null
pseudo: |
if v2 == null then
throw NullPointerException
end
field = resolve(field_id)
if op == ldobj and size(field) < 32 then
v1 = extendto32(v2.get(field))
else if type(field) == f32 then
v1 = f32tof64(v2.get(field))
else
v1 = v2.get(field)
end
semantics: |-
skip
instructions:
- sig: ldobj.v v1:out:i32, v2:in:ref, field_id
acc: none
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x83]
- sig: ldobj.v.64 v1:out:b64, v2:in:ref, field_id
acc: none
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x84]
- sig: ldobj.v.obj v1:out:ref, v2:in:ref, field_id
acc: none
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x85]
- title: Store register content into object field
description: >
Store register content into object field by field_id. For non-object variant the size of actually stored
value is determined by field_id, other register bits are discarded.
If field type size is less than 32, register content will be truncated to storage size before storing.
verification:
- v1_type
- v2_object
- field_id_non_static
- field_id_size
properties:
- acc_none
- field_id
exceptions:
- x_null
pseudo: |
if v2 == null then
throw NullPointerException
end
field = resolve(field_id)
if op == stobj and size(field) < 32 then
v2.set(field, truncate(field, v1))
else
v2.set(field, v1)
end
semantics: |-
skip
instructions:
- sig: stobj.v v1:in:b32, v2:in:ref, field_id
acc: none
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x86]
- sig: stobj.v.64 v1:in:b64, v2:in:ref, field_id
acc: none
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x87]
- sig: stobj.v.obj v1:in:ref, v2:in:ref, field_id
acc: none
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x88]
- title: Get static field
description: >
Get static field value by field_id and put it into accumulator.
For non-object variant, the size of the field is determined by the field_id,
most significant bits are sign or unsigned extended based on the field type to fit accumulator size.
If field size is less then 32, result will be sign or unsigned extended to i32 depedning on field type.
verification:
- field_id_static
- field_id_size
properties:
- acc_write
- field_id
exceptions:
- x_init
pseudo: |
field = resolve(field_id)
if op == ldstatic and size(field) < 32 then
acc = extendto32(get_static(field))
else
acc = get_static(field)
end
semantics: |-
skip
instructions:
- sig: ldstatic field_id
acc: out:b32
format: [op_id_16]
opcode_idx: [0x89]
- sig: ldstatic.64 field_id
acc: out:b64
format: [op_id_16]
opcode_idx: [0x8a]
- sig: ldstatic.obj field_id
acc: out:ref
format: [op_id_16]
opcode_idx: [0x8b]
- title: Store to static field
description: >
Store accumulator content into static field by field_id. For non-object variant the size of actually stored
value is determined by field_id, other accumulator bits are discarded.
If field type size is less than 32, then accumulator content will be trunctated to field size before storing.
verification:
- field_id_static
- field_id_size
- acc_type
properties:
- acc_read
- field_id
exceptions:
- x_init
pseudo: |
field = resolve(field_id)
if op == ststatic and size(field) < 32 then
set_static(field, truncate(field, acc))
else
set_static(field, acc)
end
semantics: |-
skip
instructions:
- sig: ststatic field_id
acc: in:b32
format: [op_id_16]
opcode_idx: [0x8c]
- sig: ststatic.64 field_id
acc: in:b64
format: [op_id_16]
opcode_idx: [0x8d]
- sig: ststatic.obj field_id
acc: in:ref
format: [op_id_16]
opcode_idx: [0x8e]
- title: Return value from method
description: >
Return from the current method, i.e. restore the frame of method invoker and return control to the
invoker. Return value should be in accumulator.
verification:
- acc_return_type
exceptions:
- x_none
properties:
- acc_read
- return
pseudo: |
return acc
semantics: |-
perform_return(@acc)
instructions:
- sig: return
acc: in:i32
format: [op_none]
opcode_idx: [0x8f]
- sig: return.64
acc: in:b64
format: [op_none]
opcode_idx: [0x90]
- sig: return.obj
acc: in:ref
format: [op_none]
opcode_idx: [0x91]
- title: Return from a void method
description: >
Return from the current method, i.e. restore the frame of method invoker and return control to the invoker.
Caller should treat accumulator value as undefined and cannot use it until accumulator definition in the caller
frame.
verification:
- none
exceptions:
- x_none
properties:
- acc_none
- return
pseudo: |
return
semantics: |-
perform_return_void()
instructions:
- sig: return.void
acc: none
format: [op_none]
opcode_idx: [0x92]
- title: Throw exception
description: >
Throw an exception located in register. The current method is searched for the first exception handler that
matches the class of exception. If exception handler is found, control is passed to the exception handler. If no
exception handler is found, the frame of invoker is restored. If such frame exists, the exception is re-thrown.
If no such frame exists, the current VM thread exits.
verification:
- v1_throw_type
exceptions:
- x_null
properties:
- acc_none
pseudo: |
if vs == null then
throw NullPointerException
end
throw vs
semantics: |-
skip
instructions:
- sig: throw v:in:ref
acc: none
format: [op_v_8]
opcode_idx: [0x93]
- title: Check cast
description: >
Resolve object type by specified id and if an object in accumulator can be cast to the resolved type,
accumulator content remains unchanged. Otherwise ClassCastException is thrown.
Object of type O can be cast to type T if O is the same as T or is subtype of T. For arrays O can be cast
to T if T is a root type in type hierarchy or T is such array that O array elements are the same or subtype of
T array elements.
'null' object reference can be cast to every type.
verification:
- type_id_any_object
- acc_obj_or_null
exceptions:
- x_cast
properties:
- acc_read
- type_id
pseudo: |
type = resolve(type_id)
if acc != null and !has_type(acc, type) then
throw ClassCastException
end
semantics: |-
skip
instructions:
- sig: checkcast type_id
acc: in:ref
format: [op_id_16]
opcode_idx: [0x94]
- title: Is instance
description: >
Resolve object type by specified id and if an object in accumulator is an instance of the resolved type,
put 1 into accumulator, otherwise put 0.
Object of type O is instance of type T if O is the same as T or is subtype of T. For arrays T should be a root
type in type hierarchy or T is such array that O array elements are the same or subtype of T array elements.
'null' object is not an instance of any class.
verification:
- type_id_any_object
- acc_obj_or_null
exceptions:
- x_classdef
properties:
- acc_read
- acc_write
- type_id
pseudo: |
type = resolve(type_id)
if acc == null or !has_type(acc, type) then
acc = 0
else
acc = 1
end
semantics: |-
skip
instructions:
- sig: isinstance type_id
acc: inout:ref->i32
format: [op_id_16]
opcode_idx: [0x95]
- title: Static call
description: >
Call indicated static method, i.e. create new frame, pass values of arguments and
continue execution from the first instruction of a method.
Callee should treat accumulator value as undefined and cannot use it until accumulator
definition in the new frame.
Result (if any) is returned in accumulator (see 'Calling sequence' chapter for more details).
Method, its class and the number of argument is resolved by given method_id in
runtime constant-pool.
Arguments are passed in source registers in the same order as in method signature.
Non-range instructions can be used to pass up to 4 arguments (unused register slot values will
be discarded and corresponding registers will not be passed to the callee).
For methods with more arguments range kind of instruction is to be used, which takes the needed number
of arguments starting from 'vs' register.
In dynamically-typed language context accept 'any' values in source registers.
verification:
- method_id_static
- method_id_non_abstract
- compatible_arguments
properties:
- acc_write
- method_id
- call
- maybe_dynamic
exceptions:
- x_none
pseudo: |
method = resolve(method_id)
acc = call(method, args)
semantics: |-
cases
when Op == "call.short" do perform_call_fixed_args( MethodId, [[ @Vs1, @Vs2 ]] ) end when;
when Op == "call" do perform_call_fixed_args( MethodId, [[ @Vs1, @Vs2, @Vs3, @Vs4 ]] ) end when;
when Op == "call.range" do perform_call_range_fixed_args( MethodId, Vs ) end when
else raise exception(UndefinedSemantics)
undefined raise exception(InternalError)
end cases
instructions:
- sig: call.short method_id, v1:in:top, v2:in:top
acc: out:top
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x96]
- sig: call method_id, v1:in:top, v2:in:top, v3:in:top, v4:in:top
acc: out:top
format: [op_v1_4_v2_4_v3_4_v4_4_id_16]
opcode_idx: [0x97]
- sig: call.range method_id, v:in:top
acc: out:top
format: [op_v_8_id_16]
opcode_idx: [0x98]
- title: Static call with accumulator as input
description: >
Call indicated static method, i.e. create new frame, pass values of arguments and
continue execution from the first instruction of a method.
Callee should treat accumulator value as undefined and cannot use it until accumulator
definition in the new frame.
Result (if any) is returned in accumulator (see 'Calling sequence' chapter for more details).
Method, its class and the number of argument is resolved by given method_id in
runtime constant-pool.
Arguments are passed in source registers in the same order as in method signature.
Non-range instructions can be used to pass up to 4 arguments (unused register slot values will
be discarded and corresponding registers will not be passed to the callee).
In dynamically-typed language context accept 'any' values in source registers.
Immediate operand encodes a position starting from 0 on which accumulator is passed.
verification:
- method_id_static
- method_id_non_abstract
- compatible_arguments
properties:
- acc_write
- acc_read
- method_id
- call
- maybe_dynamic
exceptions:
- x_none
pseudo: |
args = []
for (i = 0; i < 4; ++i)
if (i < imm) then
args[i] = regs[i]
else if (i == imm) then
args[i] = acc
else
args[i] = regs[i - 1]
end
end
method = resolve(method_id)
acc = call(method, args)
semantics: |-
skip
instructions:
- sig: call.acc.short method_id, v:in:top, imm:u1
acc: inout:top
format: [op_v_4_imm_4_id_16]
opcode_idx: [0x99]
- sig: call.acc method_id, v1:in:top, v2:in:top, v3:in:top, imm:u2
acc: inout:top
format: [op_v1_4_v2_4_v3_4_imm_4_id_16]
opcode_idx: [0x9a]
- title: Object calls
description: >
Call indicated object method, i.e. create new frame, pass values of arguments and
continue execution from the first instruction of a method.
Callee should treat accumulator value as undefined and cannot use it until accumulator
definition in the new frame.
Result (if any) is returned in accumulator (see 'Calling sequence' chapter for more details).
Method, its class and the number of argument is resolved by given method_id in runtime
constant-pool based on object reference using language-specific semantics (currently only Java
virtual methods are supported, further extensions are TBD).
Object reference is passed in the first source register, arguments are passed starting from
the second source register in the same order as in method signature.
Non-range instructions can be used to pass up to 4 arguments (including object reference).
Unused register slot values will be discarded and corresponding registers will not be
passed to the callee).
For methods with more arguments range kinds of instruction are to be used, which takes
the needed number of arguments starting from 'vs' register (including object reference).
verification:
- method_id_non_static
- compatible_arguments
- method_id_accessible
exceptions:
- x_null
- x_abstract
properties:
- acc_write
- method_id
- call
- call_virt
pseudo: |
if args[0] == null then
throw NullPointerException
end
method = resolve(method_id)
if is_abstract(method) then
throw AbstractMethodError
end
acc = call(method, args)
semantics: |-
skip
instructions:
- sig: call.virt.short method_id, v1:in:top, v2:in:top
acc: out:top
format: [op_v1_4_v2_4_id_16]
opcode_idx: [0x9b]
- sig: call.virt method_id, v1:in:top, v2:in:top, v3:in:top, v4:in:top
acc: out:top
format: [op_v1_4_v2_4_v3_4_v4_4_id_16]
opcode_idx: [0x9c]
- sig: call.virt.range method_id, v:in:top
acc: out:top
format: [op_v_8_id_16]
opcode_idx: [0x9d]
- title: Object calls with accumulator as input
description: >
Call indicated object method, i.e. create new frame, pass values of arguments and
continue execution from the first instruction of a method.
Callee should treat accumulator value as undefined and cannot use it until accumulator
definition in the new frame.
Result (if any) is returned in accumulator (see 'Calling sequence' chapter for more details).
Method, its class and the number of argument is resolved by given method_id in runtime
constant-pool based on object reference using language-specific semantics (currently only Java
virtual methods are supported, further extensions are TBD).
Object reference is passed in the first source register, arguments are passed starting from
the second source register in the same order as in method signature.
Non-range instructions can be used to pass up to 4 arguments (including object reference).
Unused register slot values will be discarded and corresponding registers will not be
passed to the callee).
Immediate operand encodes a position starting from 0 on which accumulator is passed.
verification:
- method_id_non_static
- compatible_arguments
- method_id_accessible
exceptions:
- x_null
- x_abstract
properties:
- acc_write
- acc_read
- method_id
- call
- call_virt
pseudo: |
args = []
for (i = 0; i < 4; ++i)
if (i < imm) then
args[i] = regs[i]
else if (i == imm) then
args[i] = acc
else
args[i] = regs[i - 1]
end
end
if args[0] == null then
throw NullPointerException
end
method = resolve(method_id)
if is_abstract(method) then
throw AbstractMethodError
end
acc = call(method, args)
semantics: |-
skip
instructions:
- sig: call.virt.acc.short method_id, v:in:top, imm:u1
acc: inout:top
format: [op_v_4_imm_4_id_16]
opcode_idx: [0x9e]
- sig: call.virt.acc method_id, v1:in:top, v2:in:top, v3:in:top, imm:u2
acc: inout:top
format: [op_v1_4_v2_4_v3_4_imm_4_id_16]
opcode_idx: [0x9f]
- title: Dynamic move register-to-register
description: >
Move 'any' values between registers
verification:
- valid_in_dynamic_context
exceptions:
- x_none
properties:
- acc_none
- dynamic
pseudo: |
vd = vs
semantics: |
skip
instructions:
- sig: mov.dyn v1:out:any, v2:in:any
acc: none
format: [op_v1_8_v2_8, op_v1_16_v2_16]
opcode_idx: [0xa0, 0xa1]
- title: Dynamic load accumulator from register
description: >
Move 'any' value from register to accumulator
verification:
- valid_in_dynamic_context
exceptions:
- x_none
properties:
- acc_write
- dynamic
pseudo: |
acc = v
semantics: |
skip
instructions:
- sig: lda.dyn v:in:any
acc: out:any
format: [op_v_8]
opcode_idx: [0xa2]
- title: Dynamic store accumulator
description: >
Move 'any' value from accumulator to register
verification:
- valid_in_dynamic_context
exceptions:
- x_none
properties:
- acc_read
- dynamic
pseudo: |
v = acc
semantics: |
skip
instructions:
- sig: sta.dyn v:out:any
acc: in:any
format: [op_v_8]
opcode_idx: [0xa3]
- title: Dynamic load accumulator from immediate
description: >
Move immediate as 'any' value to accumulator
verification:
- valid_in_dynamic_context
exceptions:
- x_none
properties:
- acc_write
- dynamic
pseudo: |
acc = imm
semantics: |
skip
instructions:
- sig: ldai.dyn imm:i32
acc: out:any
format: [op_imm_32]
opcode_idx: [0xa4]
- sig: fldai.dyn imm:f64
acc: out:any
format: [op_imm_64]
opcode_idx: [0xa5]
properties: [acc_write, float, dynamic]
- title: Dynamic return from method
description: >
Return 'any' value in accumulator from method to its caller
verification:
- valid_in_dynamic_context
exceptions:
- x_none
properties:
- acc_read
- dynamic
- return
pseudo: |
return acc
semantics: |
skip
instructions:
- sig: return.dyn
acc: in:any
format: [op_none]
opcode_idx: [0xa6]
- title: Dynamic indirect call
description: >
Call 'any' value that represents a function, i.e. create a new frame, pass values of arguments
and continue execution from the first instruction of a method.
Callee should treat accumulator value as undefined and cannot use it until accumulator
definition in the new frame.
The callee must always return the result in the accumulator.
First source register contains a function 'any' value, and the other imm source registers contain
arguments of a function.
Non-range instructions can be used to pass up to 4 arguments (unused register slot values will
be discarded and corresponding registers will not be passed to the callee).
For methods with more arguments range kind of instruction is to be used, which takes the needed number
of arguments starting from 'v' register.
verification:
- valid_in_dynamic_context
exceptions:
- x_none
properties:
- acc_write
- dynamic
- call
pseudo: |
func, args = source_registers
acc = func(args)
semantics: |
skip
instructions:
- sig: calli.dyn.short imm, v1:in:any, v2:in:any, v3:in:any
acc: out:any
format: [op_imm_4_v1_4_v2_4_v3_4]
opcode_idx: [0xa7]
- sig: calli.dyn imm, v1:in:any, v2:in:any, v3:in:any, v4:in:any, v5:in:any
acc: out:any
format: [op_imm_4_v1_4_v2_4_v3_4_v4_4_v5_4]
opcode_idx: [0xa8]
- sig: calli.dyn.range imm, v:in:any
acc: out:any
format: [op_imm_16_v_16]
opcode_idx: [0xa9]