Introduction to AssemblyScript (Part 2)

in assemblyscript •  4 years ago 

This article is written by the CoinEx Chain lab. CoinEx Chain is the world’s first public chain exclusively designed for DEX, and will also include a Smart Chain supporting smart contracts and a Privacy Chain protecting users’ privacy.

In the last article we discussed how AssemblyScript (hereinafter referred to as AS) programs are compiled into WebAssembly (hereinafter referred to as Wasm) modules as a whole, and introduced in detail how various elements of the AS language are mapped to each section of the Wasm binary module. This article will go into functions, and we will discuss how the AS compiler uses the Wasm instruction set to implement grammatical elements. Before we start, let's briefly review the Wasm instruction set (for a detailed introduction to the Wasm module and instruction set, please refer to the previous articles):

Wasm uses stack-based virtual machine and bytecode, and its instructions can be divided into five categories:

  • Control Instructions, including structured control instructions, jump instructions, and function call instructions.

  • Parametric Instructions, only two in total, respectively drop and select.

  • Variable Instructions, including local variable instructions and global variable instructions.

  • Memory Instructions, including storage instructions and load instructions.

  • Numeric Instructions, including constant instructions, test instructions, comparison instructions, unary operation instructions, binary operation instructions, and type conversion instructions.

Next, we will introduce details about how the AS compiler uses these five types of instructions through examples. For the convenience of testing, part of the sample codes given below call external functions for assistance of which implementation is not important. The declarations of these external functions are shown as below:

declare function printI32(n: i32): void;
declare function printI64(n: i64): void;
declare function printF32(n: f32): void;
declare function printF64(n: f64): void;
declare function randomI32(): i32;

Control Instructions

As mentioned earlier, Wasm control instructions include structured control instructions (block, loop, and if-else), jump instructions (br, br_if, br_table, and return), function call instructions (call and call_indirerct), plus nop and unreachable. Among them, structured control instructions and jump instructions together can be used to implement various control structures of the AS language, such as the if-else statement, for loop statement, and switch-case statement. The call instruction can be used to implement AS function calls, and the call_indirerct instruction can be used to support first-class functions.

The if-else statement of the AS language can be directly implemented using Wasm's if-else instruction. Here is an example:

export function printEven(n: i32): void {
  if (n  % 2 == 0) {
    printI32(1);
  } else {
    printI32(0);
  }
}

The following is the compilation result (the compiled function bytecode has been decompiled into WAT. The same below):

(func $printEven (type 0) (param i32)
  (if
    (i32.rem_s (local.get 0) (i32.const 2))
    (then (call $printI32 (i32.const 0)))
    (else (call $printI32 (i32.const 1)))
  )
)

The above example also shows the usage of the callinstruction, which will not be described separately later. By the way, some simple if-elsestatements will be optimized by the AS compiler into selectinstructions. Here is an example:

export function max(a: i32, b: i32): i32 {
  if (a > b) {
    return a;
  } else {
    return b;
  }
}

The following is the compilation result:

(func $max (type 2) (param i32 i32) (result i32)
  (select
    (local.get 0)
    (local.get 1)
    (i32.gt_s (local.get 0) (local.get 1))
  )
)

Loop statements such as forwhile, and do-whilein the AS language can be implemented with Wasm's loopinstruction. Note that the loopinstruction cannot automatically form a loop, so it must be used with jump instructions like brbr_ifor br_table. Let's look at a slightly more complicated example:

export function printNums(n: i32): void {
  for (let i: i32 = 0; i < n; i++) {
    printI32(i);
    if (i == 100) {
      break;
    }
  }
}

This example shows the usage of loopblockbr, and br_ifinstructions. Here is the compilation result:

(func $printNums (type 0) (param i32)
  (local i32)
  (loop  ;; label = @1
    (if  ;; label = @2
      (i32.lt_s (local.get 1) (local.get 0))
      (then
        (block  ;; label = @3
          (call $printI32 (local.get 1))
          (br_if 0 (;@3;)
            (i32.eq (local.get 1) (i32.const 100)))
          (local.set 1
            (i32.add (local.get 1) (i32.const 1)))
          (br 2 (;@1;))
        ) ;; end of block
      ) ;; end of then
    ) ;; end of if
  ) ;; end of loop
)

The switch-casestatement of the AS language can be implemented with Wasm's br_tableinstruction. The following is an example:

