native: support more arithmetic, int/string arrays, function returns and internal_strlen (#10279)
							parent
							
								
									04e77419cc
								
							
						
					
					
						commit
						012f866619
					
				| 
						 | 
				
			
			@ -280,11 +280,12 @@ jobs:
 | 
			
		|||
          UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./v2 -o v.c cmd/v
 | 
			
		||||
      - name: Build V using V
 | 
			
		||||
        run: ./v -o v2 cmd/v && ./v2 -o v3 cmd/v
 | 
			
		||||
      - name: v self compilation with -usecache
 | 
			
		||||
        run: |
 | 
			
		||||
          ./v -o v2 -usecache cmd/v
 | 
			
		||||
          ./v2 -o v3 -usecache cmd/v
 | 
			
		||||
          ./v3 -usecache examples/tetris/tetris.v
 | 
			
		||||
# QTODO
 | 
			
		||||
#      - name: v self compilation with -usecache
 | 
			
		||||
#        run: |
 | 
			
		||||
#          ./v -o v2 -usecache cmd/v
 | 
			
		||||
#          ./v2 -o v3 -usecache cmd/v
 | 
			
		||||
#          ./v3 -usecache examples/tetris/tetris.v
 | 
			
		||||
      - name: Test symlink
 | 
			
		||||
        run: ./v symlink
 | 
			
		||||
      #    - name: Set up pg database
 | 
			
		||||
| 
						 | 
				
			
			@ -371,11 +372,12 @@ jobs:
 | 
			
		|||
        run: ./v -freestanding run vlib/os/bare/bare_example_linux.v
 | 
			
		||||
      - name: v self compilation
 | 
			
		||||
        run: ./v -o v2 cmd/v && ./v2 -o v3 cmd/v && ./v3 -o v4 cmd/v
 | 
			
		||||
      - name: v self compilation with -usecache
 | 
			
		||||
        run: |
 | 
			
		||||
          ./v -o v2 -usecache cmd/v
 | 
			
		||||
          ./v2 -o v3 -usecache cmd/v
 | 
			
		||||
          ./v3 -usecache examples/tetris/tetris.v
 | 
			
		||||
# QTODO
 | 
			
		||||
#      - name: v self compilation with -usecache
 | 
			
		||||
#        run: |
 | 
			
		||||
#          ./v -o v2 -usecache cmd/v
 | 
			
		||||
#          ./v2 -o v3 -usecache cmd/v
 | 
			
		||||
#          ./v3 -usecache examples/tetris/tetris.v
 | 
			
		||||
      - name: Verify `v test` works
 | 
			
		||||
        run: |
 | 
			
		||||
          ./v cmd/tools/test_if_v_test_system_works.v
 | 
			
		||||
| 
						 | 
				
			
			@ -460,11 +462,12 @@ jobs:
 | 
			
		|||
          ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./v5 -o v.c cmd/v
 | 
			
		||||
      - name: v self compilation
 | 
			
		||||
        run: ./v -o v2 cmd/v && ./v2 -o v3 cmd/v && ./v3 -o v4 cmd/v
 | 
			
		||||
      - name: v self compilation with -usecache
 | 
			
		||||
        run: |
 | 
			
		||||
          ./v -o v2 -usecache cmd/v
 | 
			
		||||
          ./v2 -o v3 -usecache cmd/v
 | 
			
		||||
          ./v3 -usecache examples/tetris/tetris.v
 | 
			
		||||
# QTODO
 | 
			
		||||
#      - name: v self compilation with -usecache
 | 
			
		||||
#        run: |
 | 
			
		||||
#          ./v -o v2 -usecache cmd/v
 | 
			
		||||
#          ./v2 -o v3 -usecache cmd/v
 | 
			
		||||
#          ./v3 -usecache examples/tetris/tetris.v
 | 
			
		||||
      - name: Verify `v test` works
 | 
			
		||||
        run: |
 | 
			
		||||
          ./v cmd/tools/test_if_v_test_system_works.v
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,6 @@ mut:
 | 
			
		|||
	// arm64 specific stuff for code generation
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// string_addr map[string]i64
 | 
			
		||||
// The registers are ordered for faster generation
 | 
			
		||||
// push rax => 50
 | 
			
		||||
// push rcx => 51 etc
 | 
			
		||||
| 
						 | 
				
			
			@ -39,9 +38,23 @@ const (
 | 
			
		|||
	fn_arg_registers = [Register.rdi, .rsi, .rdx, .rcx, .r8, .r9]
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) dec(reg Register) {
 | 
			
		||||
	g.write16(0xff48)
 | 
			
		||||
	match reg {
 | 
			
		||||
		.rax { g.write8(0xc8) }
 | 
			
		||||
		.rbx { g.write8(0xcb) }
 | 
			
		||||
		.rcx { g.write8(0xc9) }
 | 
			
		||||
		.rsi { g.write8(0xce) }
 | 
			
		||||
		.rdi { g.write8(0xcf) }
 | 
			
		||||
		.r12 { g.write8(0xc4) }
 | 
			
		||||
		else { panic('unhandled inc $reg') }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) inc(reg Register) {
 | 
			
		||||
	g.write16(0xff49)
 | 
			
		||||
	match reg {
 | 
			
		||||
		.rcx { g.write8(0xc1) }
 | 
			
		||||
		.r12 { g.write8(0xc4) }
 | 
			
		||||
		else { panic('unhandled inc $reg') }
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -149,20 +162,40 @@ fn (mut g Gen) jmp(addr i64) {
 | 
			
		|||
*/
 | 
			
		||||
fn (mut g Gen) mov64(reg Register, val i64) {
 | 
			
		||||
	match reg {
 | 
			
		||||
		.rax {
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
			g.write8(0xb8)
 | 
			
		||||
		}
 | 
			
		||||
		.rcx {
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
			g.write8(0xc7)
 | 
			
		||||
			g.write8(0xc1)
 | 
			
		||||
		}
 | 
			
		||||
		.rbx {
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
			g.write8(0xc7)
 | 
			
		||||
			g.write8(0xc3)
 | 
			
		||||
		}
 | 
			
		||||
		.rsi {
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
			g.write8(0xbe)
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			eprintln('unhandled mov $reg')
 | 
			
		||||
			eprintln('unhandled mov64 $reg')
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	g.write64(val)
 | 
			
		||||
	g.println('mov64 $reg, $val')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) mov_reg_to_rbp(var_offset int, reg Register) {
 | 
			
		||||
fn (mut g Gen) mov_reg_to_var(var_offset int, reg Register) {
 | 
			
		||||
	// 89 7d fc     mov DWORD PTR [rbp-0x4],edi
 | 
			
		||||
	match reg {
 | 
			
		||||
		.rax, .rsi {
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
		}
 | 
			
		||||
		else {}
 | 
			
		||||
	}
 | 
			
		||||
	g.write8(0x89)
 | 
			
		||||
	match reg {
 | 
			
		||||
		.eax, .rax { g.write8(0x45) }
 | 
			
		||||
| 
						 | 
				
			
			@ -178,12 +211,19 @@ fn (mut g Gen) mov_reg_to_rbp(var_offset int, reg Register) {
 | 
			
		|||
 | 
			
		||||
fn (mut g Gen) mov_var_to_reg(reg Register, var_offset int) {
 | 
			
		||||
	// 8b 7d f8          mov edi,DWORD PTR [rbp-0x8]
 | 
			
		||||
	match reg {
 | 
			
		||||
		.rax, .rsi {
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
		}
 | 
			
		||||
		else {}
 | 
			
		||||
	}
 | 
			
		||||
	g.write8(0x8b)
 | 
			
		||||
	match reg {
 | 
			
		||||
		.eax, .rax { g.write8(0x45) }
 | 
			
		||||
		.edi, .rdi { g.write8(0x7d) }
 | 
			
		||||
		.rsi { g.write8(0x75) }
 | 
			
		||||
		.rdx { g.write8(0x55) }
 | 
			
		||||
		.rbx { g.write8(0x5d) }
 | 
			
		||||
		.rcx { g.write8(0x4d) }
 | 
			
		||||
		else { verror('mov_var_to_reg $reg') }
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +297,7 @@ pub fn (mut g Gen) sub8(reg Register, val int) {
 | 
			
		|||
	g.println('sub8 $reg,$val.hex2()')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) add(reg Register, val int) {
 | 
			
		||||
pub fn (mut g Gen) sub(reg Register, val int) {
 | 
			
		||||
	g.write8(0x48)
 | 
			
		||||
	g.write8(0x81)
 | 
			
		||||
	g.write8(0xe8 + int(reg)) // TODO rax is different?
 | 
			
		||||
| 
						 | 
				
			
			@ -265,6 +305,16 @@ pub fn (mut g Gen) add(reg Register, val int) {
 | 
			
		|||
	g.println('add $reg,$val.hex2()')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) add(reg Register, val int) {
 | 
			
		||||
	if reg != .rax {
 | 
			
		||||
		panic('add only works with .rax')
 | 
			
		||||
	}
 | 
			
		||||
	g.write8(0x48)
 | 
			
		||||
	g.write8(0x05)
 | 
			
		||||
	g.write32(val)
 | 
			
		||||
	g.println('add $reg,$val.hex2()')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) add8(reg Register, val int) {
 | 
			
		||||
	g.write8(0x48)
 | 
			
		||||
	g.write8(0x83)
 | 
			
		||||
| 
						 | 
				
			
			@ -294,6 +344,16 @@ fn (mut g Gen) sub8_var(reg Register, var_offset int) {
 | 
			
		|||
	g.println('sub8 $reg,DWORD PTR[rbp-$var_offset.hex2()]')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) div8_var(reg Register, var_offset int) {
 | 
			
		||||
	if reg == .rax || reg == .eax {
 | 
			
		||||
		g.mov_var_to_reg(.rbx, var_offset)
 | 
			
		||||
		g.div_reg(.rax, .rbx)
 | 
			
		||||
		g.mov_reg_to_var(var_offset, .rax)
 | 
			
		||||
	} else {
 | 
			
		||||
		panic('div8_var invalid source register')
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) mul8_var(reg Register, var_offset int) {
 | 
			
		||||
	g.write8(0x0f)
 | 
			
		||||
	g.write8(0xaf)
 | 
			
		||||
| 
						 | 
				
			
			@ -327,17 +387,75 @@ pub fn (mut g Gen) save_main_fn_addr() {
 | 
			
		|||
	g.main_fn_addr = i64(g.buf.len)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) allocate_string(s string, opsize int) int {
 | 
			
		||||
	g.strings << s
 | 
			
		||||
	str_pos := g.buf.len + opsize
 | 
			
		||||
	g.str_pos << str_pos
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) cld_repne_scasb() {
 | 
			
		||||
	g.write8(0xfc)
 | 
			
		||||
	g.println('cld')
 | 
			
		||||
	g.write8(0xf2)
 | 
			
		||||
	g.write8(0xae)
 | 
			
		||||
	g.println('repne scasb')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) xor(r Register, v int) {
 | 
			
		||||
	if v == -1 {
 | 
			
		||||
		match r {
 | 
			
		||||
			.rcx {
 | 
			
		||||
				g.write8(0x48)
 | 
			
		||||
				g.write8(0x83)
 | 
			
		||||
				g.write8(0xf1)
 | 
			
		||||
				g.write8(0xff)
 | 
			
		||||
				g.println('xor rcx, -1')
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				verror('unhandled xor')
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		verror('unhandled xor')
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// return length in .rax of string pointed by given register
 | 
			
		||||
pub fn (mut g Gen) inline_strlen(r Register) {
 | 
			
		||||
	g.mov_reg(.rdi, r)
 | 
			
		||||
	g.mov(.rcx, -1)
 | 
			
		||||
	g.mov(.eax, 0)
 | 
			
		||||
	g.cld_repne_scasb()
 | 
			
		||||
	g.xor(.rcx, -1)
 | 
			
		||||
	g.dec(.rcx)
 | 
			
		||||
	g.mov_reg(.rax, .rcx)
 | 
			
		||||
	g.println('strlen rax, $r')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: strlen of string at runtime
 | 
			
		||||
pub fn (mut g Gen) gen_print_reg(r Register, n int) {
 | 
			
		||||
	mystrlen := true
 | 
			
		||||
	g.mov_reg(.rsi, r)
 | 
			
		||||
	if mystrlen {
 | 
			
		||||
		g.inline_strlen(.rsi)
 | 
			
		||||
		g.mov_reg(.rdx, .rax)
 | 
			
		||||
	} else {
 | 
			
		||||
		g.mov(.edx, n)
 | 
			
		||||
	}
 | 
			
		||||
	g.mov(.eax, g.nsyscall_write())
 | 
			
		||||
	g.mov(.edi, 1)
 | 
			
		||||
	g.syscall()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) gen_print(s string) {
 | 
			
		||||
	//
 | 
			
		||||
	// qq := s + '\n'
 | 
			
		||||
	//
 | 
			
		||||
	g.strings << s
 | 
			
		||||
	// g.string_addr[s] = str_pos
 | 
			
		||||
	g.mov(.eax, g.nsyscall_write())
 | 
			
		||||
	g.mov(.edi, 1)
 | 
			
		||||
	str_pos := g.buf.len + 2
 | 
			
		||||
	g.str_pos << str_pos
 | 
			
		||||
	g.mov64(.rsi, 0) // segment_start +  0x9f) // str pos // placeholder
 | 
			
		||||
	// segment_start +  0x9f) // str pos // placeholder
 | 
			
		||||
	g.mov64(.rsi, g.allocate_string(s, 2)) // for rsi its 2
 | 
			
		||||
	g.mov(.edx, s.len) // len
 | 
			
		||||
	g.syscall()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -399,6 +517,27 @@ pub fn (mut g Gen) gen_amd64_exit(expr ast.Expr) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) mov(reg Register, val int) {
 | 
			
		||||
	if val == -1 {
 | 
			
		||||
		match reg {
 | 
			
		||||
			.rax {
 | 
			
		||||
				g.write8(0x48)
 | 
			
		||||
				g.write8(0xc7)
 | 
			
		||||
				g.write8(0xc0)
 | 
			
		||||
				g.write32(-1)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			.rcx {
 | 
			
		||||
				g.write8(0x48)
 | 
			
		||||
				g.write8(0xc7)
 | 
			
		||||
				g.write8(0xc1)
 | 
			
		||||
				g.write32(-1)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				verror('unhandled mov $reg, -1')
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if val == 0 {
 | 
			
		||||
		// Optimise to xor reg, reg when val is 0
 | 
			
		||||
		match reg {
 | 
			
		||||
| 
						 | 
				
			
			@ -410,6 +549,16 @@ fn (mut g Gen) mov(reg Register, val int) {
 | 
			
		|||
				g.write8(0x31)
 | 
			
		||||
				g.write8(0xff)
 | 
			
		||||
			}
 | 
			
		||||
			.rcx {
 | 
			
		||||
				g.write8(0x48)
 | 
			
		||||
				g.write8(0x31)
 | 
			
		||||
				g.write8(0xc7)
 | 
			
		||||
			}
 | 
			
		||||
			.rdx {
 | 
			
		||||
				g.write8(0x48)
 | 
			
		||||
				g.write8(0x31)
 | 
			
		||||
				g.write8(0xd2)
 | 
			
		||||
			}
 | 
			
		||||
			.edx {
 | 
			
		||||
				g.write8(0x31)
 | 
			
		||||
				g.write8(0xd2)
 | 
			
		||||
| 
						 | 
				
			
			@ -425,7 +574,7 @@ fn (mut g Gen) mov(reg Register, val int) {
 | 
			
		|||
				g.write8(0xe4)
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				panic('unhandled mov $reg')
 | 
			
		||||
				verror('unhandled mov $reg, $reg')
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		g.println('xor $reg, $reg')
 | 
			
		||||
| 
						 | 
				
			
			@ -437,6 +586,9 @@ fn (mut g Gen) mov(reg Register, val int) {
 | 
			
		|||
			.edi, .rdi {
 | 
			
		||||
				g.write8(0xbf)
 | 
			
		||||
			}
 | 
			
		||||
			.rcx {
 | 
			
		||||
				g.write8(0xc7)
 | 
			
		||||
			}
 | 
			
		||||
			.edx {
 | 
			
		||||
				g.write8(0xba)
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -457,13 +609,97 @@ fn (mut g Gen) mov(reg Register, val int) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) mul_reg(a Register, b Register) {
 | 
			
		||||
	if a != .rax {
 | 
			
		||||
		panic('mul always operates on rax')
 | 
			
		||||
	}
 | 
			
		||||
	match b {
 | 
			
		||||
		.rax {
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
			g.write8(0xf7)
 | 
			
		||||
			g.write8(0xe8)
 | 
			
		||||
			g.println('mul $a')
 | 
			
		||||
		}
 | 
			
		||||
		.rbx {
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
			g.write8(0xf7)
 | 
			
		||||
			g.write8(0xeb)
 | 
			
		||||
			g.println('mul $a')
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			panic('unhandled div $a')
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) div_reg(a Register, b Register) {
 | 
			
		||||
	if a != .rax {
 | 
			
		||||
		panic('div always operates on rax')
 | 
			
		||||
	}
 | 
			
		||||
	match b {
 | 
			
		||||
		.rax {
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
			g.write8(0xf7)
 | 
			
		||||
			g.write8(0xf8)
 | 
			
		||||
			g.println('div $a')
 | 
			
		||||
		}
 | 
			
		||||
		.rbx {
 | 
			
		||||
			g.mov(.edx, 0)
 | 
			
		||||
			g.write8(0x48)
 | 
			
		||||
			g.write8(0xf7)
 | 
			
		||||
			g.write8(0xfb) // idiv ebx
 | 
			
		||||
			g.println('div $a')
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			panic('unhandled div $a')
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) sub_reg(a Register, b Register) {
 | 
			
		||||
	if a == .rax && b == .rbx {
 | 
			
		||||
		g.write8(0x48)
 | 
			
		||||
		g.write8(0x29)
 | 
			
		||||
		g.write8(0xd8)
 | 
			
		||||
		g.println('sub $a, $b')
 | 
			
		||||
	} else {
 | 
			
		||||
		panic('unhandled add $a, $b')
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) add_reg(a Register, b Register) {
 | 
			
		||||
	if a == .rax && b == .rbx {
 | 
			
		||||
		g.write8(0x48)
 | 
			
		||||
		g.write8(0x01)
 | 
			
		||||
		g.write8(0xd8)
 | 
			
		||||
		g.println('add $a, $b')
 | 
			
		||||
	} else {
 | 
			
		||||
		panic('unhandled add $a, $b')
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) mov_reg(a Register, b Register) {
 | 
			
		||||
	match a {
 | 
			
		||||
		.rbp {
 | 
			
		||||
	if a == .rbp && b == .rsp {
 | 
			
		||||
		g.write8(0x48)
 | 
			
		||||
		g.write8(0x89)
 | 
			
		||||
		}
 | 
			
		||||
		else {}
 | 
			
		||||
	} else if a == .rdx && b == .rax {
 | 
			
		||||
		g.write8(0x48)
 | 
			
		||||
		g.write8(0x89)
 | 
			
		||||
		g.write8(0xc2)
 | 
			
		||||
	} else if a == .rax && b == .rcx {
 | 
			
		||||
		g.write8(0x48)
 | 
			
		||||
		g.write8(0x89)
 | 
			
		||||
		g.write8(0xc8)
 | 
			
		||||
	} else if a == .rdi && b == .rsi {
 | 
			
		||||
		g.write8(0x48)
 | 
			
		||||
		g.write8(0x89)
 | 
			
		||||
		g.write8(0xf7)
 | 
			
		||||
	} else if a == .rsi && b == .rax {
 | 
			
		||||
		g.write8(0x48)
 | 
			
		||||
		g.write8(0x89)
 | 
			
		||||
		g.write8(0xc6)
 | 
			
		||||
	} else {
 | 
			
		||||
		verror('unhandled mov_reg combination for $a $b')
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -472,7 +708,7 @@ fn (mut g Gen) mov_rbp_rsp() {
 | 
			
		|||
	g.write8(0x48)
 | 
			
		||||
	g.write8(0x89)
 | 
			
		||||
	g.write8(0xe5)
 | 
			
		||||
	g.println('mov rbp,rsp')
 | 
			
		||||
	g.println('mov rbp, rsp')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) call_fn(node ast.CallExpr) {
 | 
			
		||||
| 
						 | 
				
			
			@ -481,8 +717,12 @@ pub fn (mut g Gen) call_fn(node ast.CallExpr) {
 | 
			
		|||
		return
 | 
			
		||||
	}
 | 
			
		||||
	name := node.name
 | 
			
		||||
	// println('call fn $name')
 | 
			
		||||
	addr := g.fn_addr[name]
 | 
			
		||||
	mut n := name
 | 
			
		||||
	if !n.contains('.') {
 | 
			
		||||
		n = 'main.$n'
 | 
			
		||||
	}
 | 
			
		||||
	println('call fn ($n)')
 | 
			
		||||
	addr := g.fn_addr[n]
 | 
			
		||||
	if addr == 0 {
 | 
			
		||||
		verror('fn addr of `$name` = 0')
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -526,17 +766,117 @@ fn (mut g Gen) assign_stmt(node ast.AssignStmt) {
 | 
			
		|||
		// ident := left as ast.Ident
 | 
			
		||||
		match right {
 | 
			
		||||
			ast.IntegerLiteral {
 | 
			
		||||
				// g.allocate_var(name, 4, right.val.int())
 | 
			
		||||
				match node.op {
 | 
			
		||||
					.plus_assign {
 | 
			
		||||
						dest := g.get_var_offset(name)
 | 
			
		||||
						g.mov_var_to_reg(.rax, dest)
 | 
			
		||||
						g.add(.rax, right.val.int())
 | 
			
		||||
						g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
					}
 | 
			
		||||
					.minus_assign {
 | 
			
		||||
						dest := g.get_var_offset(name)
 | 
			
		||||
						g.mov_var_to_reg(.rax, dest)
 | 
			
		||||
						g.mov_var_to_reg(.rbx, g.get_var_offset(name))
 | 
			
		||||
						g.sub_reg(.rax, .rbx)
 | 
			
		||||
						g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
					}
 | 
			
		||||
					.mult_assign {
 | 
			
		||||
						dest := g.get_var_offset(name)
 | 
			
		||||
						g.mov_var_to_reg(.rax, dest)
 | 
			
		||||
						g.mov_var_to_reg(.rbx, g.get_var_offset(name))
 | 
			
		||||
						g.mul_reg(.rax, .rbx)
 | 
			
		||||
						g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
					}
 | 
			
		||||
					.div_assign {
 | 
			
		||||
						dest := g.get_var_offset(name)
 | 
			
		||||
						g.mov_var_to_reg(.rax, dest)
 | 
			
		||||
						g.mov_var_to_reg(.rbx, g.get_var_offset(name))
 | 
			
		||||
						g.div_reg(.rax, .rbx)
 | 
			
		||||
						g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
					}
 | 
			
		||||
					.decl_assign {
 | 
			
		||||
						g.allocate_var(name, 4, right.val.int())
 | 
			
		||||
					}
 | 
			
		||||
					.assign {
 | 
			
		||||
						match node.left_types[i] {
 | 
			
		||||
							7 { // ast.IndexExpr {
 | 
			
		||||
								ie := node.left[i] as ast.IndexExpr
 | 
			
		||||
								bracket := name.index('[') or {
 | 
			
		||||
									verror('bracket expected')
 | 
			
		||||
									exit(1)
 | 
			
		||||
								}
 | 
			
		||||
								var_name := name[0..bracket]
 | 
			
		||||
								mut dest := g.get_var_offset(var_name)
 | 
			
		||||
								index := ie.index as ast.IntegerLiteral
 | 
			
		||||
								dest += index.val.int() * 8
 | 
			
		||||
								// TODO check if out of bounds access
 | 
			
		||||
								g.mov(.rax, right.val.int())
 | 
			
		||||
								g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
								// eprintln('${var_name}[$index] = ${right.val.int()}')
 | 
			
		||||
							}
 | 
			
		||||
							else {
 | 
			
		||||
								tn := node.left[i].type_name()
 | 
			
		||||
								dump(node.left_types)
 | 
			
		||||
								verror('unhandled assign type: $tn')
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					else {
 | 
			
		||||
						eprintln('ERROR 2')
 | 
			
		||||
						dump(node)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			ast.InfixExpr {
 | 
			
		||||
				// eprintln('infix') dump(node) dump(right)
 | 
			
		||||
				g.infix_expr(right)
 | 
			
		||||
				g.allocate_var(name, 4, 0)
 | 
			
		||||
				offset := g.allocate_var(name, 4, 0)
 | 
			
		||||
				// `mov DWORD PTR [rbp-0x8],eax`
 | 
			
		||||
				offset := g.get_var_offset(name)
 | 
			
		||||
				if g.pref.is_verbose {
 | 
			
		||||
					println('infix assignment $name offset=$offset.hex2()')
 | 
			
		||||
				}
 | 
			
		||||
				g.mov_reg_to_rbp(offset, .eax)
 | 
			
		||||
				g.mov_reg_to_var(offset, .eax)
 | 
			
		||||
			}
 | 
			
		||||
			ast.Ident {
 | 
			
		||||
				// eprintln('identr') dump(node) dump(right)
 | 
			
		||||
				match node.op {
 | 
			
		||||
					.plus_assign {
 | 
			
		||||
						dest := g.get_var_offset(name)
 | 
			
		||||
						g.mov_var_to_reg(.rax, dest)
 | 
			
		||||
						g.add8_var(.rax, g.get_var_offset(right.name))
 | 
			
		||||
						g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
					}
 | 
			
		||||
					.minus_assign {
 | 
			
		||||
						dest := g.get_var_offset(name)
 | 
			
		||||
						g.mov_var_to_reg(.rax, dest)
 | 
			
		||||
						g.mov_var_to_reg(.rbx, g.get_var_offset(right.name))
 | 
			
		||||
						g.sub_reg(.rax, .rbx)
 | 
			
		||||
						g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
					}
 | 
			
		||||
					.div_assign {
 | 
			
		||||
						// this should be called when `a /= b` but it's not :?
 | 
			
		||||
						dest := g.get_var_offset(name)
 | 
			
		||||
						g.mov_var_to_reg(.rax, dest)
 | 
			
		||||
						g.mov_var_to_reg(.rbx, g.get_var_offset(right.name))
 | 
			
		||||
						g.div_reg(.rax, .rbx)
 | 
			
		||||
						g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
					}
 | 
			
		||||
					.decl_assign {
 | 
			
		||||
						dest := g.allocate_var(name, 4, 0)
 | 
			
		||||
						g.mov_var_to_reg(.rax, g.get_var_offset(right.name))
 | 
			
		||||
						g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
					}
 | 
			
		||||
					.assign {
 | 
			
		||||
						g.mov_var_to_reg(.rax, g.get_var_offset(right.name))
 | 
			
		||||
						g.mov_reg_to_var(g.get_var_offset(name), .rax)
 | 
			
		||||
					}
 | 
			
		||||
					else {
 | 
			
		||||
						eprintln('TODO: unhandled assign ident case')
 | 
			
		||||
						dump(node)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				// a += b
 | 
			
		||||
			}
 | 
			
		||||
			ast.StructInit {
 | 
			
		||||
				sym := g.table.get_type_symbol(right.typ)
 | 
			
		||||
| 
						 | 
				
			
			@ -549,7 +889,58 @@ fn (mut g Gen) assign_stmt(node ast.AssignStmt) {
 | 
			
		|||
					g.allocate_var(field_name, 4, 0)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			ast.ArrayInit {
 | 
			
		||||
				// check if array is empty
 | 
			
		||||
				mut pos := g.allocate_array(name, 8, right.exprs.len)
 | 
			
		||||
				// allocate array of right.exprs.len vars
 | 
			
		||||
				for e in right.exprs {
 | 
			
		||||
					match e {
 | 
			
		||||
						ast.IntegerLiteral {
 | 
			
		||||
							g.mov(.rax, e.val.int())
 | 
			
		||||
							g.mov_reg_to_var(pos, .rax)
 | 
			
		||||
							pos += 8
 | 
			
		||||
						}
 | 
			
		||||
						ast.StringLiteral {
 | 
			
		||||
							g.mov64(.rsi, g.allocate_string('$e.val', 2)) // for rsi its 2
 | 
			
		||||
							g.mov_reg_to_var(pos, .rsi)
 | 
			
		||||
							pos += 8
 | 
			
		||||
						}
 | 
			
		||||
						else {
 | 
			
		||||
							dump(e)
 | 
			
		||||
							verror('unhandled array init type')
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			ast.IndexExpr {
 | 
			
		||||
				// a := arr[0]
 | 
			
		||||
				offset := g.allocate_var(name, 4, 0)
 | 
			
		||||
				if g.pref.is_verbose {
 | 
			
		||||
					println('infix assignment $name offset=$offset.hex2()')
 | 
			
		||||
				}
 | 
			
		||||
				ie := node.right[i] as ast.IndexExpr
 | 
			
		||||
				var_name := ie.left.str()
 | 
			
		||||
				mut dest := g.get_var_offset(var_name)
 | 
			
		||||
				index := ie.index as ast.IntegerLiteral
 | 
			
		||||
				dest += index.val.int() * 8
 | 
			
		||||
				// TODO check if out of bounds access
 | 
			
		||||
				g.mov_var_to_reg(.rax, dest)
 | 
			
		||||
				g.mov_reg_to_var(offset, .eax)
 | 
			
		||||
			}
 | 
			
		||||
			ast.StringLiteral {
 | 
			
		||||
				dest := g.allocate_var(name, 4, 0)
 | 
			
		||||
				ie := node.right[i] as ast.StringLiteral
 | 
			
		||||
				g.mov64(.rsi, g.allocate_string(ie.str(), 2)) // for rsi its 2
 | 
			
		||||
				g.mov_reg_to_var(dest, .rsi)
 | 
			
		||||
			}
 | 
			
		||||
			ast.CallExpr {
 | 
			
		||||
				dest := g.allocate_var(name, 4, 0)
 | 
			
		||||
				g.call_fn(right)
 | 
			
		||||
				g.mov_reg_to_var(dest, .rax)
 | 
			
		||||
				g.mov_var_to_reg(.rsi, dest)
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				// dump(node)
 | 
			
		||||
				g.error_with_pos('native assign_stmt unhandled expr: ' + right.type_name(),
 | 
			
		||||
					right.position())
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -559,12 +950,17 @@ fn (mut g Gen) assign_stmt(node ast.AssignStmt) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) infix_expr(node ast.InfixExpr) {
 | 
			
		||||
	if g.pref.is_verbose {
 | 
			
		||||
		println('infix expr op=$node.op')
 | 
			
		||||
	}
 | 
			
		||||
	// TODO
 | 
			
		||||
	if node.left is ast.InfixExpr {
 | 
			
		||||
		verror('only simple expressions are supported right now (not more than 2 operands)')
 | 
			
		||||
	}
 | 
			
		||||
	match mut node.left {
 | 
			
		||||
		ast.Ident { g.mov_var_to_reg(.eax, g.get_var_offset(node.left.name)) }
 | 
			
		||||
		ast.Ident {
 | 
			
		||||
			g.mov_var_to_reg(.eax, g.get_var_offset(node.left.name))
 | 
			
		||||
		}
 | 
			
		||||
		else {}
 | 
			
		||||
	}
 | 
			
		||||
	if mut node.right is ast.Ident {
 | 
			
		||||
| 
						 | 
				
			
			@ -572,6 +968,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
 | 
			
		|||
		match node.op {
 | 
			
		||||
			.plus { g.add8_var(.eax, var_offset) }
 | 
			
		||||
			.mul { g.mul8_var(.eax, var_offset) }
 | 
			
		||||
			.div { g.div8_var(.eax, var_offset) }
 | 
			
		||||
			.minus { g.sub8_var(.eax, var_offset) }
 | 
			
		||||
			else {}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -621,13 +1018,20 @@ fn (mut g Gen) for_stmt(node ast.ForStmt) {
 | 
			
		|||
	g.jmp(int(0xffffffff - (g.pos() + 5 - start) + 1))
 | 
			
		||||
	// Update the jump addr to current pos
 | 
			
		||||
	g.write32_at(jump_addr, int(g.pos() - jump_addr - 4)) // 4 is for "00 00 00 00"
 | 
			
		||||
	g.println('jpm after for')
 | 
			
		||||
	g.println('jmp after for')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut g Gen) fn_decl(node ast.FnDecl) {
 | 
			
		||||
	if g.pref.is_verbose {
 | 
			
		||||
		println(term.green('\n$node.name:'))
 | 
			
		||||
	}
 | 
			
		||||
	// if g.is_method
 | 
			
		||||
	if node.is_deprecated {
 | 
			
		||||
		eprintln('fn_decl: $node.name is deprecated')
 | 
			
		||||
	}
 | 
			
		||||
	if node.is_builtin {
 | 
			
		||||
		eprintln('fn_decl: $node.name is builtin')
 | 
			
		||||
	}
 | 
			
		||||
	g.stack_var_pos = 0
 | 
			
		||||
	is_main := node.name == 'main.main'
 | 
			
		||||
	// println('saving addr $node.name $g.buf.len.hex2()')
 | 
			
		||||
| 
						 | 
				
			
			@ -640,11 +1044,13 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) {
 | 
			
		|||
		g.fn_decl_arm64(node)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g.push(.rbp)
 | 
			
		||||
	g.mov_rbp_rsp()
 | 
			
		||||
	// if !is_main {
 | 
			
		||||
	g.sub8(.rsp, 0x10)
 | 
			
		||||
	// }
 | 
			
		||||
	locals_count := node.scope.objects.len + node.params.len
 | 
			
		||||
	stackframe_size := (locals_count * 8) + 0x10
 | 
			
		||||
	g.sub8(.rsp, stackframe_size)
 | 
			
		||||
 | 
			
		||||
	if node.params.len > 0 {
 | 
			
		||||
		// g.mov(.r12, 0x77777777)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -656,7 +1062,7 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) {
 | 
			
		|||
		g.allocate_var(name, 4, 0)
 | 
			
		||||
		// `mov DWORD PTR [rbp-0x4],edi`
 | 
			
		||||
		offset += 4
 | 
			
		||||
		g.mov_reg_to_rbp(offset, native.fn_arg_registers[i])
 | 
			
		||||
		g.mov_reg_to_var(offset, native.fn_arg_registers[i])
 | 
			
		||||
	}
 | 
			
		||||
	//
 | 
			
		||||
	g.stmts(node.stmts)
 | 
			
		||||
| 
						 | 
				
			
			@ -664,10 +1070,11 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) {
 | 
			
		|||
		// println('end of main: gen exit')
 | 
			
		||||
		zero := ast.IntegerLiteral{}
 | 
			
		||||
		g.gen_exit(zero)
 | 
			
		||||
		g.ret()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// g.leave()
 | 
			
		||||
	g.add8(.rsp, 0x10)
 | 
			
		||||
	g.add8(.rsp, stackframe_size)
 | 
			
		||||
	g.pop(.rbp)
 | 
			
		||||
	g.ret()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -676,7 +1083,13 @@ pub fn (mut x Amd64) allocate_var(name string, size int, initial_val int) {
 | 
			
		|||
	// do nothing as interface call is crashing
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) allocate_var(name string, size int, initial_val int) {
 | 
			
		||||
pub fn (mut g Gen) allocate_array(name string, size int, items int) int {
 | 
			
		||||
	pos := g.allocate_var(name, size, items)
 | 
			
		||||
	g.stack_var_pos += (size * items)
 | 
			
		||||
	return pos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) allocate_var(name string, size int, initial_val int) int {
 | 
			
		||||
	// `a := 3`  =>
 | 
			
		||||
	// `move DWORD [rbp-0x4],0x3`
 | 
			
		||||
	match size {
 | 
			
		||||
| 
						 | 
				
			
			@ -709,4 +1122,5 @@ pub fn (mut g Gen) allocate_var(name string, size int, initial_val int) {
 | 
			
		|||
	g.write32(initial_val)
 | 
			
		||||
	// println('allocate_var(size=$size, initial_val=$initial_val)')
 | 
			
		||||
	g.println('mov DWORD [rbp-$n.hex2()],$initial_val (Allocate var `$name`)')
 | 
			
		||||
	return g.stack_var_pos
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -221,7 +221,7 @@ fn (mut g Gen) write_string_with_padding(s string, max int) {
 | 
			
		|||
fn (mut g Gen) get_var_offset(var_name string) int {
 | 
			
		||||
	offset := g.var_offset[var_name]
 | 
			
		||||
	if offset == 0 {
 | 
			
		||||
		panic('0 offset for var `$var_name`')
 | 
			
		||||
		verror('unknown variable `$var_name`')
 | 
			
		||||
	}
 | 
			
		||||
	return offset
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -235,13 +235,24 @@ pub fn (mut g Gen) gen_print_from_expr(expr ast.Expr, newline bool) {
 | 
			
		|||
				g.gen_print(expr.val)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {}
 | 
			
		||||
		ast.CallExpr {
 | 
			
		||||
			g.call_fn(expr)
 | 
			
		||||
			g.gen_print_reg(.rax, 3)
 | 
			
		||||
		}
 | 
			
		||||
		ast.Ident {
 | 
			
		||||
			g.expr(expr)
 | 
			
		||||
			g.gen_print_reg(.rax, 3)
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			dump(expr)
 | 
			
		||||
			verror('expected string as argument for print')
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut g Gen) register_function_address(name string) {
 | 
			
		||||
	addr := g.pos()
 | 
			
		||||
	// println('reg fn addr $name $addr')
 | 
			
		||||
	// eprintln('register function $name = $addr')
 | 
			
		||||
	g.fn_addr[name] = addr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -325,10 +336,36 @@ fn (mut g Gen) stmt(node ast.Stmt) {
 | 
			
		|||
				g.write8(b)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ast.Module {}
 | 
			
		||||
		ast.Module {
 | 
			
		||||
			eprintln('module')
 | 
			
		||||
			dump(node)
 | 
			
		||||
		}
 | 
			
		||||
		ast.Return {
 | 
			
		||||
			zero := ast.IntegerLiteral{}
 | 
			
		||||
			g.gen_exit(zero)
 | 
			
		||||
			// dump(node.exprs[0])
 | 
			
		||||
			// if in main
 | 
			
		||||
			// zero := ast.IntegerLiteral{}
 | 
			
		||||
			// g.gen_exit(zero)
 | 
			
		||||
			dump(node)
 | 
			
		||||
			dump(node.types)
 | 
			
		||||
			mut s := '?' //${node.exprs[0].val.str()}'
 | 
			
		||||
			e0 := node.exprs[0]
 | 
			
		||||
			match e0 {
 | 
			
		||||
				ast.IntegerLiteral {
 | 
			
		||||
					// TODO
 | 
			
		||||
				}
 | 
			
		||||
				ast.StringLiteral {
 | 
			
		||||
					s = e0.val.str()
 | 
			
		||||
					eprintln('jlalala $s')
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					verror('unknown return type')
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			g.expr(node.exprs[0])
 | 
			
		||||
			g.mov64(.rax, g.allocate_string(s, 2))
 | 
			
		||||
			// intel specific
 | 
			
		||||
			g.add8(.rsp, 0x20) // XXX depends on scope frame size
 | 
			
		||||
			g.pop(.rbp)
 | 
			
		||||
			g.ret()
 | 
			
		||||
		}
 | 
			
		||||
		ast.StructDecl {}
 | 
			
		||||
| 
						 | 
				
			
			@ -342,7 +379,9 @@ fn C.strtol(str &char, endptr &&char, base int) int
 | 
			
		|||
 | 
			
		||||
fn (mut g Gen) expr(node ast.Expr) {
 | 
			
		||||
	match node {
 | 
			
		||||
		ast.ArrayInit {}
 | 
			
		||||
		ast.ArrayInit {
 | 
			
		||||
			verror('array init expr not supported yet')
 | 
			
		||||
		}
 | 
			
		||||
		ast.BoolLiteral {}
 | 
			
		||||
		ast.CallExpr {
 | 
			
		||||
			if node.name == 'exit' {
 | 
			
		||||
| 
						 | 
				
			
			@ -391,6 +430,8 @@ fn (mut g Gen) postfix_expr(node ast.PostfixExpr) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// not yet supported
 | 
			
		||||
[noreturn]
 | 
			
		||||
fn verror(s string) {
 | 
			
		||||
	util.verror('native gen error', s)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,7 +44,7 @@ fn test_add() {
 | 
			
		|||
	y := 3
 | 
			
		||||
	sum := x + y
 | 
			
		||||
	product := x * y
 | 
			
		||||
	diff := y - x
 | 
			
		||||
	// diff := y - x
 | 
			
		||||
	print_number(x)
 | 
			
		||||
	print_number(y)
 | 
			
		||||
	print_number(sum)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue