100개 언어 Speedrun: 77회: Jasmin의 JVM 조립
어느 정도에 JVM 어셈블리 단계는 불필요하다. 예를 들어 안드로이드는 서로 다른 흐름을 사용하고 심지어 버전마다 같은 흐름을 사용하지 않는다.
한 마디로 하면 일반적인 JVM의 고전적인 JVM 프로그램 집합이 어떤 모양인지 살펴보자.JVM에는 사람이 읽을 수 있는 어셈블리를 위한 도구가 포함되어 있지 않으므로 Jasmin을 사용하겠습니다.또 다른 JVM 어셈블러는 약간 다르지만, 이러한 차이는 우리의 간단한 용례에 있어서는 중요하지 않다.
안녕, 세상!
JVM에 대한 기초 지식을 알고 있다면 자바든 다른 JVM 언어든 도움이 될 것입니다.만약 그렇지 않다면, 나는 한 걸음 한 걸음 모든 것을 설명하려고 시도할 것이다.
우리 너부터 시작하자, 세상!다음은
Hello.j
입니다..class public Hello
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
.limit locals 1
.limit stack 2
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hello, World!"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
return
.end method
jasmin
을 Hello.class
으로 컴파일할 수 있습니다.$ jasmin Hello.j
Generated: Hello.class
그런 다음 java
에 클래스 이름을 전달하여 실행합니다.$ java Hello
Hello, World!
혼동을 피하기 위해 java
명령은 자바 언어와 상관없이 자바 가상 기기와만 관련이 있다.javac
명령은 Java 언어를 처리합니다.다음은
Hello.j
파일의 구조를 살펴보겠습니다.Hello.j
에 대해 우리는 먼저 .class public Hello
을 정의합니다.Object
이고 내부는 java/lang/Object
이라고 부른다.main
방법을 정의했습니다. 이것은 명령줄에서 클래스를 실행할 때 실행하는 방법입니다..method public static methodname ... .end method
은 방법 정의로 방법 내부에 각종 지령이 있는데 방법이 .method ... .end method
블록 .method public static
은 그것이 유형 방법이고 특정한 실례에 귀속되지 않는다는 것을 의미한다 이름 개편
지금까지 이것은 일리가 있다.네가 물어볼 수 있는 첫 번째 질문은 이 이름들이 도대체 무엇인지이다.
main([Ljava/lang/String;)V
Ljava/io/PrintStream;
java/io/PrintStream/println(Ljava/lang/String;)V
나는 가장 간단한 방법이 무엇인지 파손된 이름을 찾아낼 수 없다.
예를 들어,
main([Ljava/lang/String;)V
은 int main(java.lang.String[]);
을 나타냅니다.main
우선 (
시작 매개 변수 목록 [
은 Ljava/lang/String;
은 java.lang.String
유형의 대상을 나타낸다. - 분호는 명칭이 )
닫기 매개 변수 목록 V
은 void
으로 되돌아간다고 하는데 이것은 명칭 혼란은 우리가 사용하는 언어에 달려 있다.다른 JVM 언어는 자바가 지원하는 유형에 대해 자바와 호환되는 변경 사항을 사용하지만, 자바가 필요로 하는 추가 기능에 대해서는 자신의 이름 변경 방안을 제시해야 한다.JVM에서 파괴된 이름은 모두 간단한 문자열일 뿐이지만, Jasmin은 모든 내용을 정확하게 설정하기 위해 자바 이름 파괴 규칙을 따라야 한다.
안에서 안녕, 세상!메서드
네, 이 방법을 보여 주세요.
우선
.limit
개의 정의부터 시작하겠습니다. .limit locals 1
.limit stack 2
이 매개 변수는 함수에 몇 개의 국부 변수가 있는지, 그리고 최대 창고 공간이 얼마나 필요한지 지정합니다.JVM은 컨테이너 기계이기 때문에 대부분의 지령은 물건을 컨테이너에 밀어넣거나 컨테이너에서 꺼내 각종 조작을 수행한다.Jasmin이 이 계산을 자동으로 완성할 수 있다고 믿을 이유가 있습니다. 만약 당신이 지정한 숫자가 너무 낮다면 JVM은 클래스를 불러오는 것을 거부할 것입니다.
이제 스택에서 값을 푸시해야 합니다.
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hello, World!"
getstatic
은 특정 유형의 정적 값(이 예는 java.lang.System.out
유형의 java.io.PrintStream
)을 가져오고 이 값에 대한 인용을 창고 꼭대기로 보냅니다.그리고
ldc "Hello, World!"
에서 상수 값을 가져와 인용을 창고 맨 위로 가져옵니다.그리고 우리는 방법이라고 부른다.
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
invokevirtual
은 virtual
방법을 사용하는데 이 방법은 이불류에 무거운 짐을 실을 수 있다.우리는 전달하고 있는 방법의 전체 이름을 지정해야 한다.JVM은 유형 서명에 따라 스택 상단에서 사용할 매개변수를 알고 있습니다.마지막:
return
은 이 방법에서 되돌아온다.부차적인 기술 문제에 대해 모든 종류, 방법, 유형 이름, 상수 문자열 등은 바이트 코드가 아닌 상수 탱크에 저장된다.바이트 코드는 실제로 "상수 탱크의 상수 #7"과 같은 것을 가리킨다.그러나 이렇게 쓰면 지루하기 때문에 Jasmin은 적어도 우리를 위해 이렇게 많은 것을 해 주었다.
순환하다
일정 횟수를 순환하고 숫자를 출력하는 약간 복잡한 일을 합시다.
다음은 플롯 값 1부터 10까지의 주기입니다.
.class public Loop
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
.limit locals 1
.limit stack 2
iconst_1 ; push value 1 on stack
istore 0 ; save that to local variable #0
loop:
iload 0 ; push local #0 to stack
bipush 10 ; push byte value 10 on stack
if_icmpgt end_loop ; if local #0 > 10, goto end_loop
getstatic java/lang/System/out Ljava/io/PrintStream; ; push System.out on stack
iload 0 ; load local #0
invokevirtual java/io/PrintStream/println(I)V ; print it
iinc 0 1 ; increase local variable #0 by 1
goto loop
end_loop:
return
.end method
테스트:$ jasmin Loop.j
Generated: Loop.class
$ java Loop
1
2
3
4
5
6
7
8
9
10
새 운영 코드는 다음과 같습니다.iconst_1
- 1
을 스택으로 밀어 넣습니다. - 고유의 작동 코드가 있는 bipush 10
- 스택에서 10
- bipush
에서 8비트 큰 숫자, sipush
에서 16비트 푸시 - 더 큰 숫자는 항상 istore 0
및 iload 0
- 로컬 변수 저장 및 로드 iinc 0 1
- 로컬 변수 #0을 1로 늘려 마이너스로 goto label
- 태그 if_icmpgt label
- 처음 두 값이 서로 크면(icmpgt
의 경우 Integer CoMPare Greater Than
) java/io/PrintStream/println(Ljava/lang/String;)V
(인쇄 문자열)에서 java/io/PrintStream/println(I)V
(인쇄 int)으로 바뀌었다는 것을 주의하십시오. - JVM의 경우 이러한 방법은 완전히 독립적이고 관련이 없습니다.모든 JVM 언어에서 우리는 println(...)
을 말할 수 있는데 이것은 우리가 가리키는 것이 어떤 것인지 확실히 알 수 있다.그러나 이는 JVM이 <unk>거리다
우리는 현재
FizzBuzz
을 만드는 데 필요한 모든 것을 가지고 있다..class public FizzBuzz
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
.limit locals 1
.limit stack 2
iconst_0 ; push value 0 on stack
istore 0 ; save that to local variable #0
loop:
iinc 0 1 ; increase local variable #0 by 1
iload 0 ; push local #0 to stack
bipush 100 ; push byte value 10 on stack
if_icmpgt end_loop ; if local #0 > 100, goto end_loop
iload 0
bipush 15
irem
ifeq fizzbuzz
iload 0
iconst_5
irem
ifeq buzz
iload 0
iconst_3
irem
ifeq fizz
print_number:
getstatic java/lang/System/out Ljava/io/PrintStream; ; push System.out on stack
iload 0 ; load local #0
invokevirtual java/io/PrintStream/println(I)V ; print it
goto loop
fizz:
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Fizz"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
goto loop
buzz:
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Buzz"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
goto loop
fizzbuzz:
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "FizzBuzz"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
goto loop
end_loop:
return
.end method
우리가 필요로 하는 유일한 새로운 조작은 다음과 같다.iconst_X
운영 코드를 5로 늘리면 최적화된 iconst_3
및 iconst_5
을 사용할 수 있지만 더 큰 숫자에는 bipush 15
이 필요합니다.irem
%
운영 ifeq
과 기타 ifXX
의 조작 코드와 0
의 비교, if_icmpXX
의 조작 코드는 두 개의 정수치인 피보나치
페보나치를 정의하고
public static int fib(int n)
함수의 등가물로 계산합시다..class public Fib
.super java/lang/Object
.method public static fib(I)I
.limit stack 3
iload_0
iconst_2
if_icmple small_fib
big_fib:
iload_0
iconst_1
isub
invokestatic Fib/fib(I)I ; push fib(i-1) to stack
iload_0
iconst_2
isub
invokestatic Fib/fib(I)I ; push fib(i-2) to stack
iadd
ireturn ; return fib(i-1) + fib(i-2)
small_fib:
iconst_1
ireturn ; return 1
.end method
.method public static main([Ljava/lang/String;)V
.limit locals 1
.limit stack 2
iconst_1 ; push value 1 on stack
istore 0 ; save that to local variable #0
loop:
iload 0 ; push local #0 to stack
bipush 30 ; push byte value 10 on stack
if_icmpgt end_loop ; if local #0 > 10, goto end_loop
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "fib("
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V ; print "fib("
getstatic java/lang/System/out Ljava/io/PrintStream;
iload 0 ; load local #0
invokevirtual java/io/PrintStream/print(I)V ; print i
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc ")="
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V ; print ")="
getstatic java/lang/System/out Ljava/io/PrintStream; ; push System.out on stack
iload 0 ; load local #0
invokestatic Fib/fib(I)I
invokevirtual java/io/PrintStream/println(I)V ; print fib(i)
iinc 0 1 ; increase local variable #0 by 1
goto loop
end_loop:
return
.end method
$ jasmin Fib.j
Generated: Fib.class
$ java Fib
fib(1)=1
fib(2)=1
fib(3)=2
fib(4)=3
fib(5)=5
fib(6)=8
fib(7)=13
fib(8)=21
fib(9)=34
fib(10)=55
fib(11)=89
fib(12)=144
fib(13)=233
fib(14)=377
fib(15)=610
fib(16)=987
fib(17)=1597
fib(18)=2584
fib(19)=4181
fib(20)=6765
fib(21)=10946
fib(22)=17711
fib(23)=28657
fib(24)=46368
fib(25)=75025
fib(26)=121393
fib(27)=196418
fib(28)=317811
fib(29)=514229
fib(30)=832040
한 걸음 한 걸음 살펴보자.print(String)
, print(int)
과 println(int)
이 invokestatic Fib/fib(I)I
호출 클래스 int fib(int)
중의 정적 함수 Fib
, 우리는 현재 fib
내부에서 invokestatic Fib/fib(I)I
을 차례로 호출합니다iload_0
은 방법의 첫 번째 파라미터를 창고로 전송한다(파라미터는 국부 변수가 되기 때문에 같은 숫자를 공유한다) iadd
과 isub
의 정수 가감 ireturn
정수 Java 어셈블러
유행하는 관련 소프트웨어 패키지는 자바 어셈블러
javap
으로 .class
파일을 JVM 프로그램 집합으로 변환할 수 있다.불행하게도 javap
과 jasmin
은 세부적으로 일치하지 않는다.$ javap -c Fib.class
Compiled from "Fib.j"
public class Fib {
public static int fib(int);
Code:
0: iload_0
1: iconst_2
2: if_icmple 19
5: iload_0
6: iconst_1
7: isub
8: invokestatic #21 // Method fib:(I)I
11: iload_0
12: iconst_2
13: isub
14: invokestatic #21 // Method fib:(I)I
17: iadd
18: ireturn
19: iconst_1
20: ireturn
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore 0
3: iload 0
5: bipush 30
7: if_icmpgt 54
10: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
13: ldc #32 // String fib(
15: invokevirtual #24 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
18: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
21: iload 0
23: invokevirtual #29 // Method java/io/PrintStream.print:(I)V
26: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
29: ldc #14 // String )=
31: invokevirtual #24 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
34: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
37: iload 0
39: invokestatic #21 // Method fib:(I)I
42: invokevirtual #9 // Method java/io/PrintStream.println:(I)V
45: iinc_w 0, 1
51: goto 3
54: return
}
보시다시피 javap
은 demangled 이름을 사용합니다. 이것은 디지털 현식 인용 상수지를 사용하고 일부 조작 코드는 다릅니다. (예: iinc_w 0, 1
과 iinc 0 1
)인간
만약 작은 종류를 정의하지 않는다면, 이 회를 끝내는 것은 무의미하다.이를 위해 우리는 두 문자열 필드(
Person
, name
)와 surname
방법을 포함하는 toString
을 정의하기만 하면 된다.또한 정적 main
을 사용하여 테스트를 수행합니다.나는 코드에 주석을 추가했다.비정상적인 방법의 경우
this
이 첫 번째 추가 매개변수로 전달되므로 JVM의 관점에서 보면 로컬 변수는 다음과 같습니다.this
.class public Person
.super java/lang/Object
.field public name Ljava/lang/String;
.field public surname Ljava/lang/String;
.method public <init>(Ljava/lang/String;Ljava/lang/String;)V
.limit locals 4
.limit stack 4
; local 0 - this
; local 1 - argument name
; local 2 - argument surname
; call super this.<init>();
aload_0
invokespecial java/lang/Object/<init>()V
; this.name = argument_name
aload_0
aload_1
putfield Person/name Ljava/lang/String;
; this.surname = argument_surname
aload_0
aload_2
putfield Person/surname Ljava/lang/String;
return
.end method
.method public toString()Ljava/lang/String;
.limit locals 4
.limit stack 4
; local 0 - this
; push this.name
aload_0
getfield Person/name Ljava/lang/String;
; push " "
ldc " "
; call String.concat, getting: this.name + " "
invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String;
; push this.surname
aload_0
getfield Person/surname Ljava/lang/String;
; call String.concat, getting: this.name + " " + this.surname
invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String;
areturn
.end method
.method public static main([Ljava/lang/String;)V
.limit locals 4
.limit stack 4
; local Person a = new Person("Alice", "Smith")
new Person
dup
ldc "Alice"
ldc "Smith"
invokespecial Person/<init>(Ljava/lang/String;Ljava/lang/String;)V
astore_1
getstatic java/lang/System/out Ljava/io/PrintStream;
; push a.toString()
aload_1
invokevirtual Person/toString()Ljava/lang/String;
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
return
.end method
$ jasmin Person.j
Generated: Person.class
$ java Person
Alice Smith
JVM 어셈블리를 사용해야 합니까?
그것은 인류가 사용할 수 있는 것이고, 심지어는 일반적인 조립보다 적기 때문에 절대로 그렇지 않다.
또 하나의 문제는 일반적인 어셈블리나 LLVM 어셈블리와 달리 후자는 완전히 지원되는 표준 형식이 있다. Jasmin은 제3자 프로그램이고 서로 다른 JVM 어셈블리와 반어셈블리 프로그램은 여러 가지 측면에서 차이가 있다.Krakatau과 같은 업데이트된 어셈블러와 반어셈블러도 있습니다. 한번 해 보십시오.Krakatau의 구문은 Jasmin 또는 javap과 다릅니다.
JVM을 위한 새로운 언어를 개발하는 경우 JVM의 작동 방식을 이해하는 데 도움이 될 수 있지만 그 뿐입니다.
JVM 어셈블리에 익숙한 또 다른 방법은 GodBolt compiler site을 사용하지만, 언어 (자바, Kotlin 등) 만 컴파일하고 출력에서
javap
을 실행하기 때문에 로컬에서 실행할 수 있습니다.비밀 번호
All code examples for the series will be in this repository .
Code for the JVM Assembly with Jasmin episode is available here .
Reference
이 문제에 관하여(100개 언어 Speedrun: 77회: Jasmin의 JVM 조립), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/taw/100-languages-speedrun-episode-77-jvm-assembly-with-jasmin-74h텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)