exportfunctionmul100(n: i32): i32{
 switch(n) {
   case1: return100;
   case2: return200;
   case3: return300;
   default: returnn*100;
}
}export function mul100(n: i32): i32 {
  switch (n) {
    case 1: return 100;
    case 2: return 200;
    case 3: return 300;
    default: return n * 100;
  }
}

In addition to the br_tableinstruction, this example also shows the usage of the returninstruction. Here is the compilation result:

(func $mul100 (type 1) (param i32) (result i32)
  (block  ;; label = @1
    (block  ;; label = @2
      (block  ;; label = @3
        (block  ;; label = @4
          (br_table 0 (;@4;) 1 (;@3;) 2 (;@2;) 3 (;@1;)
            (i32.sub (local.get 0) (i32.const 1))))
        (return (i32.const 100)))
      (return (i32.const 200)))
    (return (i32.const 300)))
  (i32.mul (local.get 0) (i32.const 100))
)

The first-class functions in the AS language are similar to the function pointers in languages such as C/C++, and can be implemented with the call_indirectinstruction. Let's look at this example:

type OP = (a: i32, b: i32) => i32;
function add(a: i32, b: i32): i32 { return a + b; }
function sub(a: i32, b: i32): i32 { return a - b; }
function mul(a: i32, b: i32): i32 { return a * b; }
function div(a: i32, b: i32): i32 { return a / b; }
export function calc(a: i32, b: i32, op: i32): i32 {
  return getOp(op)(a, b);
}
function getOp(op: i32): OP {
  switch (op) {
    case 1: return add;
    case 2: return sub;
    case 3: return mul;
    case 4: return div;
    default: return add;
  }
}

The compilation result is shown as below. Please pay attention to the tableelemfields, as well as the instructions of the calc()function:

(module
  (type (;0;) (func (param i32 i32) (result i32)))
  (type (;1;) (func (param i32) (result i32)))
  (type (;2;) (func (param i32 i32 i32) (result i32)))
  (func $add (type 0) (i32.add (local.get 0) (local.get 1)))
  (func $sub (type 0) (i32.sub (local.get 0) (local.get 1)))
  (func $mul (type 0) (i32.mul (local.get 0) (local.get 1)))
  (func $div (type 0) (i32.div_s (local.get 0) (local.get 1)))
  (func $getOp (type 1) (param i32) (result i32) (;; ommitted ;;))
  (func $calc (type 2) (param i32 i32 i32) (result i32)
    (call_indirect (type 0)
      (local.get 0)
      (local.get 1)
      (call $getOp (local.get 2))
    )
  )
  (table (;0;) 5 funcref)
  (memory (;0;) 0)
  (export "memory" (memory 0))
  (export "calc" (func $calc))
  (export "getOp" (func $getOp))
  (elem (;0;) (i32.const 1) func $add $sub $mul $div)
)

In the end of this section, let's talk about the unreachableinstruction. AS is designed to support exceptionsafter the Wasm exception handling proposalis passed. For the time being, an exception thrown will cause the abort()function to be called. We can disable abort by adding the compiler option --use abort=so that the compiler will replace the abort()function call with an unreachableinstruction. In addition, we can also explicitly insert an unreachableinstruction by directly calling the low-level built-in unreachable()function. Here is an example:

export function crash2(): void {
  unreachable();
}

The compilation result is also simple:

(func $crash2 (type 1)
  (unreachable)
)

Parametric Instructions

The parametric instructions are relatively simple, with only drop and select. We’ve mentioned the select instruction in the introduction of the if-else statement, so will be no more details here. The drop instruction can be used to pop the extra operands at the top of the operand stack and throw them away. Let's look at a simple example:

export function dropRandom(): void {
  randomI32();
}

The compilation result is also very simple:

(func $dropRandom (type 0)
  (drop (call $randomI32))
)

Variable Instructions

There are three local variable instructions: local.get, local.set, and local.tee. If optimization is not considered, every AS function can be compiled into a Wasm function by the compiler. Both reading and writing of function parameters and local variables can be completed by local variable instructions. Let's look at an example:

export function addLocals(a: i32, b: i32): i32 {
  let c: i32 = a + b;
  return c;
}

The following is the compilation result (for better observation of the result, the compiler optimization is turned off during the compiling of part of the sample code. The same below):

