Обзор возможностей TypeScript Native (AOT) Compiler

от автора

Абстракт

Данная статья является продолжением предыдущей статьи о TypeScript Native (AOT) Compiler(далее tsc) https://habr.com/ru/post/581308/. В данной статье мне хотелось бы остановиться на обзоре возможностей компилятора, а также использование компилятора из command line(коммандной строки). При написании примеров я буду подразумевать что у читателя есть минимальное понимание как пользоваться компиляторами в командной строке и наличие некоторого опыта работы с LLVM

Использование tsc

Для получения краткой справки об опциях программы нужно выполнить следующую команду

QC:\dev>tsc.exe --help  

Основана опция, которая нам потребуется это «—emit=<value>«. Эта опция важна для генерировал файлов в разных форматах вывода, таких как «obj», LLVM IR и т.д.

Генерирование OBJ (объектный файл)

Предположим, у нас есть файл example1.ts который содержит простую программу

function main() {   print("Hello World!"); }

и мы хотим получить obj. файл, то достаточно выполнить следующую команду

C:\dev>tsc.exe --emit=jit -nogc -dump-object-file -object-filename=out.o example.ts

Данная команда скомпилирует файл example.ts и сохранит OBJ в out.o. Далее сгенерированный файл можно использовать для получения исполняемого файла. Например, используя lld

Генерирование EXE (выполняемого файла)

Берем out.o из предыдущего примера и выполняем следующую команду

c:\LLVM\lld.exe -flavor link out.o /libpath:%LIBPATH% /libpath:%SDKPATH% /libpath:%UCRTPATH% msvcrt.lib ucrt.lib kernel32.lib user32.lib.lib

Альтернативный путь генерирования EXE(выполняемого файла)

C:\dev>tsc.exe --emit=llvm example1.ts 2>example1.ll C:\LLVM\llc.exe --filetype=obj -o=out.o example1.ll C:\LLVM\lld.exe -flavor link out.o

когда будете выполнять последний шаг то может понадобиться указать нужные библиотеки для C чтобы скомпилировать в .Exe файл (в конце статьи я приведу свои batch файлы для более детальной информации по компиляции)

Выполнение TypeScript кода без компиляции

Если нам нужно только посмотреть результат работы программы, то достаточно ввести следующую команду

C:\dev>tsc.exe --jit example1.ts

Шаги компилятора для генерирования исполняемого кода

После того как мы ознакомились с базовыми командами программы для командной строки, предлагаю посмотреть, как tsc компилирует программу example1.ts

C:\dev>tsc.exe --emit=mlir example1.ts

