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