(func $addLocals (type 1) (param i32 i32) (result i32)
  (local i32)
  (local.set 2 (i32.add (local.get 0) (local.get 1)))
  (local.get 2)
)

There are only two global variable instructions: global.getand global.set. The global variables of the AS language can be directly implemented using Wasm global variables, and they can be read and write through global variable instructions. Let’s look at an example:

let a: i32;
let b: i32;
let c: i32;
export function addGlobals(): void {
  c = a + b;
}

The complete compilation result is as shown below:

(module
  (type (;0;) (func))
  (func $addGlobals (type 0)
    (global.set 2 (i32.add (global.get 0) (global.get 1)))
  )
  (global (;0;) (mut i32) (i32.const 0))
  (global (;1;) (mut i32) (i32.const 0))
  (global (;2;) (mut i32) (i32.const 0))
  (export "addGlobals" (func $addGlobals))
)

Memory Instructions

The Wasm virtual machine can carry a piece of virtual memory, with many instructions to operate this memory. Among these instructions, the load instructions can load data from the memory and put it into the operand stack. The store instructions can take data out of the operand stack and store it in memory. In addition, the current number of memory can be obtained through the memory.size instruction, and the memory can be expanded by page through the memory.grow instruction. We will use a simple structure to better observe the usage of memory instructions. Below is the definition of this structure:

class S {
  a: i8; b: u8; c: i16; d: u16; e: i32; f: u32; g: i64; h: u64;
  i: f32; j: f64;
}

The following function shows the usage of load instructions for i32type:

export function loadI32(s: S): void {
  printI32(s.a as i32); // i32.load8_s
  printI32(s.b as i32); // i32.load8_u
  printI32(s.c as i32); // i32.load16_s
  printI32(s.d as i32); // i32.load16_u
  printI32(s.e as i32); // i32.load
  printI32(s.f as i32); // i32.load
}

The following is the compilation result. It can be seen from the offset immediate operand of the load instruction that the AS compiler has not rearranged the structure fields, but has performed proper alignment.

(func $loadI32 (type 0) (param i32)
  (call $printI32 (i32.load8_s            (local.get 0)))
  (call $printI32 (i32.load8_u  offset=1  (local.get 0)))
  (call $printI32 (i32.load16_s offset=2  (local.get 0)))
  (call $printI32 (i32.load16_u offset=4  (local.get 0)))
  (call $printI32 (i32.load     offset=8  (local.get 0)))
  (call $printI32 (i32.load     offset=12 (local.get 0)))
)

The following function shows the usage of load instructions for i64type:

export function loadI64(s: S): void {
  printI64(s.a as i64); // i64.load8_s?
  printI64(s.b as i64); // i64.load8_u?
  printI64(s.c as i64); // i64.load16_s?
  printI64(s.d as i64); // i64.load16_u?
  printI64(s.e as i64); // i64.load32_s?
  printI64(s.f as i64); // i64.load32_u?
  printI64(s.g as i64); // i64.load
  printI64(s.h as i64); // i64.load
}

The compilation result is as shown below. It can be seen that, in some cases, where the i64load instructions are expected, the AS compiler uses i32load instructions with the help of extend instructions.

(func $loadI64 (type 0) (param i32)
  (call $printI64 (i64.extend_i32_s (i32.load8_s            (local.get 0))))
  (call $printI64 (i64.extend_i32_u (i32.load8_u  offset=1  (local.get 0))))
  (call $printI64 (i64.extend_i32_s (i32.load16_s offset=2  (local.get 0))))
  (call $printI64 (i64.extend_i32_u (i32.load16_u offset=4  (local.get 0))))
  (call $printI64 (i64.extend_i32_s (i32.load     offset=8  (local.get 0))))
  (call $printI64 (i64.extend_i32_u (i32.load     offset=12 (local.get 0))))
  (call $printI64 (i64.load offset=16 (local.get 0)))
  (call $printI64 (i64.load offset=24 (local.get 0)))
)

The following function shows the usage of load instructions for float type:

export function loadF(s: S): void {
  printF32(s.i); // f32.load
  printF64(s.j); // f64.load
}

The compilation result is as shown below:

(func $loadF (type 0) (param i32)
  (call $printF32 (f32.load offset=32 (local.get 0)))
  (call $printF64 (f64.load offset=40 (local.get 0)))
)

