Home | History | Annotate | Download | only in syscall
      1 #!/usr/bin/env perl
      2 # Copyright 2009 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_solaris.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 use strict;
     21 
     22 my $cmdline = "mksyscall_solaris.pl " . join(' ', @ARGV);
     23 my $errors = 0;
     24 my $_32bit = "";
     25 
     26 binmode STDOUT;
     27 
     28 if($ARGV[0] eq "-b32") {
     29 	$_32bit = "big-endian";
     30 	shift;
     31 } elsif($ARGV[0] eq "-l32") {
     32 	$_32bit = "little-endian";
     33 	shift;
     34 }
     35 
     36 if($ARGV[0] =~ /^-/) {
     37 	print STDERR "usage: mksyscall_solaris.pl [-b32 | -l32] [file ...]\n";
     38 	exit 1;
     39 }
     40 
     41 sub parseparamlist($) {
     42 	my ($list) = @_;
     43 	$list =~ s/^\s*//;
     44 	$list =~ s/\s*$//;
     45 	if($list eq "") {
     46 		return ();
     47 	}
     48 	return split(/\s*,\s*/, $list);
     49 }
     50 
     51 sub parseparam($) {
     52 	my ($p) = @_;
     53 	if($p !~ /^(\S*) (\S*)$/) {
     54 		print STDERR "$ARGV:$.: malformed parameter: $p\n";
     55 		$errors = 1;
     56 		return ("xx", "int");
     57 	}
     58 	return ($1, $2);
     59 }
     60 
     61 my $package = "";
     62 my $text = "";
     63 my $vars = "";
     64 my $dynimports = "";
     65 my $linknames = "";
     66 while(<>) {
     67 	chomp;
     68 	s/\s+/ /g;
     69 	s/^\s+//;
     70 	s/\s+$//;
     71 	$package = $1 if !$package && /^package (\S+)$/;
     72 	my $nonblock = /^\/\/sysnb /;
     73 	next if !/^\/\/sys / && !$nonblock;
     74 
     75 	my $syscalldot = "";
     76 	$syscalldot = "syscall." if $package ne "syscall";
     77 
     78 	# Line must be of the form
     79 	#	func Open(path string, mode int, perm int) (fd int, err error)
     80 	# Split into name, in params, out params.
     81 	if(!/^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$/) {
     82 		print STDERR "$ARGV:$.: malformed //sys declaration\n";
     83 		$errors = 1;
     84 		next;
     85 	}
     86 	my ($nb, $func, $in, $out, $modname, $sysname) = ($1, $2, $3, $4, $5, $6);
     87 
     88 	# Split argument lists on comma.
     89 	my @in = parseparamlist($in);
     90 	my @out = parseparamlist($out);
     91 
     92 	# So file name.
     93 	if($modname eq "") {
     94 		$modname = "libc";
     95 	}
     96 
     97 	# System call name.
     98 	if($sysname eq "") {
     99 		$sysname = "$func";
    100 	}
    101 
    102 	# System call pointer variable name.
    103 	my $sysvarname = "libc_$sysname";
    104 
    105 	my $strconvfunc = "BytePtrFromString";
    106 	my $strconvtype = "*byte";
    107 
    108 	# Library proc address variable.
    109 	$sysname =~ y/A-Z/a-z/; # All libc functions are lowercase.
    110 	if($vars eq "") {
    111 		$vars .= "\t$sysvarname";
    112 	} else {
    113 		$vars .= ",\n\t$sysvarname";
    114 	}
    115 	$dynimports .= "//go:cgo_import_dynamic $sysvarname $sysname \"$modname.so\"\n";
    116 	$linknames .= "//go:linkname $sysvarname $sysvarname\n";
    117 
    118 	# Go function header.
    119 	$out = join(', ', @out);
    120 	if($out ne "") {
    121 		$out = " ($out)";
    122 	}
    123 	if($text ne "") {
    124 		$text .= "\n"
    125 	}
    126 	$text .= sprintf "func %s(%s)%s {\n", $func, join(', ', @in), $out;
    127 
    128 	# Check if err return available
    129 	my $errvar = "";
    130 	foreach my $p (@out) {
    131 		my ($name, $type) = parseparam($p);
    132 		if($type eq "error") {
    133 			$errvar = $name;
    134 			last;
    135 		}
    136 	}
    137 
    138 	# Prepare arguments to Syscall.
    139 	my @args = ();
    140 	my @uses = ();
    141 	my $n = 0;
    142 	foreach my $p (@in) {
    143 		my ($name, $type) = parseparam($p);
    144 		if($type =~ /^\*/) {
    145 			push @args, "uintptr(unsafe.Pointer($name))";
    146 		} elsif($type eq "string" && $errvar ne "") {
    147 			$text .= "\tvar _p$n $strconvtype\n";
    148 			$text .= "\t_p$n, $errvar = $strconvfunc($name)\n";
    149 			$text .= "\tif $errvar != nil {\n\t\treturn\n\t}\n";
    150 			push @args, "uintptr(unsafe.Pointer(_p$n))";
    151 			push @uses, "use(unsafe.Pointer(_p$n))";
    152 			$n++;
    153 		} elsif($type eq "string") {
    154 			print STDERR "$ARGV:$.: $func uses string arguments, but has no error return\n";
    155 			$text .= "\tvar _p$n $strconvtype\n";
    156 			$text .= "\t_p$n, _ = $strconvfunc($name)\n";
    157 			push @args, "uintptr(unsafe.Pointer(_p$n))";
    158 			push @uses, "use(unsafe.Pointer(_p$n))";
    159 			$n++;
    160 		} elsif($type =~ /^\[\](.*)/) {
    161 			# Convert slice into pointer, length.
    162 			# Have to be careful not to take address of &a[0] if len == 0:
    163 			# pass nil in that case.
    164 			$text .= "\tvar _p$n *$1\n";
    165 			$text .= "\tif len($name) > 0 {\n\t\t_p$n = \&$name\[0]\n\t}\n";
    166 			push @args, "uintptr(unsafe.Pointer(_p$n))", "uintptr(len($name))";
    167 			$n++;
    168 		} elsif($type eq "int64" && $_32bit ne "") {
    169 			if($_32bit eq "big-endian") {
    170 				push @args, "uintptr($name >> 32)", "uintptr($name)";
    171 			} else {
    172 				push @args, "uintptr($name)", "uintptr($name >> 32)";
    173 			}
    174 		} elsif($type eq "bool") {
    175  			$text .= "\tvar _p$n uint32\n";
    176 			$text .= "\tif $name {\n\t\t_p$n = 1\n\t} else {\n\t\t_p$n = 0\n\t}\n";
    177 			push @args, "uintptr(_p$n)";
    178 			$n++;
    179 		} else {
    180 			push @args, "uintptr($name)";
    181 		}
    182 	}
    183 	my $nargs = @args;
    184 
    185 	# Determine which form to use; pad args with zeros.
    186 	my $asm = "${syscalldot}sysvicall6";
    187 	if ($nonblock) {
    188 		$asm = "${syscalldot}rawSysvicall6";
    189 	}
    190 	if(@args <= 6) {
    191 		while(@args < 6) {
    192 			push @args, "0";
    193 		}
    194 	} else {
    195 		print STDERR "$ARGV:$.: too many arguments to system call\n";
    196 	}
    197 
    198 	# Actual call.
    199 	my $args = join(', ', @args);
    200 	my $call = "$asm(uintptr(unsafe.Pointer(&$sysvarname)), $nargs, $args)";
    201 
    202 	# Assign return values.
    203 	my $body = "";
    204 	my $failexpr = "";
    205 	my @ret = ("_", "_", "_");
    206 	my @pout= ();
    207 	my $do_errno = 0;
    208 	for(my $i=0; $i<@out; $i++) {
    209 		my $p = $out[$i];
    210 		my ($name, $type) = parseparam($p);
    211 		my $reg = "";
    212 		if($name eq "err") {
    213 			$reg = "e1";
    214 			$ret[2] = $reg;
    215 			$do_errno = 1;
    216 		} else {
    217 			$reg = sprintf("r%d", $i);
    218 			$ret[$i] = $reg;
    219 		}
    220 		if($type eq "bool") {
    221 			$reg = "$reg != 0";
    222 		}
    223 		if($type eq "int64" && $_32bit ne "") {
    224 			# 64-bit number in r1:r0 or r0:r1.
    225 			if($i+2 > @out) {
    226 				print STDERR "$ARGV:$.: not enough registers for int64 return\n";
    227 			}
    228 			if($_32bit eq "big-endian") {
    229 				$reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i, $i+1);
    230 			} else {
    231 				$reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i+1, $i);
    232 			}
    233 			$ret[$i] = sprintf("r%d", $i);
    234 			$ret[$i+1] = sprintf("r%d", $i+1);
    235 		}
    236 		if($reg ne "e1") {
    237 			$body .= "\t$name = $type($reg)\n";
    238 		}
    239 	}
    240 	if ($ret[0] eq "_" && $ret[1] eq "_" && $ret[2] eq "_") {
    241 		$text .= "\t$call\n";
    242 	} else {
    243 		$text .= "\t$ret[0], $ret[1], $ret[2] := $call\n";
    244 	}
    245 	foreach my $use (@uses) {
    246 		$text .= "\t$use\n";
    247 	}
    248 	$text .= $body;
    249 
    250 	if ($do_errno) {
    251 		$text .= "\tif e1 != 0 {\n";
    252 		$text .= "\t\terr = errnoErr(e1)\n";
    253 		$text .= "\t}\n";
    254 	}
    255 	$text .= "\treturn\n";
    256 	$text .= "}\n";
    257 }
    258 
    259 if($errors) {
    260 	exit 1;
    261 }
    262 
    263 print <<EOF;
    264 // $cmdline
    265 // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
    266 
    267 package $package
    268 
    269 import "unsafe"
    270 EOF
    271 
    272 print "import \"syscall\"\n" if $package ne "syscall";
    273 
    274 print <<EOF;
    275 
    276 $dynimports
    277 $linknames
    278 type libcFunc uintptr
    279 
    280 var (
    281 $vars libcFunc
    282 )
    283 
    284 $text
    285 
    286 EOF
    287 exit 0;
    288