1#!/usr/bin/env perl
2# Copyright 2018 The Go Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style
4# license that can be found in the LICENSE file.
5
6# This program reads a file containing function prototypes
7# (like syscall_aix.go) and generates system call bodies.
8# The prototypes are marked by lines beginning with "//sys"
9# and read like func declarations if //sys is replaced by func, but:
10#	* The parameter lists must give a name for each argument.
11#	  This includes return parameters.
12#	* The parameter lists must give a type for each argument:
13#	  the (x, y, z int) shorthand is not allowed.
14#	* If the return parameter is an error number, it must be named err.
15#	* If go func name needs to be different than its libc name,
16#	* or the function is not in libc, name could be specified
17#	* at the end, after "=" sign, like
18#	  //sys getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error) = libsocket.getsockopt
19
20# This program will generate three files and handle both gc and gccgo implementation:
21#   - zsyscall_aix_ppc64.go: the common part of each implementation (error handler, pointer creation)
22#   - zsyscall_aix_ppc64_gc.go: gc part with //go_cgo_import_dynamic and a call to syscall6
23#   - zsyscall_aix_ppc64_gccgo.go: gccgo part with C function and conversion to C type.
24
25# The generated code looks like this
26#
27# zsyscall_aix_ppc64.go
28# func asyscall(...) (n int, err error) {
29#	  // Pointer Creation
30#	  r1, e1 := callasyscall(...)
31#	  // Type Conversion
32#	  // Error Handler
33#	  return
34# }
35#
36# zsyscall_aix_ppc64_gc.go
37# //go:cgo_import_dynamic libc_asyscall asyscall "libc.a/shr_64.o"
38# //go:linkname libc_asyscall libc_asyscall
39# var asyscall syscallFunc
40#
41# func callasyscall(...) (r1 uintptr, e1 Errno) {
42#	  r1, _, e1 = syscall6(uintptr(unsafe.Pointer(&libc_asyscall)), "nb_args", ... )
43#	  return
44# }
45#
46# zsyscall_aix_ppc64_ggcgo.go
47# /*
48#  int asyscall(...)
49#
50# */
51# import "C"
52#
53# func callasyscall(...) (r1 uintptr, e1 Errno) {
54#	  r1 = uintptr(C.asyscall(...))
55#	  e1 = syscall.GetErrno()
56#	  return
57# }
58
59
60
61use strict;
62
63my $cmdline = "mksyscall_aix_ppc64.pl " . join(' ', @ARGV);
64my $errors = 0;
65my $_32bit = "";
66my $tags = "";  # build tags
67my $aix = 0;
68my $solaris = 0;
69
70binmode STDOUT;
71
72if($ARGV[0] eq "-b32") {
73	$_32bit = "big-endian";
74	shift;
75} elsif($ARGV[0] eq "-l32") {
76	$_32bit = "little-endian";
77	shift;
78}
79if($ARGV[0] eq "-aix") {
80	$aix = 1;
81	shift;
82}
83if($ARGV[0] eq "-tags") {
84	shift;
85	$tags = $ARGV[0];
86	shift;
87}
88
89if($ARGV[0] =~ /^-/) {
90	print STDERR "usage: mksyscall_aix.pl [-b32 | -l32] [-tags x,y] [file ...]\n";
91	exit 1;
92}
93
94sub parseparamlist($) {
95	my ($list) = @_;
96	$list =~ s/^\s*//;
97	$list =~ s/\s*$//;
98	if($list eq "") {
99		return ();
100	}
101	return split(/\s*,\s*/, $list);
102}
103
104sub parseparam($) {
105	my ($p) = @_;
106	if($p !~ /^(\S*) (\S*)$/) {
107		print STDERR "$ARGV:$.: malformed parameter: $p\n";
108		$errors = 1;
109		return ("xx", "int");
110	}
111	return ($1, $2);
112}
113
114my $package = "";
115# GCCGO
116my $textgccgo = "";
117my $c_extern = "/*\n#include <stdint.h>\n";
118# GC
119my $textgc = "";
120my $dynimports = "";
121my $linknames = "";
122my @vars = ();
123# COMMUN
124my $textcommon = "";
125
126while(<>) {
127	chomp;
128	s/\s+/ /g;
129	s/^\s+//;
130	s/\s+$//;
131	$package = $1 if !$package && /^package (\S+)$/;
132	my $nonblock = /^\/\/sysnb /;
133	next if !/^\/\/sys / && !$nonblock;
134
135	# Line must be of the form
136	# func Open(path string, mode int, perm int) (fd int, err error)
137	# Split into name, in params, out params.
138	if(!/^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$/) {
139		print STDERR "$ARGV:$.: malformed //sys declaration\n";
140		$errors = 1;
141		next;
142	}
143	my ($nb, $func, $in, $out, $modname, $sysname) = ($1, $2, $3, $4, $5, $6);
144
145	# Split argument lists on comma.
146	my @in = parseparamlist($in);
147	my @out = parseparamlist($out);
148
149	$in = join(', ', @in);
150	$out = join(', ', @out);
151
152	if($sysname eq "") {
153		$sysname = "$func";
154	}
155
156	my $onlyCommon = 0;
157	if ($func eq "readlen" || $func eq "writelen" || $func eq "FcntlInt" || $func eq "FcntlFlock") {
158		# This function call another syscall which is already implemented.
159		# Therefore, the gc and gccgo part must not be generated.
160		$onlyCommon = 1
161	}
162
163	# Try in vain to keep people from editing this file.
164	# The theory is that they jump into the middle of the file
165	# without reading the header.
166
167	$textcommon .= "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n";
168	if (!$onlyCommon) {
169		$textgccgo .= "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n";
170		$textgc .= "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n";
171	}
172
173
174	# Check if value return, err return available
175	my $errvar = "";
176	my $retvar = "";
177	my $rettype = "";
178	foreach my $p (@out) {
179		my ($name, $type) = parseparam($p);
180		if($type eq "error") {
181			$errvar = $name;
182		} else {
183			$retvar = $name;
184			$rettype = $type;
185		}
186	}
187
188
189	$sysname =~ s/([a-z])([A-Z])/${1}_$2/g;
190	$sysname =~ y/A-Z/a-z/; # All libc functions are lowercase.
191
192	# GCCGO Prototype return type
193	my $C_rettype = "";
194	if($rettype eq "unsafe.Pointer") {
195		$C_rettype = "uintptr_t";
196	} elsif($rettype eq "uintptr") {
197		$C_rettype = "uintptr_t";
198	} elsif($rettype =~ /^_/) {
199		$C_rettype = "uintptr_t";
200	} elsif($rettype eq "int") {
201		$C_rettype = "int";
202	} elsif($rettype eq "int32") {
203		$C_rettype = "int";
204	} elsif($rettype eq "int64") {
205		$C_rettype = "long long";
206	} elsif($rettype eq "uint32") {
207		$C_rettype = "unsigned int";
208	} elsif($rettype eq "uint64") {
209		$C_rettype = "unsigned long long";
210	} else {
211		$C_rettype = "int";
212	}
213	if($sysname eq "exit") {
214		$C_rettype = "void";
215	}
216
217	# GCCGO Prototype arguments type
218	my @c_in = ();
219	foreach my $i (0 .. $#in) {
220		my ($name, $type) = parseparam($in[$i]);
221		if($type =~ /^\*/) {
222			push @c_in, "uintptr_t";
223			} elsif($type eq "string") {
224			push @c_in, "uintptr_t";
225		} elsif($type =~ /^\[\](.*)/) {
226			push @c_in, "uintptr_t", "size_t";
227		} elsif($type eq "unsafe.Pointer") {
228			push @c_in, "uintptr_t";
229		} elsif($type eq "uintptr") {
230			push @c_in, "uintptr_t";
231		} elsif($type =~ /^_/) {
232			push @c_in, "uintptr_t";
233		} elsif($type eq "int") {
234			if (($i == 0 || $i == 2) && $func eq "fcntl"){
235				# These fcntl arguments needs to be uintptr to be able to call FcntlInt and FcntlFlock
236				push @c_in, "uintptr_t";
237			} else {
238				push @c_in, "int";
239			}
240		} elsif($type eq "int32") {
241			push @c_in, "int";
242		} elsif($type eq "int64") {
243			push @c_in, "long long";
244		} elsif($type eq "uint32") {
245			push @c_in, "unsigned int";
246		} elsif($type eq "uint64") {
247			push @c_in, "unsigned long long";
248		} else {
249			push @c_in, "int";
250		}
251	}
252
253	if (!$onlyCommon){
254		# GCCGO Prototype Generation
255		# Imports of system calls from libc
256		$c_extern .= "$C_rettype $sysname";
257		my $c_in = join(', ', @c_in);
258		$c_extern .= "($c_in);\n";
259	}
260
261	# GC Library name
262	if($modname eq "") {
263		$modname = "libc.a/shr_64.o";
264	} else {
265		print STDERR "$func: only syscall using libc are available\n";
266		$errors = 1;
267		next;
268	}
269	my $sysvarname = "libc_${sysname}";
270
271	if (!$onlyCommon){
272		# GC Runtime import of function to allow cross-platform builds.
273		$dynimports .= "//go:cgo_import_dynamic ${sysvarname} ${sysname} \"$modname\"\n";
274		# GC Link symbol to proc address variable.
275		$linknames .= "//go:linkname ${sysvarname} ${sysvarname}\n";
276		# GC Library proc address variable.
277		push @vars, $sysvarname;
278	}
279
280	my $strconvfunc ="BytePtrFromString";
281	my $strconvtype = "*byte";
282
283	# Go function header.
284	if($out ne "") {
285		$out = " ($out)";
286	}
287	if($textcommon ne "") {
288		$textcommon .= "\n"
289	}
290
291	$textcommon .= sprintf "func %s(%s)%s {\n", $func, join(', ', @in), $out ;
292
293	# Prepare arguments to call.
294	my @argscommun = (); # Arguments in the commun part
295	my @argscall = ();   # Arguments for call prototype
296	my @argsgc = ();     # Arguments for gc call (with syscall6)
297	my @argsgccgo = ();  # Arguments for gccgo call (with C.name_of_syscall)
298	my $n = 0;
299	my $arg_n = 0;
300	foreach my $p (@in) {
301		my ($name, $type) = parseparam($p);
302		if($type =~ /^\*/) {
303			push @argscommun, "uintptr(unsafe.Pointer($name))";
304			push @argscall, "$name uintptr";
305			push @argsgc, "$name";
306			push @argsgccgo, "C.uintptr_t($name)";
307		} elsif($type eq "string" && $errvar ne "") {
308			$textcommon .= "\tvar _p$n $strconvtype\n";
309			$textcommon .= "\t_p$n, $errvar = $strconvfunc($name)\n";
310			$textcommon .= "\tif $errvar != nil {\n\t\treturn\n\t}\n";
311
312			push @argscommun, "uintptr(unsafe.Pointer(_p$n))";
313			push @argscall, "_p$n uintptr ";
314			push @argsgc, "_p$n";
315			push @argsgccgo, "C.uintptr_t(_p$n)";
316			$n++;
317		} elsif($type eq "string") {
318			print STDERR "$ARGV:$.: $func uses string arguments, but has no error return\n";
319			$textcommon .= "\tvar _p$n $strconvtype\n";
320			$textcommon .= "\t_p$n, $errvar = $strconvfunc($name)\n";
321			$textcommon .= "\tif $errvar != nil {\n\t\treturn\n\t}\n";
322
323			push @argscommun, "uintptr(unsafe.Pointer(_p$n))";
324			push @argscall, "_p$n uintptr";
325			push @argsgc, "_p$n";
326			push @argsgccgo, "C.uintptr_t(_p$n)";
327			$n++;
328		} elsif($type =~ /^\[\](.*)/) {
329			# Convert slice into pointer, length.
330			# Have to be careful not to take address of &a[0] if len == 0:
331			# pass nil in that case.
332			$textcommon .= "\tvar _p$n *$1\n";
333			$textcommon .= "\tif len($name) > 0 {\n\t\t_p$n = \&$name\[0]\n\t}\n";
334			push @argscommun, "uintptr(unsafe.Pointer(_p$n))", "len($name)";
335			push @argscall, "_p$n uintptr", "_lenp$n int";
336			push @argsgc, "_p$n", "uintptr(_lenp$n)";
337			push @argsgccgo, "C.uintptr_t(_p$n)", "C.size_t(_lenp$n)";
338			$n++;
339		} elsif($type eq "int64" && $_32bit ne "") {
340			print STDERR "$ARGV:$.: $func uses int64 with 32 bits mode. Case not yet implemented\n";
341			# if($_32bit eq "big-endian") {
342			# 	push @args, "uintptr($name >> 32)", "uintptr($name)";
343			# } else {
344			# 	push @args, "uintptr($name)", "uintptr($name >> 32)";
345			# }
346			# $n++;
347		} elsif($type eq "bool") {
348			print STDERR "$ARGV:$.: $func uses bool. Case not yet implemented\n";
349			# $text .= "\tvar _p$n uint32\n";
350			# $text .= "\tif $name {\n\t\t_p$n = 1\n\t} else {\n\t\t_p$n = 0\n\t}\n";
351			# push @args, "_p$n";
352			# $n++;
353		} elsif($type =~ /^_/ ||$type eq "unsafe.Pointer") {
354			push @argscommun, "uintptr($name)";
355			push @argscall, "$name uintptr";
356			push @argsgc, "$name";
357			push @argsgccgo, "C.uintptr_t($name)";
358		} elsif($type eq "int") {
359			if (($arg_n == 0 || $arg_n == 2) && ($func eq "fcntl" || $func eq "FcntlInt" || $func eq "FcntlFlock")) {
360				# These fcntl arguments need to be uintptr to be able to call FcntlInt and FcntlFlock
361				push @argscommun, "uintptr($name)";
362				push @argscall, "$name uintptr";
363				push @argsgc, "$name";
364				push @argsgccgo, "C.uintptr_t($name)";
365			} else {
366				push @argscommun, "$name";
367				push @argscall, "$name int";
368				push @argsgc, "uintptr($name)";
369				push @argsgccgo, "C.int($name)";
370			}
371		} elsif($type eq "int32") {
372			push @argscommun, "$name";
373			push @argscall, "$name int32";
374			push @argsgc, "uintptr($name)";
375			push @argsgccgo, "C.int($name)";
376		} elsif($type eq "int64") {
377			push @argscommun, "$name";
378			push @argscall, "$name int64";
379			push @argsgc, "uintptr($name)";
380			push @argsgccgo, "C.longlong($name)";
381		} elsif($type eq "uint32") {
382			push @argscommun, "$name";
383			push @argscall, "$name uint32";
384			push @argsgc, "uintptr($name)";
385			push @argsgccgo, "C.uint($name)";
386		} elsif($type eq "uint64") {
387			push @argscommun, "$name";
388			push @argscall, "$name uint64";
389			push @argsgc, "uintptr($name)";
390			push @argsgccgo, "C.ulonglong($name)";
391		} elsif($type eq "uintptr") {
392			push @argscommun, "$name";
393			push @argscall, "$name uintptr";
394			push @argsgc, "$name";
395			push @argsgccgo, "C.uintptr_t($name)";
396		} else {
397			push @argscommun, "int($name)";
398			push @argscall, "$name int";
399			push @argsgc, "uintptr($name)";
400			push @argsgccgo, "C.int($name)";
401		}
402		$arg_n++;
403	}
404	my $nargs = @argsgc;
405
406	# COMMUN function generation
407	my $argscommun = join(', ', @argscommun);
408	my $callcommun = "call$sysname($argscommun)";
409	my @ret = ("_", "_");
410	my $body = "";
411	my $do_errno = 0;
412	for(my $i=0; $i<@out; $i++) {
413		my $p = $out[$i];
414		my ($name, $type) = parseparam($p);
415		my $reg = "";
416		if($name eq "err") {
417			$reg = "e1";
418			$ret[1] = $reg;
419			$do_errno = 1;
420		} else {
421			$reg = "r0";
422			$ret[0] = $reg;
423		}
424		if($type eq "bool") {
425			$reg = "$reg != 0";
426		}
427		if($reg ne "e1") {
428			$body .= "\t$name = $type($reg)\n";
429		}
430	}
431	if ($ret[0] eq "_"  && $ret[1] eq "_") {
432		$textcommon .= "\t$callcommun\n";
433	} else {
434		$textcommon .= "\t$ret[0], $ret[1] := $callcommun\n";
435	}
436	$textcommon .= $body;
437
438	if ($do_errno) {
439		$textcommon .= "\tif e1 != 0 {\n";
440		$textcommon .= "\t\terr = errnoErr(e1)\n";
441		$textcommon .= "\t}\n";
442	}
443	$textcommon .= "\treturn\n";
444	$textcommon .= "}\n";
445
446	if ($onlyCommon){
447		next
448	}
449	# CALL Prototype
450	my $callProto = sprintf "func call%s(%s) (r1 uintptr, e1 Errno) {\n", $sysname, join(', ', @argscall);
451
452	# GC function generation
453	my $asm = "syscall6";
454	if ($nonblock) {
455		$asm = "rawSyscall6";
456	}
457
458	if(@argsgc <= 6) {
459		while(@argsgc < 6) {
460			push @argsgc, "0";
461		}
462	} else {
463		print STDERR "$ARGV:$.: too many arguments to system call\n";
464	}
465	my $argsgc = join(', ', @argsgc);
466	my $callgc = "$asm(uintptr(unsafe.Pointer(&$sysvarname)), $nargs, $argsgc)";
467
468	$textgc .= $callProto;
469	$textgc .= "\tr1, _, e1 = $callgc\n";
470	$textgc .= "\treturn\n}\n";
471
472	# GCCGO function generation
473	my $argsgccgo = join(', ', @argsgccgo);
474	my $callgccgo = "C.$sysname($argsgccgo)";
475	$textgccgo .= $callProto;
476	$textgccgo .= "\tr1 = uintptr($callgccgo)\n";
477	$textgccgo .= "\te1 = syscall.GetErrno()\n";
478	$textgccgo .= "\treturn\n}\n";
479}
480
481if($errors) {
482	exit 1;
483}
484
485# Print zsyscall_aix_ppc64.go
486open(my $fcommun, '>', 'zsyscall_aix_ppc64.go');
487my $tofcommun = <<EOF;
488// $cmdline
489// Code generated by the command above; see README.md. DO NOT EDIT.
490
491// +build $tags
492
493package $package
494
495import (
496	"unsafe"
497)
498
499EOF
500
501$tofcommun .= "import \"golang.org/x/sys/unix\"\n" if $package ne "unix";
502
503$tofcommun .=<<EOF;
504
505$textcommon
506EOF
507print $fcommun $tofcommun;
508
509
510# Print zsyscall_aix_ppc64_gc.go
511open(my $fgc, '>', 'zsyscall_aix_ppc64_gc.go');
512my $tofgc = <<EOF;
513// $cmdline
514// Code generated by the command above; see README.md. DO NOT EDIT.
515
516// +build $tags
517// +build !gccgo
518
519package $package
520
521import (
522	"unsafe"
523)
524
525
526EOF
527
528$tofgc .= "import \"golang.org/x/sys/unix\"\n" if $package ne "unix";
529
530my $vardecls = "\t" . join(",\n\t", @vars);
531$vardecls .= " syscallFunc";
532
533$tofgc .=<<EOF;
534$dynimports
535$linknames
536type syscallFunc uintptr
537
538var (
539$vardecls
540)
541
542// Implemented in runtime/syscall_aix.go.
543func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
544func syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
545
546$textgc
547EOF
548print $fgc $tofgc;
549
550# Print zsyscall_aix_ppc64_gc.go
551open(my $fgccgo, '>', 'zsyscall_aix_ppc64_gccgo.go');
552my $tofgccgo = <<EOF;
553// $cmdline
554// Code generated by the command above; see README.md. DO NOT EDIT.
555
556// +build $tags
557// +build gccgo
558
559package $package
560
561
562$c_extern
563*/
564import "C"
565import (
566	"syscall"
567)
568
569
570EOF
571
572$tofgccgo .= "import \"golang.org/x/sys/unix\"\n" if $package ne "unix";
573
574$tofgccgo .=<<EOF;
575
576$textgccgo
577EOF
578print $fgccgo $tofgccgo;
579exit 0;
580