The store instructions are simpler than load instructions. The following example shows the usage of store instructions:

export function store(s: S, v: i64): void {
  s.a = v as i8;  // i32.store8
  s.b = v as u8;  // i32.store8
  s.c = v as i16; // i32.store16
  s.d = v as u16; // i32.store16
  s.e = v as i32; // i32.store
  s.f = v as u32; // i32.store
  s.g = v as i64; // i64.store
  s.h = v as u64; // i64.store
  s.i = v as f32; // f32.store
  s.j = v as f64; // f64.store
}

The compilation result is as shown below:

(func $store (type 1) (param i32 i64)
  (i32.store8            (local.get 0) (i32.wrap_i64 (local.get 1)))
  (i32.store8  offset=1  (local.get 0) (i32.wrap_i64 (local.get 1)))
  (i32.store16 offset=2  (local.get 0) (i32.wrap_i64 (local.get 1)))
  (i32.store16 offset=4  (local.get 0) (i32.wrap_i64 (local.get 1)))
  (i32.store   offset=8  (local.get 0) (i32.wrap_i64 (local.get 1)))
  (i32.store   offset=12 (local.get 0) (i32.wrap_i64 (local.get 1)))
  (i64.store   offset=16 (local.get 0) (local.get 1))
  (i64.store   offset=24 (local.get 0) (local.get 1))
  (f32.store   offset=32 (local.get 0) (f32.convert_i64_s (local.get 1)))
  (f64.store   offset=40 (local.get 0) (f64.convert_i64_s (local.get 1)))
)

Like the unreachableinstruction introduced earlier, the memory.sizeand memory.growinstructions can also be generated by built-in functions. The following is a simple example:

export function sizeAndGrow(n: i32): void {
  printI32(memory.size());
  printI32(memory.grow(n));
}

The compilation result is as shown below:

(func $sizeAndGrow (type 0) (param i32)
  (call $printI32 (memory.size))
  (call $printI32 (memory.grow (local.get 0)))
)

Numeric Instructions

As mentioned earlier, numerical instructions can be divided into constant instructions, test instructions, comparison instructions, unary and binary arithmetic instructions, and type conversion instructions.

Specifically, there are four constant instructions in total. Numerical literals in the AS language can be implemented with constant instructions. Here is an example:

export function consts(): void {
  printI32(1234); // i32.const
  printI64(5678); // i64.const
  printF32(3.14); // f32.const
  printF64(2.71); // f64.const
}

Below is the compilation result:

(func consts (type 1)
  (call $printI32 (i32.const 1234))
  (call $printI64 (i64.const 5678))
  (call $printF32 (f32.const 0x1.91eb86p+1 (;=3.14;)))
  (call $printF64 (f64.const 0x1.5ae147ae147aep+1 (;=2.71;)))
)

There are only two test instructions: i32.eqzand i64.eqz. The following example shows the usage of i32.eqzinstruction:

export function testOps(a: i32): void {
  if (a == 0) { // i32.eqz
    printI32(123);
  }
}

The following is the compilation result:

(func $testOps (type 0) (param i32)
  (if (i32.eqz (local.get 0))
    (then (call $printI32 (i32.const 123)))
  )
)

The relational operators supported by the AS language can be implemented with comparison instructions. The following example shows the usage of comparison instructions for i32type:

export function relOps(a: i32, b: i32, c: u32, d:  u32): void {
  if (a == b) { printI32(0); } // i32.eq
  if (a != b) { printI32(1); } // i32.ne
  if (a <  b) { printI32(2); } // i32.lt_s
  if (c <  d) { printI32(3); } // i32.lt_u
  if (a >  b) { printI32(4); } // i32.gt_s
  if (c >  d) { printI32(5); } // i32.gt_u
  if (a <= b) { printI32(6); } // i32.le_s
  if (c <= d) { printI32(7); } // i32.le_u
  if (a >= b) { printI32(8); } // i32.ge_s
  if (c >= d) { printI32(9); } // i32.ge_u
}

Here is the compilation result:

(func relOps (type 2) (param i32 i32 i32 i32)
  (if (i32.eq (local.get 0) (local.get 1))
    (then (call $printI32 (i32.const 0))))
  (if (i32.ne (local.get 0) (local.get 1))
    (then (call $printI32 (i32.const 1))))
  (if (i32.lt_s (local.get 0) (local.get 1))
    (then (call $printI32 (i32.const 2))))
  (if (i32.lt_u (local.get 2) (local.get 3))
    (then (call $printI32 (i32.const 3))))
  (if (i32.gt_s (local.get 0) (local.get 1))
    (then (call $printI32 (i32.const 4))))
  (if (i32.gt_u (local.get 2) (local.get 3))
    (then (call $printI32 (i32.const 5))))
  (if (i32.le_s (local.get 0) (local.get 1))
    (then (call $printI32 (i32.const 6))))
  (if (i32.le_u (local.get 2) (local.get 3))
    (then (call $printI32 (i32.const 7))))
  (if (i32.ge_s (local.get 0) (local.get 1))
    (then (call $printI32 (i32.const 8))))
  (if (i32.ge_u (local.get 2) (local.get 3))
    (then (call $printI32 (i32.const 9))))
)

Except for the negate operation for floating-point numbers, other unary arithmetic instructions are not directly used by the AS compiler, but can be generated by built-in functions. The following example shows the usage of i32and f32unary arithmetic instructions:

export function unOps(a: i32, b: f32): void {
  printI32(clz<i32>(a));     // i32.clz
  printI32(ctz<i32>(a));     // i32.ctz
  printI32(popcnt<i32>(a));  // i32.popcnt
  printF32(abs<f32>(b));     // f32.abs
  printF32(-b);              // f32.neg
  printF32(sqrt<f32>(b));    // f32.sqrt
  printF32(floor<f32>(b));   // f32.floor
  printF32(trunc<f32>(b));   // f32.trunc
  printF32(nearest<f32>(b)); // f32.nearest
}

The compilation result is as shown below:

(func unOps (type 3) (param i32 f32 f32)
  (call $printI32 (i32.clz     (local.get 0)))
  (call $printI32 (i32.ctz     (local.get 0)))
  (call $printI32 (i32.popcnt  (local.get 0)))
  (call $printF32 (f32.abs     (local.get 1)))
  (call $printF32 (f32.neg     (local.get 1)))
  (call $printF32 (f32.sqrt    (local.get 1)))
  (call $printF32 (f32.floor   (local.get 1)))
  (call $printF32 (f32.trunc   (local.get 1)))
  (call $printF32 (f32.nearest (local.get 1)))
)

The binary operators supported by AS language can be implemented with binary operation instructions. The following example shows the usage of binary operation instructions for i32type:

export function binOps(a: i32, b: i32, c: u32, d: u32, e: f32, f: f32): void {
  printI32(a + b);           // i32.add
  printI32(a - b);           // i32.sub
  printI32(a * b);           // i32.mul
  printI32(a / b);           // i32.div_s
  printI32(c / d);           // i32.div_u
  printI32(a % b);           // i32.rem_s
  printI32(c % d);           // i32.rem_u
  printI32(a & b);           // i32.and
  printI32(a | b);           // i32.or
  printI32(a ^ b);           // i32.xor
  printI32(a << b);          // i32.shl
  printI32(a >> b);          // i32.shr_s
  printI32(a >>> b);         // i32.shr_u
  printI32(rotl<i32>(a, b)); // i32.rotl
  printI32(rotr<i32>(a, b)); // i32.rotr
}

Since the AS language does not have a "rotate" operator, we can only generate rotate instructions through built-in functions. The following is the compilation result:

(func binOps (type 3) (param i32 i32 i32 i32 f32 f32)
  (call $printI32 (i32.add      (local.get 0) (local.get 1)))
  (call $printI32 (i32.sub      (local.get 0) (local.get 1)))
  (call $printI32 (i32.mul      (local.get 0) (local.get 1)))
  (call $printI32 (i32.div_s    (local.get 0) (local.get 1)))
  (call $printI32 (i32.div_s    (local.get 2) (local.get 3)))
  (call $printI32 (i32.rem_s    (local.get 0) (local.get 1)))
  (call $printI32 (i32.rem_s    (local.get 2) (local.get 3)))
  (call $printI32 (i32.and      (local.get 0) (local.get 1)))
  (call $printI32 (i32.or       (local.get 0) (local.get 1)))
  (call $printI32 (i32.xor      (local.get 0) (local.get 1)))
  (call $printI32 (i32.shl      (local.get 0) (local.get 1)))
  (call $printI32 (i32.shr_s    (local.get 0) (local.get 1)))
  (call $printI32 (i32.shr_u    (local.get 0) (local.get 1)))
  (call $printI32 (i32.rotl     (local.get 0) (local.get 1)))
  (call $printI32 (i32.rotr     (local.get 0) (local.get 1)))
)