После чего мы получим вывод в MLIR (больше о MLIR тут https://mlir.llvm.org/)

module @"1.ts"  {   ts.Func @main () -> ()  {     "ts.Entry"() : () -> ()     %0 = ts.UnresolvedSymbolRef {identifier = @print} : none     %1 = ts.Constant {value = "Hello World!"} : !ts.string     ts.Print(%1) : !ts.string     "ts.Exit"() : () -> ()   } }

TypeScript файл будет преобразован в псевдоязык, который описывает логику программы. Этот промежуточный шаг позволяет компилятору проводить анализ кода и выполнять некоторую оптимизацию на данном этапе.

Что бы получить обработанной код нужно ввести следующую команду

C:\dev>tsc.exe --emit=mlir-affine example1.ts

Мы получим следующий листинг.

module @"example1.ts"  {   ts.Func @main () -> ()  {     %0 = ts.Constant {value = "Hello World!"} : !ts.string     ts.Print(%0) : !ts.string     ts.ReturnInternal   } }

Что нам говорит следующий псевдокод, что есть программа «example1.ts» которая имеет функцию «@main» которая в свою очередь загружает константу в параметр псевдокоманды «Print» и выходит из функции. Как не трудно догадаться, что при выполнении данной программы, мы увидим нашу строку вывода «Hello World!».

Дальнейшим шагом хотелось бы посмотреть выполняемый код близкий к машинному коду. Для этого мы выполним следующую команду

C:\dev>tsc.exe --emit=mlir-llvm example1.ts

И получаем результат выполнения

module @"1.ts"  {   llvm.func @puts(!llvm.ptr<i8>) -> i32   llvm.mlir.global internal constant @s_10092224619179044402("Hello World!\00")   llvm.func @main() {     %0 = llvm.mlir.addressof @s_10092224619179044402 : !llvm.ptr<array<13 x i8>>     %1 = llvm.mlir.constant(0 : i64) : i64     %2 = llvm.getelementptr %0[%1, %1] : (!llvm.ptr<array<13 x i8>>, i64, i64) -> !llvm.ptr<i8>     %3 = llvm.call @puts(%2) : (!llvm.ptr<i8>) -> i32     llvm.return   } } 

Но этого еще мало, чтобы получить файл, который мы может «скормить» llс для получения obj-файла нам нужно выполнить команду

C:\dev>tsc.exe --emit=llvm example1.ts

Получаем вывод

; ModuleID = 'LLVMDialectModule' source_filename = "LLVMDialectModule" target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-windows-msvc"  @s_10092224619179044402 = internal constant [13 x i8] c"Hello World!\00"  declare i32 @puts(i8*)  define void @main() {   %1 = call i32 @puts(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @s_10092224619179044402, i64 0, i64 0)), !d   ret void }  

Только что мы прошлись по основным этапам компиляции программы. Хотелось бы добавить, что шаги по оптимизации обычно выполняются на этапе, когда мы выполнили команду с параметром «—emit=mlir-affine»

Далее давайте посмотрим какой код может компилировать программа (я выбрал только более-менее интересные примеры)

Параметры функций по умолчанию

В следующем куске кода мы посмотрим, как можно использовать параметры по умолчанию

function defaultArgs(x: number, y = 3, z = 7) {     return x + y + z; }

И вариант, когда значение по умолчанию указывать не нужно

function optargs(x: number, y?: number, z?: number) {     if (y == undefined) y = 0;     return x + y; }

В первом варианте параметры «и «z» указаны как параметры со значением, а во втором параметры «и «z» указаны как опциональные параметры. Заметьте, что в первом случае мы не указывает тип параметра т.к. тип параметра определяется из значения по умолчанию.

Вызов данных функций будет выглядеть следующим образом

defaultArgs(1); defaultArgs(1, 4); defaultArgs(1, 4, 8);  optargs(1); optargs(1, 2); optargs(1, 2, 3);

Полный пример кода (обратите внимание что декларировать функции можно в любом порядке)

function defaultArgs(x: number, y = 3, z = 7) {     return x + y + z; }  function main() {     defaultArgs(1);     defaultArgs(1, 4);     defaultArgs(1, 4, 8);      optargs(1);     optargs(1, 2);     optargs(1, 2, 3); }  function optargs(x: number, y?: number, z?: number) {     if (y == undefined) y = 0;     return x + y; } 

И получаемый псевдокод, из которого будет сгенерирован выполняемый файл (для этого нужно выполнить команду tsc —emit=mlir example1.ts)

module @"C:\\temp\\1.ts"  {   ts.Func @defaultArgs (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number {sym_visibility = "private"}  {   ^bb0(%arg0: !ts.number, %arg1: !ts.optional<i32>, %arg2: !ts.optional<i32>):  // no predecessors     %0 = "ts.Entry"() : () -> !ts.ref<!ts.number>     %1 = "ts.Param"(%arg0) : (!ts.number) -> !ts.ref<!ts.number>     %2 = "ts.ParamOpt"(%arg1) ( {       %11 = ts.Constant {value = 3 : i32} : i32       ts.DefaultValue %11 : i32     }) : (!ts.optional<i32>) -> !ts.ref<i32>     %3 = "ts.ParamOpt"(%arg2) ( {       %11 = ts.Constant {value = 7 : i32} : i32       ts.DefaultValue %11 : i32     }) : (!ts.optional<i32>) -> !ts.ref<i32>     %4 = ts.Load(%1) : !ts.ref<!ts.number> -> !ts.number     %5 = ts.Load(%2) : !ts.ref<i32> -> i32     %6 = ts.Cast %5 : i32 to !ts.number     %7 = ts.ArithmeticBinary %4(39) %6 : !ts.number, !ts.number -> !ts.number     %8 = ts.Load(%3) : !ts.ref<i32> -> i32     %9 = ts.Cast %8 : i32 to !ts.number     %10 = ts.ArithmeticBinary %7(39) %9 : !ts.number, !ts.number -> !ts.number     "ts.ReturnVal"(%10, %0) : (!ts.number, !ts.ref<!ts.number>) -> ()     "ts.Exit"() : () -> ()   }   ts.Func @main () -> ()  {     "ts.Entry"() : () -> ()     %0 = ts.SymbolRef {identifier = @defaultArgs} : (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number     %1 = ts.Constant {value = 1 : i32} : i32     %2 = ts.Cast %1 : i32 to !ts.number     %3 = ts.Undef : !ts.optional<i32>     %4 = ts.Undef : !ts.optional<i32>     %5 = ts.CallIndirect %0(%2, %3, %4) : (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number     %6 = ts.SymbolRef {identifier = @defaultArgs} : (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number     %7 = ts.Constant {value = 1 : i32} : i32     %8 = ts.Cast %7 : i32 to !ts.number     %9 = ts.Constant {value = 4 : i32} : i32     %10 = ts.Cast %9 : i32 to !ts.optional<i32>     %11 = ts.Undef : !ts.optional<i32>     %12 = ts.CallIndirect %6(%8, %10, %11) : (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number     %13 = ts.SymbolRef {identifier = @defaultArgs} : (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number     %14 = ts.Constant {value = 1 : i32} : i32     %15 = ts.Cast %14 : i32 to !ts.number     %16 = ts.Constant {value = 4 : i32} : i32     %17 = ts.Cast %16 : i32 to !ts.optional<i32>     %18 = ts.Constant {value = 8 : i32} : i32     %19 = ts.Cast %18 : i32 to !ts.optional<i32>     %20 = ts.CallIndirect %13(%15, %17, %19) : (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number     %21 = ts.SymbolRef {identifier = @optargs} : (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number     %22 = ts.Constant {value = 1 : i32} : i32     %23 = ts.Cast %22 : i32 to !ts.number     %24 = ts.Undef : !ts.optional<!ts.number>     %25 = ts.Undef : !ts.optional<!ts.number>     %26 = ts.CallIndirect %21(%23, %24, %25) : (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number     %27 = ts.SymbolRef {identifier = @optargs} : (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number     %28 = ts.Constant {value = 1 : i32} : i32     %29 = ts.Cast %28 : i32 to !ts.number     %30 = ts.Constant {value = 2 : i32} : i32     %31 = ts.Cast %30 : i32 to !ts.optional<!ts.number>     %32 = ts.Undef : !ts.optional<!ts.number>     %33 = ts.CallIndirect %27(%29, %31, %32) : (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number     %34 = ts.SymbolRef {identifier = @optargs} : (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number     %35 = ts.Constant {value = 1 : i32} : i32     %36 = ts.Cast %35 : i32 to !ts.number     %37 = ts.Constant {value = 2 : i32} : i32     %38 = ts.Cast %37 : i32 to !ts.optional<!ts.number>     %39 = ts.Constant {value = 3 : i32} : i32     %40 = ts.Cast %39 : i32 to !ts.optional<!ts.number>     %41 = ts.CallIndirect %34(%36, %38, %40) : (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number     "ts.Exit"() : () -> ()   }   ts.Func @optargs (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number {sym_visibility = "private"}  {   ^bb0(%arg0: !ts.number, %arg1: !ts.optional<!ts.number>, %arg2: !ts.optional<!ts.number>):  // no predecessors     %0 = "ts.Entry"() : () -> !ts.ref<!ts.number>     %1 = "ts.Param"(%arg0) : (!ts.number) -> !ts.ref<!ts.number>     %2 = "ts.Param"(%arg1) : (!ts.optional<!ts.number>) -> !ts.ref<!ts.optional<!ts.number>>     %3 = "ts.Param"(%arg2) : (!ts.optional<!ts.number>) -> !ts.ref<!ts.optional<!ts.number>>     %4 = ts.Load(%2) : !ts.ref<!ts.optional<!ts.number>> -> !ts.optional<!ts.number>     %5 = ts.Undef : !ts.optional<!ts.undef_ph>     %6 = ts.LogicalBinary %4(34) %5 : !ts.optional<!ts.number>, !ts.optional<!ts.undef_ph> -> !ts.boolean     "ts.If"(%6) ( {       %11 = ts.Load(%2) : !ts.ref<!ts.optional<!ts.number>> -> !ts.optional<!ts.number>       %12 = ts.Constant {value = 0 : i32} : i32       %13 = ts.Cast %12 : i32 to !ts.optional<!ts.number>       ts.Store %13, %2 : !ts.optional<!ts.number> -> !ts.ref<!ts.optional<!ts.number>>       "ts.Result"() : () -> ()     },  {     }) : (!ts.boolean) -> ()     %7 = ts.Load(%1) : !ts.ref<!ts.number> -> !ts.number     %8 = ts.Load(%2) : !ts.ref<!ts.optional<!ts.number>> -> !ts.optional<!ts.number>     %9 = ts.Cast %8 : !ts.optional<!ts.number> to !ts.number     %10 = ts.ArithmeticBinary %7(39) %9 : !ts.number, !ts.number -> !ts.number     "ts.ReturnVal"(%10, %0) : (!ts.number, !ts.ref<!ts.number>) -> ()     "ts.Exit"() : () -> ()   } }

Данный код содержит части, которые могут быть упрощены на стадии оптимизации. Давайте посмотрим, что получиться на стадии оптимизации (для этого нам нужно выполнить команду tsc —emit=mlir-affine example1.ts)

module @"C:\\temp\\1.ts"  {   ts.Func @defaultArgs (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number {sym_visibility = "private"}  {   ^bb0(%arg0: !ts.number, %arg1: !ts.optional<i32>, %arg2: !ts.optional<i32>):  // no predecessors     %c7_i32 = constant 7 : i32     %c3_i32 = constant 3 : i32     %0 = ts.Variable() {false} :  -> !ts.ref<!ts.number>     %1 = ts.Variable(%arg0) {false} : !ts.number -> !ts.ref<!ts.number>     %2 = "ts.HasValue"(%arg1) : (!ts.optional<i32>) -> !ts.boolean     %3 = ts.Cast %2 : !ts.boolean to i1     cond_br %3, ^bb1, ^bb2(%c3_i32 : i32)   ^bb1:  // pred: ^bb0     %4 = "ts.Value"(%arg1) : (!ts.optional<i32>) -> i32     br ^bb2(%4 : i32)   ^bb2(%5: i32):  // 2 preds: ^bb0, ^bb1     %6 = ts.Variable(%5) {false} : i32 -> !ts.ref<i32>     %7 = "ts.HasValue"(%arg2) : (!ts.optional<i32>) -> !ts.boolean     %8 = ts.Cast %7 : !ts.boolean to i1     cond_br %8, ^bb3, ^bb4(%c7_i32 : i32)   ^bb3:  // pred: ^bb2     %9 = "ts.Value"(%arg2) : (!ts.optional<i32>) -> i32     br ^bb4(%9 : i32)   ^bb4(%10: i32):  // 2 preds: ^bb2, ^bb3     %11 = ts.Variable(%10) {false} : i32 -> !ts.ref<i32>     %12 = ts.Load(%1) : !ts.ref<!ts.number> -> !ts.number     %13 = ts.Load(%6) : !ts.ref<i32> -> i32     %14 = ts.Cast %13 : i32 to !ts.number     %15 = ts.ArithmeticBinary %12(39) %14 : !ts.number, !ts.number -> !ts.number     %16 = ts.Load(%11) : !ts.ref<i32> -> i32     %17 = ts.Cast %16 : i32 to !ts.number     %18 = ts.ArithmeticBinary %15(39) %17 : !ts.number, !ts.number -> !ts.number     ts.Store %18, %0 : !ts.number -> !ts.ref<!ts.number>     %19 = ts.Load(%0) : !ts.ref<!ts.number> -> !ts.number     ts.ReturnInternal %19 : !ts.number   }   ts.Func @main () -> ()  {     %c3_i32 = constant 3 : i32     %c2_i32 = constant 2 : i32     %c8_i32 = constant 8 : i32     %c4_i32 = constant 4 : i32     %c1_i32 = constant 1 : i32     %0 = ts.Cast %c1_i32 : i32 to !ts.number     %1 = ts.Undef : !ts.optional<i32>     %2 = ts.Undef : !ts.optional<i32>     %3 = ts.CallInternal @defaultArgs(%0, %1, %2) : (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number     %4 = ts.Cast %c4_i32 : i32 to !ts.optional<i32>     %5 = ts.Undef : !ts.optional<i32>     %6 = ts.CallInternal @defaultArgs(%0, %4, %5) : (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number     %7 = ts.Cast %c8_i32 : i32 to !ts.optional<i32>     %8 = ts.CallInternal @defaultArgs(%0, %4, %7) : (!ts.number, !ts.optional<i32>, !ts.optional<i32>) -> !ts.number     %9 = ts.Undef : !ts.optional<!ts.number>     %10 = ts.Undef : !ts.optional<!ts.number>     %11 = ts.CallInternal @optargs(%0, %9, %10) : (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number     %12 = ts.Cast %c2_i32 : i32 to !ts.optional<!ts.number>     %13 = ts.Undef : !ts.optional<!ts.number>     %14 = ts.CallInternal @optargs(%0, %12, %13) : (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number     %15 = ts.Cast %c3_i32 : i32 to !ts.optional<!ts.number>     %16 = ts.CallInternal @optargs(%0, %12, %15) : (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number     ts.ReturnInternal   }   ts.Func @optargs (!ts.number, !ts.optional<!ts.number>, !ts.optional<!ts.number>) -> !ts.number {sym_visibility = "private"}  {   ^bb0(%arg0: !ts.number, %arg1: !ts.optional<!ts.number>, %arg2: !ts.optional<!ts.number>):  // no predecessors     %c0_i32 = constant 0 : i32     %0 = ts.Variable() {false} :  -> !ts.ref<!ts.number>     %1 = ts.Variable(%arg0) {false} : !ts.number -> !ts.ref<!ts.number>     %2 = ts.Variable(%arg1) {false} : !ts.optional<!ts.number> -> !ts.ref<!ts.optional<!ts.number>>     %3 = ts.Variable(%arg2) {false} : !ts.optional<!ts.number> -> !ts.ref<!ts.optional<!ts.number>>     %4 = ts.Load(%2) : !ts.ref<!ts.optional<!ts.number>> -> !ts.optional<!ts.number>     %5 = ts.Undef : !ts.optional<!ts.undef_ph>     %6 = ts.LogicalBinary %4(34) %5 : !ts.optional<!ts.number>, !ts.optional<!ts.undef_ph> -> !ts.boolean     %7 = ts.Cast %6 : !ts.boolean to i1     cond_br %7, ^bb1, ^bb2   ^bb1:  // pred: ^bb0     %8 = ts.Cast %c0_i32 : i32 to !ts.optional<!ts.number>     ts.Store %8, %2 : !ts.optional<!ts.number> -> !ts.ref<!ts.optional<!ts.number>>     br ^bb2   ^bb2:  // 2 preds: ^bb0, ^bb1     %9 = ts.Load(%1) : !ts.ref<!ts.number> -> !ts.number     %10 = ts.Load(%2) : !ts.ref<!ts.optional<!ts.number>> -> !ts.optional<!ts.number>     %11 = ts.Cast %10 : !ts.optional<!ts.number> to !ts.number     %12 = ts.ArithmeticBinary %9(39) %11 : !ts.number, !ts.number -> !ts.number     ts.Store %12, %0 : !ts.number -> !ts.ref<!ts.number>     %13 = ts.Load(%0) : !ts.ref<!ts.number> -> !ts.number     ts.ReturnInternal %13 : !ts.number   } } 

И наконец, чтобы посмотреть, как работает данный код нужно запустить команду tsc —emit=jit -nogc example1.ts

В дальнейшем все листинги приводить буду по минимуму из-за большого их объёма.

Итерация массивов

В следующем коде посмотрим, как компилятор может генерировать код для обхода массивов

function main() {     const trees = [[1], [2, 3], [4, 5, 6]];      for (const a of trees) {         print("array");         for (const b of a) {             print(b);         }     }      print("done."); }

Результат работы (tsc —emit=jit -nogc example1.ts)

C:\>tsc.exe --emit=jit -nogc example1.ts   array 1 array 2 3 array 4 5 6 done.

Давайте посмотрим на сгенерированный псевдокод (tsc —emit=mlir example1.ts)

module @"C:\\temp\\1.ts"  {   ts.Func @main () -> ()  {     "ts.Entry"() : () -> ()     %0 = ts.Constant {value = 1 : i32} : i32     %1 = ts.Constant {value = [1 : i32]} : !ts.const_array<i32,1>     %2 = ts.Constant {value = 2 : i32} : i32     %3 = ts.Constant {value = 3 : i32} : i32     %4 = ts.Constant {value = [2 : i32, 3 : i32]} : !ts.const_array<i32,2>     %5 = ts.Constant {value = 4 : i32} : i32     %6 = ts.Constant {value = 5 : i32} : i32     %7 = ts.Constant {value = 6 : i32} : i32     %8 = ts.Constant {value = [4 : i32, 5 : i32, 6 : i32]} : !ts.const_array<i32,3>     %9 = ts.Constant {value = [[1 : i32], [2 : i32, 3 : i32], [4 : i32, 5 : i32, 6 : i32]]} : !ts.const_array<!ts.array<i32>,3>     %10 = ts.Constant {value = 0 : i32} : i32     %11 = ts.Variable(%10) {false} : i32 -> !ts.ref<i32>     ts.For :  -> ( {       %14 = ts.Load(%11) : !ts.ref<i32> -> i32       %15 = ts.Constant {value = 3 : i32} : i32       %16 = ts.LogicalBinary %14(29) %15 : i32, i32 -> !ts.boolean       ts.Condition(%16) :      },  {       %14 = ts.Load(%11) : !ts.ref<i32> -> i32       %15 = ts.PrefixUnary %14(45) : i32 -> i32       "ts.Result"() : () -> ()     })  {       %14 = ts.Load(%11) : !ts.ref<i32> -> i32       %15 = ts.ElementRef %9[%14] : !ts.const_array<!ts.array<i32>,3>[i32] -> !ts.ref<!ts.array<i32>>       %16 = ts.Load(%15) : !ts.ref<!ts.array<i32>> -> !ts.array<i32>       %17 = ts.UnresolvedSymbolRef {identifier = @print} : none       %18 = ts.Constant {value = "array"} : !ts.string       ts.Print(%18) : !ts.string       %19 = ts.Constant {value = 0 : i32} : i32       %20 = ts.Variable(%19) {false} : i32 -> !ts.ref<i32>       ts.For :  -> ( {         %21 = ts.Load(%20) : !ts.ref<i32> -> i32         %22 = ts.Load(%15) : !ts.ref<!ts.array<i32>> -> !ts.array<i32>         %23 = "ts.LengthOf"(%22) : (!ts.array<i32>) -> i32         %24 = ts.LogicalBinary %21(29) %23 : i32, i32 -> !ts.boolean         ts.Condition(%24) :        },  {         %21 = ts.Load(%20) : !ts.ref<i32> -> i32         %22 = ts.PrefixUnary %21(45) : i32 -> i32         "ts.Result"() : () -> ()       })  {         %21 = ts.Load(%20) : !ts.ref<i32> -> i32         %22 = ts.Load(%15) : !ts.ref<!ts.array<i32>> -> !ts.array<i32>         %23 = ts.ElementRef %22[%21] : !ts.array<i32>[i32] -> !ts.ref<i32>         %24 = ts.Load(%23) : !ts.ref<i32> -> i32         %25 = ts.UnresolvedSymbolRef {identifier = @print} : none         ts.Print(%24) : i32         "ts.Result"() : () -> ()       }       "ts.Result"() : () -> ()     }     %12 = ts.UnresolvedSymbolRef {identifier = @print} : none     %13 = ts.Constant {value = "done."} : !ts.string     ts.Print(%13) : !ts.string     "ts.Exit"() : () -> ()   } }

Захват переменных функциями

function func_in_object(a: number | undefined) {     const s = {         f() {             assert(a == 11);             print(a);         },     };      s.f(); }  function main() {     func_in_object(11);     print("done."); }

Результат работы:

C:\temp>C:\tsc.exe --emit=jit -nogc example1.ts   11 done. 

В данном примере, передается параметр «a» с типом «number | undefined«. Это значит, что, «а» может быть опциональный т.е. аналог «a?: number«. Далее этот параметр будет захвачен в функции «f()» которая будет принадлежать абстрактному объекту «s«

И давайте посмотрим какой псевдокод будет сгенерирован в данном примере

module @"C:\\temp\\1.ts"  {   ts.Func @__uf14725333033948923702 (!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> () {arg_attrs = [{ts.nest}, {}], sym_visibility = "private", ts.nest}  {   ^bb0(%arg0: !ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, %arg1: !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>):  // no predecessors     "ts.Entry"() : () -> ()     %0 = "ts.Param"(%arg1) : (!ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> !ts.ref<!ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>>     %1 = ts.PropertyRef %arg0<0> : !ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>> -> !ts.ref<!ts.ref<!ts.optional<!ts.number>>>     %2 = ts.Load(%1) : !ts.ref<!ts.ref<!ts.optional<!ts.number>>> -> !ts.ref<!ts.optional<!ts.number>>     %3 = ts.UnresolvedSymbolRef {identifier = @assert} : none     %4 = ts.Load(%2) : !ts.ref<!ts.optional<!ts.number>> -> !ts.optional<!ts.number>     %5 = ts.Constant {value = 11 : i32} : i32     %6 = ts.Cast %4 : !ts.optional<!ts.number> to i32     %7 = ts.LogicalBinary %6(34) %5 : i32, i32 -> !ts.boolean     ts.Assert %7, "assert"     %8 = ts.UnresolvedSymbolRef {identifier = @print} : none     %9 = ts.Load(%2) : !ts.ref<!ts.optional<!ts.number>> -> !ts.optional<!ts.number>     ts.Print(%9) : !ts.optional<!ts.number>     "ts.Exit"() : () -> ()   }   ts.Func @func_in_object (!ts.optional<!ts.number>) -> () {sym_visibility = "private"}  {   ^bb0(%arg0: !ts.optional<!ts.number>):  // no predecessors     "ts.Entry"() : () -> ()     %0 = "ts.Param"(%arg0) : (!ts.optional<!ts.number>) -> !ts.ref<!ts.optional<!ts.number>>     %1 = ts.SymbolRef {identifier = @__uf14725333033948923702} : (!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()     %2 = ts.Load(%0) : !ts.ref<!ts.optional<!ts.number>> -> !ts.optional<!ts.number>     %3 = "ts.Capture"(%0) : (!ts.ref<!ts.optional<!ts.number>>) -> !ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>     %4 = "ts.Trampoline"(%1, %3) {allocInHeap = false} : ((!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> (), !ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>) -> ((!ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ())     %5 = ts.Constant {value = [unit]} : !ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()}>     %6 = ts.Variable(%5) {false} : !ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()}> -> !ts.ref<!ts.tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()}>>     %7 = ts.PropertyRef %6<0> : !ts.ref<!ts.tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()}>> -> !ts.bound_ref<(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()>     %8 = ts.Load(%7) : !ts.bound_ref<(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()> -> !ts.this_func<(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>,!ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()>     %9 = ts.Cast %4 : (!ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> () to (!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()     ts.Store %9, %7 : (!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> () -> !ts.bound_ref<(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()>     %10 = ts.Load(%6) : !ts.ref<!ts.tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()}>> -> !ts.tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()}>     %11 = ts.PropertyRef %6<0> : !ts.ref<!ts.tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()}>> -> !ts.bound_ref<(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()>     %12 = ts.Load(%11) : !ts.bound_ref<(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()> -> !ts.this_func<(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>,!ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()>     %13 = ts.GetThis %12 : !ts.this_func<(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>,!ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()> -> !ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>     %14 = ts.GetMethod %12 : !ts.this_func<(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>,!ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()> -> (!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()     %15 = ts.Undef : !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>     ts.CallIndirect %14(%13, %15) : (!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.object<!ts.const_tuple<{"f",(!ts.ref<!ts.tuple<{"a",!ts.ref<!ts.optional<!ts.number>>}>>, !ts.opaque) -> ()}>>) -> ()     "ts.Exit"() : () -> ()   }   ts.Func @main () -> ()  {     "ts.Entry"() : () -> ()     %0 = ts.SymbolRef {identifier = @func_in_object} : (!ts.optional<!ts.number>) -> ()     %1 = ts.Constant {value = 11 : i32} : i32     %2 = ts.Cast %1 : i32 to !ts.optional<!ts.number>     ts.CallIndirect %0(%2) : (!ts.optional<!ts.number>) -> ()     %3 = ts.UnresolvedSymbolRef {identifier = @print} : none     %4 = ts.Constant {value = "done."} : !ts.string     ts.Print(%4) : !ts.string     "ts.Exit"() : () -> ()   } } 

Интересующие строки, где псевдокоманды, которые нас интересуют, имеют имена «ts.Capture» и «ts.Trampoline«. Первая команда захватывает ссылку на объект или значение, а вторая команда создает виртуальную функцию, которая будет получать объект, содержащий ссылку на переменную «а«

Интерфейсы по требованию

В этом параграфе мы рассмотрим пример, который покажет, как можно создавать интерфейсы из классов, которые не задекларированы на уровне класса

class S { // нам не нужно указывать наличие интерфейса в декларации // class S implements IPrn     print() {         print("Hello World");     } }  interface IPrn {     print(); }  function run(iface: IPrn) {     iface.print(); }  function main() {     const s = new S();     let iface = <IPrn>s;     iface.print();     run(s);      print("done."); }

В этом коде мы создаем экземпляр интерфейса «IPrn» который не объявлен в классе «S» как это обычно требуется в С++

Использование yield в генераторах

Пример использования «yield«.

function* foo() {     let i = 1;     while (i < 3) {         yield ++i;     } }  function main() {     for (const o of foo3()) {         print(o);     } }

Результат выполнения кода

C:\>tsc.exe --emit=jit -nogc example1.ts   2 3

Примеры с использованием async/await

В этом параграфе я покажу два примера с async/await и for await

async function f(a = 1) {     return a; }  function main() {     const v = await f(2);     print(v);     print("done."); } 

Давайте посмотрим на сгенерированный псевдокод

module @"C:\\temp\\1.ts"  {   ts.Func @f (!ts.optional<i32>) -> i32 {sym_visibility = "private"}  {   ^bb0(%arg0: !ts.optional<i32>):  // no predecessors     %0 = "ts.Entry"() : () -> !ts.ref<i32>     %1 = "ts.ParamOpt"(%arg0) ( {       %3 = ts.Constant {value = 1 : i32} : i32       ts.DefaultValue %3 : i32     }) : (!ts.optional<i32>) -> !ts.ref<i32>     %2 = ts.Load(%1) : !ts.ref<i32> -> i32     "ts.ReturnVal"(%2, %0) : (i32, !ts.ref<i32>) -> ()     "ts.Exit"() : () -> ()   }   ts.Func @main () -> ()  {     "ts.Entry"() : () -> ()     %token, %results = async.execute -> !async.value<i32> {       %4 = ts.SymbolRef {identifier = @f} : (!ts.optional<i32>) -> i32       %5 = ts.Constant {value = 2 : i32} : i32       %6 = ts.Cast %5 : i32 to !ts.optional<i32>       %7 = ts.CallIndirect %4(%6) : (!ts.optional<i32>) -> i32       async.yield %7 : i32     }     %0 = async.await %results : !async.value<i32>     %1 = ts.UnresolvedSymbolRef {identifier = @print} : none     ts.Print(%0) : i32     %2 = ts.UnresolvedSymbolRef {identifier = @print} : none     %3 = ts.Constant {value = "done."} : !ts.string     ts.Print(%3) : !ts.string     "ts.Exit"() : () -> ()   } } 

И второй пример с for/await

function main() {     for await (const v of [1, 2, 3, 4, 5]) {         print(v);     } }

сгенерированный псевдокод

module @"C:\\temp\\1.ts"  {   ts.Func @main () -> ()  {     "ts.Entry"() : () -> ()     %0 = ts.Constant {value = 1 : i32} : i32     %1 = ts.Constant {value = 2 : i32} : i32     %2 = ts.Constant {value = 3 : i32} : i32     %3 = ts.Constant {value = 4 : i32} : i32     %4 = ts.Constant {value = 5 : i32} : i32     %5 = ts.Constant {value = [1 : i32, 2 : i32, 3 : i32, 4 : i32, 5 : i32]} : !ts.const_array<i32,5>     %6 = ts.Constant {value = 0 : i32} : i32     %7 = ts.Variable(%6) {false} : i32 -> !ts.ref<i32>     %8 = async.create_group     ts.For :  -> ( {       %9 = ts.Load(%7) : !ts.ref<i32> -> i32       %10 = ts.Constant {value = 5 : i32} : i32       %11 = ts.LogicalBinary %9(29) %10 : i32, i32 -> !ts.boolean       ts.Condition(%11) :      },  {       %9 = ts.Load(%7) : !ts.ref<i32> -> i32       %10 = ts.PrefixUnary %9(45) : i32 -> i32       "ts.Result"() : () -> ()     })  {       %9 = ts.Load(%7) : !ts.ref<i32> -> i32       %token = async.execute {         %11 = ts.ElementRef %5[%9] : !ts.const_array<i32,5>[i32] -> !ts.ref<i32>         %12 = ts.Load(%11) : !ts.ref<i32> -> i32         %13 = ts.UnresolvedSymbolRef {identifier = @print} : none         ts.Print(%12) : i32         async.yield       }       %10 = async.add_to_group %token, %8 : !async.token       "ts.Result"() : () -> ()     }     async.await_all %8     "ts.Exit"() : () -> ()   } } 

Интересующие нас команды в этом коде это «async.execute» которые берут всю ответственность за выполнение кода асинхронно на себя

Хочется добавить, что чтобы запустить данный код нам нужна поддержка «runtime«.

C:\>tsc.exe --emit=jit --shared-libs=TypeScriptRuntime.dll example1.ts   1 2 3 4 5

Как мы видим из исполняемой строки что бы активировать «runtime» нужно указать параметр «—shared-libs=TypeScriptRuntime.dll». Для компиляции в LLVM IR данный параметр не нужен, но если мы хотим получить obj-файл с использованием —emit=jit тогда «runtime» нужно указывать. Также «runtime» нужно указывать при использовании «Garbage Collector» (сборщика мусора)

Примеры Batch-файлов для компиляции Exe-файлов

Компиляция, когда не нужна поддержка GC, async и захвата переменных.

set FILENAME=%1 set LLVMPATH=C:\dev\TypeScriptCompiler\3rdParty\llvm\release\bin set TSCPATH=C:\dev\TypeScriptCompiler\__build\tsc\bin set LIBPATH="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64" set SDKPATH="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\um\x64" set UCRTPATH="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\ucrt\x64" set LLVMLIBPATH=C:\dev\TypeScriptCompiler\3rdParty\llvm\release\lib %TSCPATH%\tsc.exe --emit=llvm -nogc C:\temp\%FILENAME%.ts 2>%FILENAME%.ll %LLVMPATH%\llc.exe --filetype=obj -o=%FILENAME%.o %FILENAME%.ll %LLVMPATH%\lld.exe -flavor link %FILENAME%.o /libpath:%LIBPATH% /libpath:%SDKPATH% /libpath:%UCRTPATH% /defaultlib:libcmt.lib libvcruntime.lib del %FILENAME%.o  echo "RUN:..." %FILENAME%.exe 

Компиляция, когда нужна поддержка захвата переменных

set FILENAME=%1 set LLVMPATH=C:\dev\TypeScriptCompiler\3rdParty\llvm\release\bin set TSCPATH=C:\dev\TypeScriptCompiler\__build\tsc\bin set LIBPATH="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64" set SDKPATH="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\um\x64" set UCRTPATH="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\ucrt\x64" set CLANGLIBPATH=C:\dev\TypeScriptCompiler\3rdParty\llvm\release\lib\clang\13.0.0\lib\windows %TSCPATH%\tsc.exe --emit=llvm -nogc C:\temp\%FILENAME%.ts 2>%FILENAME%.ll %LLVMPATH%\llc.exe --filetype=obj -o=%FILENAME%.o %FILENAME%.ll %LLVMPATH%\lld.exe -flavor link %FILENAME%.o /libpath:%LIBPATH% /libpath:%SDKPATH% /libpath:%UCRTPATH% /libpath:%CLANGLIBPATH% /defaultlib:libcmt.lib libvcruntime.lib clang_rt.builtins-x86_64.lib del %FILENAME%.o  echo "RUN:..." %FILENAME%.exe 

Компиляция если нужна поддержка GC

set FILENAME=%1 set LLVMPATH=C:\dev\TypeScriptCompiler\3rdParty\llvm\release\bin set TSCPATH=C:\dev\TypeScriptCompiler\__build\tsc\bin set LIBPATH="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64" set SDKPATH="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\um\x64" set UCRTPATH="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\ucrt\x64" set LLVMLIBPATH=C:\dev\TypeScriptCompiler\3rdParty\llvm\release\lib set GCLIBPATH=C:\dev\TypeScriptCompiler\3rdParty\gc\Release %TSCPATH%\tsc.exe --emit=llvm C:\temp\%FILENAME%.ts 2>%FILENAME%.ll %LLVMPATH%\llc.exe --filetype=obj -o=%FILENAME%.o %FILENAME%.ll %LLVMPATH%\lld.exe -flavor link %FILENAME%.o /libpath:%LIBPATH% /libpath:%SDKPATH% /libpath:%UCRTPATH% /libpath:%GCLIBPATH% msvcrt.lib ucrt.lib kernel32.lib user32.lib gcmt-lib.lib del %FILENAME%.o  echo "RUN:..." %FILENAME%.exe 

Пример компиляции на Ubuntu

#/bin/sh ./tsc --emit=mlir-llvm -nogc /mnt/c/temp/1.ts -debug-only=llvm 2>1d.mlir ./tsc --emit=llvm -nogc /mnt/c/temp/1.ts 2>1.ll llc --filetype=obj --relocation-model=pic -o=1.o 1.ll

И если нужна поддержка try/catch/finally

#/bin/sh ./tsc --emit=llvm -nogc /mnt/c/temp/1.ts 2>1.ll llc --filetype=obj --relocation-model=pic -o=1.o 1.ll gcc -o 1.out 1.o -frtti -fexceptions -lstdc++

Пример компиляции для WASM

set FILENAME=%1 set LLVMPATH=C:\dev\TypeScriptCompiler\3rdParty\llvm\release\bin set TSCPATH=C:\dev\TypeScriptCompiler\__build\tsc\bin %TSCPATH%\tsc.exe --emit=llvm C:\temp\%FILENAME%.ts 2>%FILENAME%.ll %LLVMPATH%\llc.exe -mtriple=wasm32-unknown-unknown -O3 --filetype=obj -o=%FILENAME%.o %FILENAME%.ll %LLVMPATH%\wasm-ld.exe %FILENAME%.o -o %FILENAME%.wasm --no-entry --export-all --allow-undefined del *.o 

Больше примеров batch-файлов можно посмотреть тут TypeScriptCompiler/docs/how at main · ASDAlexander77/TypeScriptCompiler (github.com)

Заключение

Я рекомендую заглянуть в репозиторий проекта в папку test что бы посмотреть примеры кодов, которые могут быть скомпилированы tsc.

TypeScriptCompiler/tsc/test/tester/tests at main · ASDAlexander77/TypeScriptCompiler (github.com)

Если Вам понравился данный проект, то дайте мне знать какую информацию Вы бы хотели еще узнать.


ссылка на оригинал статьи https://habr.com/ru/articles/583862/