The type conversion operation in the AS language can be immplemented by type conversion instructions. Here is an example:

export function cvtOps(a: i32, b: i64, c: u32, d: u64, e: f32, f: f64): void {
  printI32(b as i32); // i32.wrap_i64
  printI32(e as i32); // i32.trunc_f32_s
  printI32(e as u32); // i32.trunc_f32_u
  printI32(f as i32); // i32.trunc_f64_s
  printI32(f as u32); // i32.trunc_f64_u
  printI64(a);        // i64.extend_i32_s
  printI64(a as u32); // i64.extend_i32_u
  printI64(e as i64); // i64.trunc_f32_s
  printI64(e as u64); // i64.trunc_f32_u
  printI64(f as i64); // i64.trunc_f64_s
  printI64(f as u64); // i64.trunc_f64_u
  printF32(a as f32); // f32.convert_i32_s
  printF32(c as f32); // f32.convert_i32_u
  printF32(b as f32); // f32.convert_i64_s
  printF32(d as f32); // f32.convert_i64_u
  printF32(f as f32); // f32.demote_f64
  printF64(a as f64); // f64.convert_i32_s
  printF64(c as f64); // f64.convert_i32_u
  printF64(b as f64); // f64.convert_i64_s
  printF64(d as f64); // f64.convert_i64_u
  printF64(e);        // f64.promote_f32
  printI32(reinterpret<i32>(e)); // i32.reinterpret_f32
  printI64(reinterpret<i64>(f)); // i64.reinterpret_f64
  printF32(reinterpret<f32>(a)); // f32.reinterpret_i32
  printF64(reinterpret<f64>(b)); // f64.reinterpret_i64
}

The compilation result is shown as below:

(func cvtOps (type 4) (param i32 i64 i32 i64 f32 f64)
  (call $printI32 (i32.wrap_i64        (local.get 1)))
  (call $printI32 (i32.trunc_f32_s     (local.get 4)))
  (call $printI32 (i32.trunc_f32_u     (local.get 4)))
  (call $printI32 (i32.trunc_f64_s     (local.get 5)))
  (call $printI32 (i32.trunc_f64_u     (local.get 5)))
  (call $printI64 (i64.extend_i32_s    (local.get 0)))
  (call $printI64 (i64.extend_i32_u    (local.get 0)))
  (call $printI64 (i64.trunc_f32_s     (local.get 4)))
  (call $printI64 (i64.trunc_f32_u     (local.get 4)))
  (call $printI64 (i64.trunc_f64_s     (local.get 5)))
  (call $printI64 (i64.trunc_f64_u     (local.get 5)))
  (call $printF32 (f32.convert_i32_s   (local.get 0)))
  (call $printF32 (f32.convert_i32_u   (local.get 2)))
  (call $printF32 (f32.convert_i64_s   (local.get 1)))
  (call $printF32 (f32.convert_i64_u   (local.get 3)))
  (call $printF32 (f32.demote_f64      (local.get 5)))
  (call $printF64 (f64.convert_i32_s   (local.get 0)))
  (call $printF64 (f64.convert_i32_u   (local.get 2)))
  (call $printF64 (f64.convert_i64_s   (local.get 1)))
  (call $printF64 (f64.convert_i64_u   (local.get 3)))
  (call $printF64 (f64.promote_f32     (local.get 4)))
  (call $printI32 (i32.reinterpret_f32 (local.get 4)))
  (call $printI64 (i64.reinterpret_f64 (local.get 5)))
  (call $printF32 (f32.reinterpret_i32 (local.get 0)))
  (call $printF64 (f64.reinterpret_i64 (local.get 1)))
)

Summary

In this article we discussed how the AS compiler implements AS syntax elements through various Wasm instructions. In simple terms: various control structures are implemented through control instructions, local variables and global variables are read and written through variable instructions, memory operations are performed through memory instructions, and operators and type conversions are implemented through numerical instructions. In the following articles, we will discuss in depth how AS implements object-oriented programming and automatic memory management.



Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!